diff --git a/assets/css/main.min.css b/assets/css/main.min.css index adc06838..3f9d5e34 100644 --- a/assets/css/main.min.css +++ b/assets/css/main.min.css @@ -1 +1 @@ -.backwpup-max-width{max-width:800px}@font-face{font-family:'backwpup';src:url("../fonts/backwpup.eot");src:url("../fonts/backwpup.eot?#iefix") format("embedded-opentype"),url("../fonts/backwpup.ttf") format("truetype"),url("../fonts/backwpup.woff") format("woff"),url("../fonts/backwpup.svg#backwpup") format("svg");font-weight:normal;font-style:normal}.bwu-message-error{color:red}.bwu-message-success{color:#86cf6f}.notice-inpsyde{border-left-color:#9DC55D}.notice-inpsyde .button--inpsyde{background-color:#9DC55D;color:#fff;border-color:#92be4a;box-shadow:0 1px 0 #92be4a}.notice-inpsyde .button--inpsyde:hover,.notice-inpsyde .button--inpsyde:focus{background-color:#92be4a;border-color:#85b03f;box-shadow:0 1px 0 #92be4a,0 0 3px #9DC55D;color:#fff}#wp-admin-bar-backwpup .ab-icon{font:normal 20px/1 'backwpup' !important}#wp-admin-bar-backwpup .ab-icon::before{content:"\e600";top:2px}@media screen and (max-width: 48.875em){#wp-admin-bar-backwpup .ab-icon{font:normal 32px/1 'backwpup' !important;display:block;text-indent:0;speak:none;top:7px;width:50px;text-align:center;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}#wp-admin-bar-backwpup::before{top:0}}#adminmenu #toplevel_page_backwpup div.wp-menu-image:before{font:normal 20px/1 'backwpup' !important;content:"\e600"}#backwpup-page{margin-bottom:100px}#backwpup-page hr{border:0;border-top:none;border-bottom:1px solid #eee}#backwpup-page .backwpup-floated-postbox{margin:25px 0 0;max-width:100%;overflow:hidden;padding:15px;position:relative}#backwpup-page .backwpup-floated-postbox h3 a{font-size:14px;text-decoration:none;font-weight:600}#backwpup-page .backwpup-cleared-postbox{clear:both;margin:25px 0 0;padding:10px}#backwpup-page .postbox ul{padding-left:10px}#backwpup-page .postbox table{caption-side:top;margin-bottom:25px}#backwpup-page .postbox table caption{color:#222;font-size:14px;font-weight:600;padding:8px 12px;margin:0;line-height:1.4;text-align:left}#backwpup-page .postbox .hndle{cursor:auto}#backwpup-page .postbox .backwpup-bullet-list{list-style:disc;padding-left:16px}#backwpup-page .postbox ol.backwpup-bullet-list{list-style:decimal}#backwpup-page .wizardbox{color:#fff;background:#1d94cf url("../images/hgbox.png") no-repeat right bottom;min-height:20em}#backwpup-page .wizardbox:hover{background-color:#0f79ae}#backwpup-page .wizardbox .wizardbox_name{color:#fff;margin-top:0}#backwpup-page .wizardbox .wizardbox_start{position:absolute;bottom:10px;left:10px}#backwpup-page .wizardbox select{max-width:100%;width:100%}#backwpup-page .button-bwp,#backwpup-page .button-primary-bwp{border-radius:0;-webkit-box-shadow:none;box-shadow:none}#backwpup-page .button-primary-bwp{border:none;background:#38b0eb;color:#fff;min-width:8em}#backwpup-page .button-primary-bwp:hover{background:#064565}#backwpup-page .backwpup-table-wrap{overflow-x:auto}#backwpup-page .backwpup-banner-img{display:block;height:auto;margin:26px auto;max-width:100%}#backwpup-page .backwpup-text-center{text-align:center}#backwpup-page ul.backwpup-text-center{padding-left:0}#backwpup-page .backwpup-message{background-color:#fff;border:none;border-left:4px solid #ccc;box-shadow:0px 1px 1px 0px rgba(0,0,0,0.1);margin:5px 0 15px;padding:1px 12px;-webkit-box-shadow:0px 1px 1px 0px rgba(0,0,0,0.1)}#backwpup-page .backwpup-message p{margin:0.5em 0;padding:2px}#backwpup-page .backwpup-info{border-left:4px solid #38b0eb}#backwpup-page .backwpup-warning{border-left:4px solid #ffba00}#backwpup-page .backwpup-full-width{max-width:100%}#backwpup-page .backwpup-full-width.action{margin-bottom:20px}#backwpup-page .notice ul{list-style-type:disc;margin-left:2em}@media screen and (min-width: 45em){#backwpup-page .backwpup-floated-postbox{float:left;margin:25px 25px 25px 0;width:290px}#backwpup-page #backwpup-one-click-backup,#backwpup-page #wizard-jobimport,#backwpup-page #backwpup-thank-you,#backwpup-page #backwpup-stats{margin-right:0}#backwpup-page #backwpup-one-click-backup+.backwpup-floated-postbox,#backwpup-page #backwpup-thank-you,#backwpup-page #backwpup-stats{clear:left}}@media screen and (min-width: 64em){#backwpup-page .backwpup-floated-postbox{width:362px}#backwpup-page .wizardbox{width:225px;min-height:225px}#backwpup-page .backwpup-max-width{max-width:800px}body[class*="_backwpupabout"] #backwpup-page .backwpup-welcome{max-width:none}}.progressbar{margin-top:10px;height:auto;background:#f6f6f6 url("../../assets/images/progressbarhg.jpg")}.progressbar #progresssteps{background-color:#007fb6}.progressbar .bwpu-progress{background-color:#1d94cf;color:#fff;padding:5px 0;text-align:center}#documentation_content p{font-size:14px;line-height:20px;color:#333}#documentation_content h3{padding:7px 0;font-size:22px}#documentation_content h4{padding:7px 0;font-size:18px}#documentation_content img.size-full{border:10px #f6f6f6 solid}#wpfooter #footer-left,#wpfooter #footer-right{overflow:hidden}#wpfooter #footer-left #footer-thankyou{clear:both;display:block;padding-top:.5em}#wpfooter #footer-left .backwpup-get-pro{display:inline-block;line-height:25px}#wpfooter #footer-upgrade .backwpup-update-footer{display:block;line-height:25px}#wpfooter .inpsyde_logo{display:block;margin-right:15px;margin-top:5px;line-height:25px;height:25px;width:80px;text-decoration:none}#wpfooter .inpsyde_logo svg{display:block}.backwpup_page_backwpupbackups .TB_overlayBG,.backwpup-pro_page_backwpupbackups .TB_overlayBG{pointer-events:none} +.backwpup-max-width{max-width:800px}@font-face{font-family:"backwpup";src:url("../fonts/backwpup.eot");src:url("../fonts/backwpup.eot?#iefix") format("embedded-opentype"),url("../fonts/backwpup.ttf") format("truetype"),url("../fonts/backwpup.woff") format("woff"),url("../fonts/backwpup.svg#backwpup") format("svg");font-weight:normal;font-style:normal}.bwu-message-error{color:red}.bwu-message-success{color:#86cf6f}.notice-inpsyde{border-left-color:#9dc55d}.notice-inpsyde .button--inpsyde{background-color:#9dc55d;color:#fff;border-color:#92be4a;box-shadow:0 1px 0 #92be4a}.notice-inpsyde .button--inpsyde:hover,.notice-inpsyde .button--inpsyde:focus{background-color:#92be4a;border-color:#85b03f;box-shadow:0 1px 0 #92be4a,0 0 3px #9dc55d;color:#fff}#wp-admin-bar-backwpup .ab-icon{font:normal 20px/1 "backwpup" !important}#wp-admin-bar-backwpup .ab-icon::before{content:"";top:2px}@media screen and (max-width: 48.875em){#wp-admin-bar-backwpup .ab-icon{font:normal 32px/1 "backwpup" !important;display:block;text-indent:0;speak:none;top:7px;width:50px;text-align:center;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}#wp-admin-bar-backwpup::before{top:0}}#adminmenu #toplevel_page_backwpup div.wp-menu-image:before{font:normal 20px/1 "backwpup" !important;content:""}#backwpup-page{margin-bottom:100px}#backwpup-page hr{border:0;border-top:none;border-bottom:1px solid #eee}#backwpup-page .backwpup-floated-postbox{margin:25px 0 0;max-width:100%;overflow:hidden;padding:15px;position:relative}#backwpup-page .backwpup-floated-postbox h3 a{font-size:14px;text-decoration:none;font-weight:600}#backwpup-page .backwpup-cleared-postbox{clear:both;margin:25px 0 0;padding:10px}#backwpup-page .postbox ul{padding-left:10px}#backwpup-page .postbox table{caption-side:top;margin-bottom:25px}#backwpup-page .postbox table caption{color:#222;font-size:14px;font-weight:600;padding:8px 12px;margin:0;line-height:1.4;text-align:left}#backwpup-page .postbox .hndle{cursor:auto}#backwpup-page .postbox .backwpup-bullet-list{list-style:disc;padding-left:16px}#backwpup-page .postbox ol.backwpup-bullet-list{list-style:decimal}#backwpup-page .wizardbox{color:#fff;background:#1d94cf url("../images/hgbox.png") no-repeat right bottom;min-height:20em}#backwpup-page .wizardbox:hover{background-color:#0f79ae}#backwpup-page .wizardbox .wizardbox_name{color:#fff;margin-top:0}#backwpup-page .wizardbox .wizardbox_start{position:absolute;bottom:10px;left:10px}#backwpup-page .wizardbox select{max-width:100%;width:100%}#backwpup-page .button-bwp,#backwpup-page .button-primary-bwp{border-radius:0;-webkit-box-shadow:none;box-shadow:none}#backwpup-page .button-primary-bwp{border:none;background:#38b0eb;color:#fff;min-width:8em}#backwpup-page .button-primary-bwp:hover{background:#064565}#backwpup-page .backwpup-table-wrap{overflow-x:auto}#backwpup-page .backwpup-banner-img{display:block;height:auto;margin:26px auto;max-width:100%}#backwpup-page .backwpup-text-center{text-align:center}#backwpup-page ul.backwpup-text-center{padding-left:0}#backwpup-page .backwpup-message{background-color:#fff;border:none;border-left:4px solid #ccc;box-shadow:0px 1px 1px 0px rgba(0,0,0,.1);margin:5px 0 15px;padding:1px 12px;-webkit-box-shadow:0px 1px 1px 0px rgba(0,0,0,.1)}#backwpup-page .backwpup-message p{margin:.5em 0;padding:2px}#backwpup-page .backwpup-info{border-left:4px solid #38b0eb}#backwpup-page .backwpup-warning{border-left:4px solid #ffba00}#backwpup-page .backwpup-full-width{max-width:100%}#backwpup-page .backwpup-full-width.action{margin-bottom:20px}#backwpup-page .notice ul{list-style-type:disc;margin-left:2em}@media screen and (min-width: 45em){#backwpup-page .backwpup-floated-postbox{float:left;margin:25px 25px 25px 0;width:290px}#backwpup-page #backwpup-one-click-backup,#backwpup-page #wizard-jobimport,#backwpup-page #backwpup-thank-you,#backwpup-page #backwpup-stats{margin-right:0}#backwpup-page #backwpup-one-click-backup+.backwpup-floated-postbox,#backwpup-page #backwpup-thank-you,#backwpup-page #backwpup-stats{clear:left}}@media screen and (min-width: 64em){#backwpup-page .backwpup-floated-postbox{width:362px}#backwpup-page .wizardbox{width:225px;min-height:225px}#backwpup-page .backwpup-max-width{max-width:800px}body[class*=_backwpupabout] #backwpup-page .backwpup-welcome{max-width:none}}.progressbar{margin-top:10px;height:auto;background:#f6f6f6 url("../../assets/images/progressbarhg.jpg")}.progressbar #progresssteps{background-color:#007fb6}.progressbar .bwpu-progress{background-color:#1d94cf;color:#fff;padding:5px 0;text-align:center}#documentation_content p{font-size:14px;line-height:20px;color:#333}#documentation_content h3{padding:7px 0;font-size:22px}#documentation_content h4{padding:7px 0;font-size:18px}#documentation_content img.size-full{border:10px #f6f6f6 solid}#wpfooter #footer-left,#wpfooter #footer-right{overflow:hidden}#wpfooter #footer-left #footer-thankyou{clear:both;display:block;padding-top:.5em}#wpfooter #footer-left .backwpup-get-pro{display:inline-block;line-height:25px}#wpfooter #footer-upgrade .backwpup-update-footer{display:block;line-height:25px}#wpfooter .inpsyde_logo{display:block;margin-right:15px;margin-top:5px;line-height:25px;height:25px;width:80px;text-decoration:none}#wpfooter .inpsyde_logo svg{display:block}.backwpup_page_backwpupbackups .TB_overlayBG,.backwpup-pro_page_backwpupbackups .TB_overlayBG{pointer-events:none} \ No newline at end of file diff --git a/assets/js/page_edit_jobtype_dbdump.js b/assets/js/page_edit_jobtype_dbdump.js index 0ef18d59..cc4dffbd 100644 --- a/assets/js/page_edit_jobtype_dbdump.js +++ b/assets/js/page_edit_jobtype_dbdump.js @@ -1,96 +1,123 @@ jQuery(document).ready(function ($) { + $("#dball").click(function () { + $('input[name="tabledb[]"]').prop("checked", true).change(); + }); - $('#dball').click(function () { - $('input[name="tabledb[]"]').prop("checked", true).change(); - }); + $("#dbnone").click(function () { + $('input[name="tabledb[]"]').prop("checked", false).change(); + }); - $('#dbnone').click(function () { - $('input[name="tabledb[]"]').prop("checked", false).change(); - }); + $("#dbwp").click(function () { + $('input[name="tabledb[]"]').prop("checked", false).change(); + $('input[name="tabledb[]"][value^="' + $("#dbwp").val() + '"]') + .prop("checked", true) + .change(); + }); - $('#dbwp').click(function () { - $('input[name="tabledb[]"]').prop("checked", false).change(); - $('input[name="tabledb[]"][value^="' + $('#dbwp').val() + '"]').prop("checked", true).change(); + var dbdumpwpdbsettings = $('input[name="dbdumpwpdbsettings"]'); + if (dbdumpwpdbsettings.length > 0) { + dbdumpwpdbsettings.change(function () { + if (dbdumpwpdbsettings.prop("checked")) { + $("#dbconnection").hide(); + } else { + $("#dbconnection").show(); + } }); + } - var dbdumpwpdbsettings = $('input[name="dbdumpwpdbsettings"]'); - if ( dbdumpwpdbsettings.length > 0 ) { - dbdumpwpdbsettings.change(function () { - if ( dbdumpwpdbsettings.prop("checked") ) { - $('#dbconnection').hide(); - } else { - $('#dbconnection').show(); - } - }); - } + var trdbdumpmysqlfolder = $("#trdbdumpmysqlfolder"); + $('input[name="dbdumptype"]').change(function () { + if ($("#iddbdumptype-syssql").prop("checked")) { + trdbdumpmysqlfolder.show(); + } else { + trdbdumpmysqlfolder.hide(); + } + }); - var trdbdumpmysqlfolder = $('#trdbdumpmysqlfolder'); - if ( trdbdumpmysqlfolder.length > 0 ) { - $('input[name="dbdumptype"]').change(function () { - if ( $('#iddbdumptype-syssql').prop("checked") ) { - trdbdumpmysqlfolder.show(); - } else { - trdbdumpmysqlfolder.hide(); - } - }); + $("a#fix-mysqldump-path").click(function (e) { + e.preventDefault(); + trdbdumpmysqlfolder.show(); + $("input", trdbdumpmysqlfolder).focus(); + }); - if ( $('#iddbdumptype-syssql').prop("checked") ) { - trdbdumpmysqlfolder.show(); - } else { - trdbdumpmysqlfolder.hide(); - } - } + if ($("#iddbdumptype-syssql").prop("checked")) { + trdbdumpmysqlfolder.show(); + } else { + trdbdumpmysqlfolder.hide(); + } - function db_tables() { - var data = { - action:'backwpup_jobtype_dbdump', - action2:'tables', - dbname:$('#dbdumpdbname').val(), - dbhost:$('#dbdumpdbhost').val(), - dbuser:$('#dbdumpdbuser').val(), - dbpassword:$('#dbdumpdbpassword').val(), - wpdbsettings:$('#dbdumpwpdbsettings:checked').val(), - jobid:$('#jobid').val(), - _ajax_nonce:$('#backwpupajaxnonce').val() - }; - $.post(ajaxurl, data, function (response) { - $('#dbtables').replaceWith(response); - }); - } - $('#dbdumpdbname').change(function () { - db_tables(); - }); - $('#dbdumpwpdbsettings').change(function () { - db_tables(); - db_databases(); + function db_tables() { + var data = { + action: "backwpup_jobtype_dbdump", + action2: "tables", + dbname: $("#dbdumpdbname").val(), + dbhost: $("#dbdumpdbhost").val(), + dbuser: $("#dbdumpdbuser").val(), + dbpassword: $("#dbdumpdbpassword").val(), + wpdbsettings: $("#dbdumpwpdbsettings:checked").val(), + jobid: $("#jobid").val(), + _ajax_nonce: $("#backwpupajaxnonce").val(), + }; + $.post(ajaxurl, data, function (response) { + $("#dbtables").replaceWith(response); }); + } - function db_databases() { - var data = { - action:'backwpup_jobtype_dbdump', - action2:'databases', - dbhost:$('#dbdumpdbhost').val(), - dbuser:$('#dbdumpdbuser').val(), - dbpassword:$('#dbdumpdbpassword').val(), - wpdbsettings:$('#dbdumpwpdbsettings:checked').val(), - _ajax_nonce:$('#backwpupajaxnonce').val() + $("#dbdumpdbname").change(function () { + db_tables(); + }); + $("#dbdumpwpdbsettings").change(function () { + db_tables(); + db_databases(); + db_charsets(); + }); - }; - $.post(ajaxurl, data, function (response) { - $('#dbdumpdbname').replaceWith(response); - db_tables(); - $('#dbdumpdbname').change(function () { - db_tables(); - }); - }); - } - $('#dbdumpdbhost').change(function () { - db_databases(); - }); - $('#dbdumpdbuser').change(function () { - db_databases(); + function db_databases() { + var data = { + action: "backwpup_jobtype_dbdump", + action2: "databases", + dbhost: $("#dbdumpdbhost").val(), + dbuser: $("#dbdumpdbuser").val(), + dbpassword: $("#dbdumpdbpassword").val(), + wpdbsettings: $("#dbdumpwpdbsettings:checked").val(), + _ajax_nonce: $("#backwpupajaxnonce").val(), + }; + $.post(ajaxurl, data, function (response) { + $("#dbdumpdbname").replaceWith(response); + db_tables(); + $("#dbdumpdbname").change(function () { + db_tables(); + }); }); - $('#dbdumpdbpassword').change(function () { - db_databases(); + } + + function db_charsets() { + var data = { + action: "backwpup_jobtype_dbdump", + action2: "charsets", + dbhost: $("#dbdumpdbhost").val(), + dbuser: $("#dbdumpdbuser").val(), + dbpassword: $("#dbdumpdbpassword").val(), + wpdbsettings: $("#dbdumpwpdbsettings:checked").val(), + jobid: $("#jobid").val(), + _ajax_nonce: $("#backwpupajaxnonce").val(), + }; + + $.post(ajaxurl, data, function (response) { + $("#dbdumpdbcharset").replaceWith(response); }); + } + + $("#dbdumpdbhost").change(function () { + db_databases(); + db_charsets(); + }); + $("#dbdumpdbuser").change(function () { + db_databases(); + db_charsets(); + }); + $("#dbdumpdbpassword").change(function () { + db_databases(); + db_charsets(); + }); }); diff --git a/assets/js/page_edit_jobtype_dbdump.min.js b/assets/js/page_edit_jobtype_dbdump.min.js index 2185d16a..8ad241b3 100644 --- a/assets/js/page_edit_jobtype_dbdump.min.js +++ b/assets/js/page_edit_jobtype_dbdump.min.js @@ -1 +1 @@ -jQuery(document).ready(function(n){n("#dball").click(function(){n('input[name="tabledb[]"]').prop("checked",!0).change()}),n("#dbnone").click(function(){n('input[name="tabledb[]"]').prop("checked",!1).change()}),n("#dbwp").click(function(){n('input[name="tabledb[]"]').prop("checked",!1).change(),n('input[name="tabledb[]"][value^="'+n("#dbwp").val()+'"]').prop("checked",!0).change()});var d=n('input[name="dbdumpwpdbsettings"]');0 BackWPup::get_plugin_data('version'), @@ -76,7 +77,6 @@ private function __construct() { // Load pro features if (self::$is_pro) { - $license = new License( get_site_option('license_product_id', ''), get_site_option('license_api_key', ''), @@ -91,29 +91,28 @@ private function __construct() { $pro->init(); } - // WP-Cron - if ( defined( 'DOING_CRON' ) && DOING_CRON ) { - if ( ! empty( $_GET['backwpup_run'] ) && class_exists( 'BackWPup_Job' ) ) { - // Early disable caches - BackWPup_Job::disable_caches(); - // Add action for running jobs in wp-cron.php - add_action( 'wp_loaded', array( 'BackWPup_Cron', 'cron_active' ), PHP_INT_MAX ); - } else { - // Add cron actions - add_action( 'backwpup_cron', array( 'BackWPup_Cron', 'run' ) ); - add_action( 'backwpup_check_cleanup', array( 'BackWPup_Cron', 'check_cleanup' ) ); - } - - // If in cron the rest is not needed - return; - } - - // Deactivation hook - register_deactivation_hook( __FILE__, array( 'BackWPup_Install', 'deactivate' ) ); - - // Only in backend - if ( is_admin() && class_exists( 'BackWPup_Admin' ) ) { + // WP-Cron + if (defined('DOING_CRON') && DOING_CRON) { + if (!empty($_GET['backwpup_run']) && class_exists(\BackWPup_Job::class)) { + // Early disable caches + BackWPup_Job::disable_caches(); + // Add action for running jobs in wp-cron.php + add_action('wp_loaded', [\BackWPup_Cron::class, 'cron_active'], PHP_INT_MAX); + } else { + // Add cron actions + add_action('backwpup_cron', [\BackWPup_Cron::class, 'run']); + add_action('backwpup_check_cleanup', [\BackWPup_Cron::class, 'check_cleanup']); + } + // If in cron the rest is not needed + return; + } + + // Deactivation hook + register_deactivation_hook(__FILE__, [\BackWPup_Install::class, 'deactivate']); + + // Only in backend + if (is_admin() && class_exists(\BackWPup_Admin::class)) { $settings_views = []; $settings_updaters = []; @@ -125,23 +124,23 @@ private function __construct() { $settings_views = array_merge( $settings_views, [ - new Settings\EncryptionSettingsView(), + new EncryptionSettingsView(), new LicenseSettingsView( $activate, $deactivate, $status - ) + ), ] ); $settings_updaters = array_merge( $settings_updaters, [ - new Settings\EncryptionSettingUpdater(), + new EncryptionSettingUpdater(), new LicenseSettingUpdater( $activate, $deactivate, $status - ) + ), ] ); } @@ -163,439 +162,447 @@ private function __construct() { } // Work with wp-cli - if ( defined( 'WP_CLI' ) && WP_CLI && method_exists( 'WP_CLI', 'add_command' ) ) { - WP_CLI::add_command( 'backwpup', 'BackWPup_WP_CLI' ); - } - } - - /** - * @return self - */ - public static function get_instance() { - - if ( null === self::$instance ) { - self::$instance = new self; - } - - return self::$instance; - } - - /** - * @return bool - */ - public static function is_pro() { - - return self::$is_pro; - } - - /** - * Prevent Cloning - */ - public function __clone() { - - wp_die( 'Cheatin’ huh?' ); - } - - /** - * Prevent deserialization - */ - public function __wakeup() { - - wp_die( 'Cheatin’ huh?' ); - } - - /** - * get information about the Plugin - * - * @param string $name Name of info to get or NULL to get all - * - * @return string|array - */ - public static function get_plugin_data( $name = null ) { - - if ( $name ) { - $name = strtolower( trim( $name ) ); - } - - if ( empty( self::$plugin_data ) ) { - self::$plugin_data = get_file_data( - __FILE__, - array( - 'name' => 'Plugin Name', - 'version' => 'Version', - ), - 'plugin' - ); - self::$plugin_data['name'] = trim( self::$plugin_data['name'] ); - //set some extra vars - self::$plugin_data['basename'] = plugin_basename( __DIR__ ); - self::$plugin_data['mainfile'] = __FILE__; - self::$plugin_data['plugindir'] = untrailingslashit( __DIR__ ); - self::$plugin_data['hash'] = get_site_option( 'backwpup_cfg_hash' ); - if ( empty( self::$plugin_data['hash'] ) || strlen( self::$plugin_data['hash'] ) < 6 - || strlen( - self::$plugin_data['hash'] - ) > 12 ) { - self::$plugin_data['hash'] = self::get_generated_hash(6); - update_site_option( 'backwpup_cfg_hash', self::$plugin_data['hash'] ); - } - if ( defined( 'WP_TEMP_DIR' ) && is_dir( WP_TEMP_DIR ) ) { - self::$plugin_data['temp'] = str_replace( - '\\', - '/', - get_temp_dir() - ) . 'backwpup-' . self::$plugin_data['hash'] . '/'; - } else { - $upload_dir = wp_upload_dir(); - self::$plugin_data['temp'] = str_replace( - '\\', - '/', - $upload_dir['basedir'] - ) . '/backwpup-' . self::$plugin_data['hash'] . '-temp/'; - } - self::$plugin_data['running_file'] = self::$plugin_data['temp'] . 'backwpup-working.php'; - self::$plugin_data['url'] = plugins_url( '', __FILE__ ); - self::$plugin_data['cacert'] = apply_filters( - 'backwpup_cacert_bundle', - ABSPATH . WPINC . '/certificates/ca-bundle.crt' - ); - //get unmodified WP Versions - include ABSPATH . WPINC . '/version.php'; - /** @var $wp_version string */ - self::$plugin_data['wp_version'] = $wp_version; - //Build User Agent - self::$plugin_data['user-agent'] = self::$plugin_data['name'] . '/' . self::$plugin_data['version'] . '; WordPress/' . self::$plugin_data['wp_version'] . '; ' . home_url(); - } - - if ( ! empty( $name ) ) { - return self::$plugin_data[ $name ]; - } else { - return self::$plugin_data; - } - } + if (defined(\WP_CLI::class) && WP_CLI && method_exists(\WP_CLI::class, 'add_command')) { + WP_CLI::add_command('backwpup', \BackWPup_WP_CLI::class); + } + } + + /** + * @return self + */ + public static function get_instance() + { + if (null === self::$instance) { + self::$instance = new self(); + } + + return self::$instance; + } + + /** + * @return bool + */ + public static function is_pro() + { + return self::$is_pro; + } + + /** + * Prevent Cloning. + */ + public function __clone() + { + wp_die('Cheatin’ huh?'); + } + + /** + * Prevent deserialization. + */ + public function __wakeup() + { + wp_die('Cheatin’ huh?'); + } + + /** + * get information about the Plugin. + * + * @param string $name Name of info to get or NULL to get all + * + * @return string|array + */ + public static function get_plugin_data($name = null) + { + if ($name) { + $name = strtolower(trim($name)); + } + + if (empty(self::$plugin_data)) { + self::$plugin_data = get_file_data( + __FILE__, + [ + 'name' => 'Plugin Name', + 'version' => 'Version', + ], + 'plugin' + ); + self::$plugin_data['name'] = trim(self::$plugin_data['name']); + //set some extra vars + self::$plugin_data['basename'] = plugin_basename(__DIR__); + self::$plugin_data['mainfile'] = __FILE__; + self::$plugin_data['plugindir'] = untrailingslashit(__DIR__); + self::$plugin_data['hash'] = get_site_option('backwpup_cfg_hash'); + if (empty(self::$plugin_data['hash']) || strlen(self::$plugin_data['hash']) < 6 + || strlen( + self::$plugin_data['hash'] + ) > 12) { + self::$plugin_data['hash'] = self::get_generated_hash(6); + update_site_option('backwpup_cfg_hash', self::$plugin_data['hash']); + } + if (defined('WP_TEMP_DIR') && is_dir(WP_TEMP_DIR)) { + self::$plugin_data['temp'] = str_replace( + '\\', + '/', + get_temp_dir() + ) . 'backwpup-' . self::$plugin_data['hash'] . '/'; + } else { + $upload_dir = wp_upload_dir(); + self::$plugin_data['temp'] = str_replace( + '\\', + '/', + $upload_dir['basedir'] + ) . '/backwpup-' . self::$plugin_data['hash'] . '-temp/'; + } + self::$plugin_data['running_file'] = self::$plugin_data['temp'] . 'backwpup-working.php'; + self::$plugin_data['url'] = plugins_url('', __FILE__); + self::$plugin_data['cacert'] = apply_filters( + 'backwpup_cacert_bundle', + ABSPATH . WPINC . '/certificates/ca-bundle.crt' + ); + //get unmodified WP Versions + include ABSPATH . WPINC . '/version.php'; + /** @var string $wp_version */ + self::$plugin_data['wp_version'] = $wp_version; + //Build User Agent + self::$plugin_data['user-agent'] = self::$plugin_data['name'] . '/' . self::$plugin_data['version'] . '; WordPress/' . self::$plugin_data['wp_version'] . '; ' . home_url(); + } + + if (!empty($name)) { + return self::$plugin_data[$name]; + } + + return self::$plugin_data; + } /** - * Generates a random hash + * Generates a random hash. * * @param int $length * * @return string */ - public static function get_generated_hash( $length = 6 ) { - - $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + public static function get_generated_hash($length = 6) + { + $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; $hash = ''; - for ( $i = 0; $i < 254; $i++ ) { - $hash .= $chars[mt_rand(0, 61)]; + + for ($i = 0; $i < 254; ++$i) { + $hash .= $chars[random_int(0, 61)]; + } + + return substr(md5($hash), random_int(0, 31 - $length), $length); + } + + /** + * Load Plugin Translation. + * + * @return bool Text domain loaded + */ + public static function load_text_domain() + { + if (is_textdomain_loaded('backwpup')) { + return true; + } + + return load_plugin_textdomain('backwpup', false, dirname(plugin_basename(__FILE__)) . '/languages'); + } + + /** + * Get a array of instances for Backup Destination's. + * + * @param $key string Key of Destination where get class instance from + * + * @return array BackWPup_Destinations + */ + public static function get_destination($key) + { + $key = strtoupper($key); + + if (isset(self::$destinations[$key]) && is_object(self::$destinations[$key])) { + return self::$destinations[$key]; + } + + $reg_dests = self::get_registered_destinations(); + if (!empty($reg_dests[$key]['class'])) { + self::$destinations[$key] = new $reg_dests[$key]['class'](); + } else { + return null; + } + + return self::$destinations[$key]; + } + + /** + * Get a array of registered Destination's for Backups. + * + * @return array BackWPup_Destinations + */ + public static function get_registered_destinations() + { + //only run it one time + if (!empty(self::$registered_destinations)) { + return self::$registered_destinations; + } + + //add BackWPup Destinations + // to folder + self::$registered_destinations['FOLDER'] = [ + 'class' => \BackWPup_Destination_Folder::class, + 'info' => [ + 'ID' => 'FOLDER', + 'name' => __('Folder', 'backwpup'), + 'description' => __('Backup to Folder', 'backwpup'), + ], + 'can_sync' => false, + 'needed' => [ + 'php_version' => '', + 'functions' => [], + 'classes' => [], + ], + ]; + // backup with mail + self::$registered_destinations['EMAIL'] = [ + 'class' => \BackWPup_Destination_Email::class, + 'info' => [ + 'ID' => 'EMAIL', + 'name' => __('Email', 'backwpup'), + 'description' => __('Backup sent via email', 'backwpup'), + ], + 'can_sync' => false, + 'needed' => [ + 'php_version' => '', + 'functions' => [], + 'classes' => [], + ], + ]; + // backup to ftp + self::$registered_destinations['FTP'] = [ + 'class' => \BackWPup_Destination_Ftp::class, + 'info' => [ + 'ID' => 'FTP', + 'name' => __('FTP', 'backwpup'), + 'description' => __('Backup to FTP', 'backwpup'), + ], + 'can_sync' => false, + 'needed' => [ + 'php_version' => '', + 'functions' => ['ftp_nb_fput'], + 'classes' => [], + ], + ]; + // backup to dropbox + self::$registered_destinations['DROPBOX'] = [ + 'class' => \BackWPup_Destination_Dropbox::class, + 'info' => [ + 'ID' => 'DROPBOX', + 'name' => __('Dropbox', 'backwpup'), + 'description' => __('Backup to Dropbox', 'backwpup'), + ], + 'can_sync' => false, + 'needed' => [ + 'php_version' => '', + 'functions' => ['curl_exec'], + 'classes' => [], + ], + ]; + // Backup to S3 + self::$registered_destinations['S3'] = [ + 'class' => \BackWPup_Destination_S3::class, + 'info' => [ + 'ID' => 'S3', + 'name' => __('S3 Service', 'backwpup'), + 'description' => __('Backup to an S3 Service', 'backwpup'), + ], + 'can_sync' => false, + 'needed' => [ + 'php_version' => '', + 'functions' => ['curl_exec'], + 'classes' => [\XMLWriter::class], + ], + ]; + // backup to MS Azure + self::$registered_destinations['MSAZURE'] = [ + 'class' => \BackWPup_Destination_MSAzure::class, + 'info' => [ + 'ID' => 'MSAZURE', + 'name' => __('MS Azure', 'backwpup'), + 'description' => __('Backup to Microsoft Azure (Blob)', 'backwpup'), + ], + 'can_sync' => false, + 'needed' => [ + 'php_version' => '5.6.0', + 'functions' => [], + 'classes' => [], + ], + ]; + // backup to Rackspace Cloud + self::$registered_destinations['RSC'] = [ + 'class' => \BackWPup_Destination_RSC::class, + 'info' => [ + 'ID' => 'RSC', + 'name' => __('RSC', 'backwpup'), + 'description' => __('Backup to Rackspace Cloud Files', 'backwpup'), + ], + 'can_sync' => false, + 'needed' => [ + 'php_version' => '', + 'functions' => ['curl_exec'], + 'classes' => [], + ], + ]; + // backup to Sugarsync + self::$registered_destinations['SUGARSYNC'] = [ + 'class' => \BackWPup_Destination_SugarSync::class, + 'info' => [ + 'ID' => 'SUGARSYNC', + 'name' => __('SugarSync', 'backwpup'), + 'description' => __('Backup to SugarSync', 'backwpup'), + ], + 'can_sync' => false, + 'needed' => [ + 'php_version' => '', + 'functions' => ['curl_exec'], + 'classes' => [], + ], + ]; + + //Hook for adding Destinations like above + self::$registered_destinations = apply_filters( + 'backwpup_register_destination', + self::$registered_destinations + ); + + //check BackWPup Destinations + foreach (self::$registered_destinations as $dest_key => $dest) { + self::$registered_destinations[$dest_key]['error'] = ''; + // check PHP Version + if (!empty($dest['needed']['php_version']) + && version_compare( + PHP_VERSION, + $dest['needed']['php_version'], + '<' + )) { + self::$registered_destinations[$dest_key]['error'] .= sprintf( + __( + 'PHP Version %1$s is to low, you need Version %2$s or above.', + 'backwpup' + ), + PHP_VERSION, + $dest['needed']['php_version'] + ) . ' '; + self::$registered_destinations[$dest_key]['class'] = null; + } + //check functions exists + if (!empty($dest['needed']['functions'])) { + foreach ($dest['needed']['functions'] as $function_need) { + if (!function_exists($function_need)) { + self::$registered_destinations[$dest_key]['error'] .= sprintf( + __( + 'Missing function "%s".', + 'backwpup' + ), + $function_need + ) . ' '; + self::$registered_destinations[$dest_key]['class'] = null; + } + } + } + //check classes exists + if (!empty($dest['needed']['classes'])) { + foreach ($dest['needed']['classes'] as $class_need) { + if (!class_exists($class_need)) { + self::$registered_destinations[$dest_key]['error'] .= sprintf( + __( + 'Missing class "%s".', + 'backwpup' + ), + $class_need + ) . ' '; + self::$registered_destinations[$dest_key]['class'] = null; + } + } + } + } + + return self::$registered_destinations; + } + + /** + * Gets a array of instances from Job types. + * + * @return array BackWPup_JobTypes + */ + public static function get_job_types() + { + if (!empty(self::$job_types)) { + return self::$job_types; + } + + self::$job_types['DBDUMP'] = new BackWPup_JobType_DBDump(); + self::$job_types['FILE'] = new BackWPup_JobType_File(); + self::$job_types['WPEXP'] = new BackWPup_JobType_WPEXP(); + self::$job_types['WPPLUGIN'] = new BackWPup_JobType_WPPlugin(); + self::$job_types['DBCHECK'] = new BackWPup_JobType_DBCheck(); + + self::$job_types = apply_filters('backwpup_job_types', self::$job_types); + + //remove types can't load + foreach (self::$job_types as $key => $job_type) { + if (empty($job_type) || !is_object($job_type)) { + unset(self::$job_types[$key]); + } } - return substr(md5($hash), mt_rand(0, 31 - $length), $length); + return self::$job_types; } - /** - * Load Plugin Translation - * - * @return bool Text domain loaded - */ - public static function load_text_domain() { - - if ( is_textdomain_loaded( 'backwpup' ) ) { - return true; - } - - return load_plugin_textdomain( 'backwpup', false, dirname( plugin_basename( __FILE__ ) ) . '/languages' ); - } - - /** - * Get a array of instances for Backup Destination's - * - * @param $key string Key of Destination where get class instance from - * - * @return array BackWPup_Destinations - */ - public static function get_destination( $key ) { - - $key = strtoupper( $key ); - - if ( isset( self::$destinations[ $key ] ) && is_object( self::$destinations[ $key ] ) ) { - return self::$destinations[ $key ]; - } - - $reg_dests = self::get_registered_destinations(); - if ( ! empty( $reg_dests[ $key ]['class'] ) ) { - self::$destinations[ $key ] = new $reg_dests[ $key ]['class']; - } else { - return null; - } - - return self::$destinations[ $key ]; - } - - /** - * Get a array of registered Destination's for Backups - * - * @return array BackWPup_Destinations - */ - public static function get_registered_destinations() { - - //only run it one time - if ( ! empty( self::$registered_destinations ) ) { - return self::$registered_destinations; - } - - //add BackWPup Destinations - // to folder - self::$registered_destinations['FOLDER'] = array( - 'class' => 'BackWPup_Destination_Folder', - 'info' => array( - 'ID' => 'FOLDER', - 'name' => __( 'Folder', 'backwpup' ), - 'description' => __( 'Backup to Folder', 'backwpup' ), - ), - 'can_sync' => false, - 'needed' => array( - 'php_version' => '', - 'functions' => array(), - 'classes' => array(), - ), - ); - // backup with mail - self::$registered_destinations['EMAIL'] = array( - 'class' => 'BackWPup_Destination_Email', - 'info' => array( - 'ID' => 'EMAIL', - 'name' => __( 'Email', 'backwpup' ), - 'description' => __( 'Backup sent via email', 'backwpup' ), - ), - 'can_sync' => false, - 'needed' => array( - 'php_version' => '', - 'functions' => array(), - 'classes' => array(), - ), - ); - // backup to ftp - self::$registered_destinations['FTP'] = array( - 'class' => 'BackWPup_Destination_Ftp', - 'info' => array( - 'ID' => 'FTP', - 'name' => __( 'FTP', 'backwpup' ), - 'description' => __( 'Backup to FTP', 'backwpup' ), - ), - 'can_sync' => false, - 'needed' => array( - 'php_version' => '', - 'functions' => array( 'ftp_nb_fput' ), - 'classes' => array(), - ), - ); - // backup to dropbox - self::$registered_destinations['DROPBOX'] = array( - 'class' => 'BackWPup_Destination_Dropbox', - 'info' => array( - 'ID' => 'DROPBOX', - 'name' => __( 'Dropbox', 'backwpup' ), - 'description' => __( 'Backup to Dropbox', 'backwpup' ), - ), - 'can_sync' => false, - 'needed' => array( - 'php_version' => '', - 'functions' => array( 'curl_exec' ), - 'classes' => array(), - ), - ); - // Backup to S3 - self::$registered_destinations['S3'] = array( - 'class' => 'BackWPup_Destination_S3', - 'info' => array( - 'ID' => 'S3', - 'name' => __( 'S3 Service', 'backwpup' ), - 'description' => __( 'Backup to an S3 Service', 'backwpup' ), - ), - 'can_sync' => false, - 'needed' => array( - 'php_version' => '', - 'functions' => array( 'curl_exec' ), - 'classes' => array( 'XMLWriter' ), - ), - ); - // backup to MS Azure - self::$registered_destinations['MSAZURE'] = array( - 'class' => 'BackWPup_Destination_MSAzure', - 'info' => array( - 'ID' => 'MSAZURE', - 'name' => __( 'MS Azure', 'backwpup' ), - 'description' => __( 'Backup to Microsoft Azure (Blob)', 'backwpup' ), - ), - 'can_sync' => false, - 'needed' => array( - 'php_version' => '5.6.0', - 'functions' => array(), - 'classes' => array(), - ), - ); - // backup to Rackspace Cloud - self::$registered_destinations['RSC'] = array( - 'class' => 'BackWPup_Destination_RSC', - 'info' => array( - 'ID' => 'RSC', - 'name' => __( 'RSC', 'backwpup' ), - 'description' => __( 'Backup to Rackspace Cloud Files', 'backwpup' ), - ), - 'can_sync' => false, - 'needed' => array( - 'php_version' => '', - 'functions' => array( 'curl_exec' ), - 'classes' => array(), - ), - ); - // backup to Sugarsync - self::$registered_destinations['SUGARSYNC'] = array( - 'class' => 'BackWPup_Destination_SugarSync', - 'info' => array( - 'ID' => 'SUGARSYNC', - 'name' => __( 'SugarSync', 'backwpup' ), - 'description' => __( 'Backup to SugarSync', 'backwpup' ), - ), - 'can_sync' => false, - 'needed' => array( - 'php_version' => '', - 'functions' => array( 'curl_exec' ), - 'classes' => array(), - ), - ); - - //Hook for adding Destinations like above - self::$registered_destinations = apply_filters( - 'backwpup_register_destination', - self::$registered_destinations - ); - - //check BackWPup Destinations - foreach ( self::$registered_destinations as $dest_key => $dest ) { - self::$registered_destinations[ $dest_key ]['error'] = ''; - // check PHP Version - if ( ! empty( $dest['needed']['php_version'] ) - && version_compare( - PHP_VERSION, - $dest['needed']['php_version'], - '<' - ) ) { - self::$registered_destinations[ $dest_key ]['error'] .= sprintf( - __( 'PHP Version %1$s is to low, you need Version %2$s or above.', - 'backwpup' ), - PHP_VERSION, - $dest['needed']['php_version'] - ) . ' '; - self::$registered_destinations[ $dest_key ]['class'] = null; - } - //check functions exists - if ( ! empty( $dest['needed']['functions'] ) ) { - foreach ( $dest['needed']['functions'] as $function_need ) { - if ( ! function_exists( $function_need ) ) { - self::$registered_destinations[ $dest_key ]['error'] .= sprintf( - __( 'Missing function "%s".', - 'backwpup' ), - $function_need - ) . ' '; - self::$registered_destinations[ $dest_key ]['class'] = null; - } - } - } - //check classes exists - if ( ! empty( $dest['needed']['classes'] ) ) { - foreach ( $dest['needed']['classes'] as $class_need ) { - if ( ! class_exists( $class_need ) ) { - self::$registered_destinations[ $dest_key ]['error'] .= sprintf( - __( 'Missing class "%s".', - 'backwpup' ), - $class_need - ) . ' '; - self::$registered_destinations[ $dest_key ]['class'] = null; - } - } - } - } - - return self::$registered_destinations; - } - - /** - * Gets a array of instances from Job types - * - * @return array BackWPup_JobTypes - */ - public static function get_job_types() { - - if ( ! empty( self::$job_types ) ) { - return self::$job_types; - } - - self::$job_types['DBDUMP'] = new BackWPup_JobType_DBDump; - self::$job_types['FILE'] = new BackWPup_JobType_File; - self::$job_types['WPEXP'] = new BackWPup_JobType_WPEXP; - self::$job_types['WPPLUGIN'] = new BackWPup_JobType_WPPlugin; - self::$job_types['DBCHECK'] = new BackWPup_JobType_DBCheck; - - self::$job_types = apply_filters( 'backwpup_job_types', self::$job_types ); - - //remove types can't load - foreach ( self::$job_types as $key => $job_type ) { - if ( empty( $job_type ) || ! is_object( $job_type ) ) { - unset( self::$job_types[ $key ] ); - } - } - - return self::$job_types; - } - - /** - * Gets a array of instances from Wizards - * - * @return array BackWPup_Pro_Wizards - */ - public static function get_wizards() { - - if ( ! empty( self::$wizards ) ) { - return self::$wizards; - } - - self::$wizards = apply_filters( 'backwpup_pro_wizards', self::$wizards ); - - //remove wizards can't load - foreach ( self::$wizards as $key => $wizard ) { - if ( empty( $wizard ) || ! is_object( $wizard ) ) { - unset( self::$wizards[ $key ] ); - } - } - - return self::$wizards; - - } - } - - require_once __DIR__ . '/inc/class-system-requirements.php'; - require_once __DIR__ . '/inc/class-system-tests.php'; - $system_requirements = new BackWPup_System_Requirements(); - $system_tests = new BackWPup_System_Tests( $system_requirements ); - - // Don't activate on anything less than PHP 5.3 or WordPress 3.9 - if ( ! $system_tests->is_php_version_compatible() || ! $system_tests->is_wp_version_compatible() ) { - require_once ABSPATH . 'wp-admin/includes/plugin.php'; - deactivate_plugins( __FILE__ ); - die( - sprintf( - esc_html__( - 'BackWPup requires PHP version %1$s with spl extension or greater and WordPress %2$s or greater.', - 'backwpup' - ), - $system_requirements->php_minimum_version(), - $system_requirements->wp_minimum_version() - ) - ); - } - - //Start Plugin - add_action( 'plugins_loaded', array( 'BackWPup', 'get_instance' ), 11 ); + /** + * Gets a array of instances from Wizards. + * + * @return array BackWPup_Pro_Wizards + */ + public static function get_wizards() + { + if (!empty(self::$wizards)) { + return self::$wizards; + } + + self::$wizards = apply_filters('backwpup_pro_wizards', self::$wizards); + + //remove wizards can't load + foreach (self::$wizards as $key => $wizard) { + if (empty($wizard) || !is_object($wizard)) { + unset(self::$wizards[$key]); + } + } + + return self::$wizards; + } + } + + require_once __DIR__ . '/inc/class-system-requirements.php'; + + require_once __DIR__ . '/inc/class-system-tests.php'; + $system_requirements = new BackWPup_System_Requirements(); + $system_tests = new BackWPup_System_Tests($system_requirements); + + // Don't activate on anything less than PHP 5.3 or WordPress 3.9 + if (!$system_tests->is_php_version_compatible() || !$system_tests->is_wp_version_compatible()) { + require_once ABSPATH . 'wp-admin/includes/plugin.php'; + deactivate_plugins(__FILE__); + + exit( + sprintf( + esc_html__( + 'BackWPup requires PHP version %1$s with spl extension or greater and WordPress %2$s or greater.', + 'backwpup' + ), + $system_requirements->php_minimum_version(), + $system_requirements->wp_minimum_version() + ) + ); + } + + //Start Plugin + add_action('plugins_loaded', [\BackWPup::class, 'get_instance'], 11); } diff --git a/changelog.txt b/changelog.txt index 8d71eb23..9da8e2f1 100755 --- a/changelog.txt +++ b/changelog.txt @@ -1,4 +1,29 @@ == Changelog == += 4.0.0 = +Release Date: November 16, 2022 + +* Added: Support for additional S3 storage classes +* Added: Support for Glacier Instant Retrieval +* Added: Created backwpup_ftp_use_passive_address filter for when FTP is behind NAT +* Added: Support for object lock in S3 by adding Content-MD5 header +* Added (pro): Include unique IV when encrypting archives +* Added (pro): Default character set fetched from alternative database credentials +* Changed: Upgraded minimum version to PHP 7.2 +* Changed (pro): Upgraded Google Drive SDK to V3 +* Fixed: Better support for PHP 8 +* Fixed: PHP warning when backing up to S3 destination +* Fixed: Unable to download from S3 when using predefined region +* Fixed: Unable to download from SugarSync +* Fixed: Backups downloaded twice from MS Azure +* Fixed: wp-config.php backed up twice when parent folder is included in backup +* Fixed: Text fields too long on SugarSync destination settings +* Fixed: Dropbox runs out of memory when more than 50 files in folder +* Fixed (pro): Authentication of OneDrive when HTTP_REFERER not set. +* Fixed (pro): Displayed creation date of OneDrive backups was incorrect +* Fixed (pro): Disabled mysqldump radio button when binary cannot be found +* Fixed (pro): HiDrive does not detect when refresh token expires +* Removed: Unnecessary AWS and Google library files to save on package size. + = Version 3.10.0 = Release Date: September 1, 2021 diff --git a/inc/Notice/DismissibleNoticeOption.php b/inc/Notice/DismissibleNoticeOption.php index dbead111..5970815b 100644 --- a/inc/Notice/DismissibleNoticeOption.php +++ b/inc/Notice/DismissibleNoticeOption.php @@ -4,35 +4,58 @@ class DismissibleNoticeOption { + /** + * @var string + */ + public const OPTION_PREFIX = 'backwpup_dinotopt_'; + /** + * @var string + */ + public const FOR_GOOD_ACTION = 'dismiss_admin_notice_for_good'; + /** + * @var string + */ + public const FOR_NOW_ACTION = 'dismiss_admin_notice_for_now'; + /** + * @var string + */ + public const FOR_USER_FOR_GOOD_ACTION = 'dismiss_admin_notice_for_good_user'; + /** + * @var string + */ + public const SKIP = 'skip_action'; - const OPTION_PREFIX = 'backwpup_dinotopt_'; - const FOR_GOOD_ACTION = 'dismiss_admin_notice_for_good'; - const FOR_NOW_ACTION = 'dismiss_admin_notice_for_now'; - const FOR_USER_FOR_GOOD_ACTION = 'dismiss_admin_notice_for_good_user'; - const SKIP = 'skip_action'; - - private static $setup = [ - 'sitewide' => [], - 'blog' => [], - ]; - - private static $all_actions = [ + /** + * @var string[] + */ + private const ALL_ACTIONS = [ self::FOR_GOOD_ACTION, self::FOR_NOW_ACTION, self::FOR_USER_FOR_GOOD_ACTION, ]; /** - * @var bool + * @phpstan-var array{ + * sitewide: array, + * blog: array, + * } */ - private $sitewide; + private static $setup = [ + 'sitewide' => [], + 'blog' => [], + ]; /** - * @param bool $sitewide - * @param string $notice_id - * @param string $capability + * @var bool */ - public static function setup_actions($sitewide, $notice_id, $capability = 'read') + private $sitewide = false; + + public function __construct(bool $sitewide = false) + { + $this->sitewide = $sitewide; + } + + public static function setup_actions(bool $sitewide, string $notice_id, string $capability = 'read'): void { if (!is_string($notice_id)) { return; @@ -48,9 +71,15 @@ public static function setup_actions($sitewide, $notice_id, $capability = 'read' if (self::$setup[$key] === []) { $option = new self($sitewide); - add_action('admin_post_' . self::FOR_GOOD_ACTION, [$option, 'dismiss']); - add_action('admin_post_' . self::FOR_NOW_ACTION, [$option, 'dismiss']); - add_action('admin_post_' . self::FOR_USER_FOR_GOOD_ACTION, [$option, 'dismiss']); + add_action('admin_post_' . self::FOR_GOOD_ACTION, function () use ($option): void { + $option->dismiss(); + }); + add_action('admin_post_' . self::FOR_NOW_ACTION, function () use ($option): void { + $option->dismiss(); + }); + add_action('admin_post_' . self::FOR_USER_FOR_GOOD_ACTION, function () use ($option): void { + $option->dismiss(); + }); } self::$setup[$key][$notice_id] = $capability; @@ -58,13 +87,8 @@ public static function setup_actions($sitewide, $notice_id, $capability = 'read' /** * Returns the URL that can be used to dismiss a given notice for good or temporarily according to given action. - * - * @param string $notice_id - * @param string $action - * - * @return string */ - public static function dismiss_action_url($notice_id, $action) + public static function dismiss_action_url(string $notice_id, string $action): string { return add_query_arg( [ @@ -77,22 +101,10 @@ public static function dismiss_action_url($notice_id, $action) ); } - /** - * @param bool $sitewide - */ - public function __construct($sitewide = false) - { - $this->sitewide = $sitewide; - } - /** * Returns true when given notice is dismissed for good or temporarily for current user. - * - * @param $notice_id - * - * @return bool */ - public function is_dismissed($notice_id) + public function is_dismissed(string $notice_id): bool { $option_name = self::OPTION_PREFIX . $notice_id; @@ -111,15 +123,15 @@ public function is_dismissed($notice_id) $transient_name = self::OPTION_PREFIX . $notice_id . get_current_user_id(); $transient = $this->sitewide ? get_site_transient($transient_name) : get_transient($transient_name); - return (bool)$transient; + return (bool) $transient; } /** * Action callback to dismiss an action for good. */ - public function dismiss() + public function dismiss(): void { - list($action, $notice_id, $is_ajax) = $this->assert_allowed(); + [$action, $notice_id, $is_ajax] = $this->assert_allowed(); $end_request = true; @@ -127,26 +139,29 @@ public function dismiss() case self::FOR_GOOD_ACTION: $this->dismiss_for_good($notice_id); break; + case self::FOR_USER_FOR_GOOD_ACTION: $this->dismiss_for_user_for_good($notice_id); break; + case self::FOR_NOW_ACTION: $this->dismiss_for_now($notice_id); break; + case self::SKIP: $end_request = false; break; } - $end_request and $this->end_request($is_ajax); + if ($end_request) { + $this->end_request($is_ajax); + } } /** * Action callback to dismiss an action for good. - * - * @param string $notice_id */ - private function dismiss_for_good($notice_id) + private function dismiss_for_good(string $notice_id): void { $option_name = self::OPTION_PREFIX . $notice_id; @@ -157,10 +172,8 @@ private function dismiss_for_good($notice_id) /** * Action callback to dismiss an action definitively for current user. - * - * @param string $notice_id */ - private function dismiss_for_user_for_good($notice_id) + private function dismiss_for_user_for_good(string $notice_id): void { update_user_option( get_current_user_id(), @@ -172,10 +185,8 @@ private function dismiss_for_user_for_good($notice_id) /** * Action callback to dismiss an action temporarily for current user. - * - * @param string $notice_id */ - private function dismiss_for_now($notice_id) + private function dismiss_for_now(string $notice_id): void { $transient_name = self::OPTION_PREFIX . $notice_id . get_current_user_id(); $expiration = 12 * HOUR_IN_SECONDS; @@ -187,10 +198,8 @@ private function dismiss_for_now($notice_id) /** * Ends a request redirecting to referer page. - * - * @param bool $no_redirect */ - private function end_request($no_redirect = false) + private function end_request(bool $no_redirect = false): void { if ($no_redirect) { exit(); @@ -202,13 +211,14 @@ private function end_request($no_redirect = false) } wp_safe_redirect($referer); + exit(); } /** - * @return array + * @phpstan-return array{string, string, bool} */ - private function assert_allowed() + private function assert_allowed(): array { if (!is_admin()) { $this->end_request(); @@ -222,8 +232,8 @@ private function assert_allowed() ]; $data = array_merge( - array_filter((array)filter_input_array(INPUT_GET, $definition)), - array_filter((array)filter_input_array(INPUT_POST, $definition)) + array_filter((array) filter_input_array(INPUT_GET, $definition)), + array_filter((array) filter_input_array(INPUT_POST, $definition)) ); $is_ajax = !empty($data['isAjax']); @@ -233,7 +243,7 @@ private function assert_allowed() if (!$action || !$notice || !is_string($notice) - || !in_array($action, self::$all_actions, true) + || !in_array($action, self::ALL_ACTIONS, true) ) { $this->end_request($is_ajax); } @@ -249,16 +259,15 @@ private function assert_allowed() if (!$capability || !current_user_can($capability)) { $this->end_request($is_ajax); } + $nonce = filter_input(INPUT_POST, $action, FILTER_SANITIZE_STRING) + ?: filter_input(INPUT_GET, $action, FILTER_SANITIZE_STRING); - $nonce = filter_input(INPUT_POST, $action, FILTER_SANITIZE_STRING); - $nonce or $nonce = filter_input(INPUT_GET, $action, FILTER_SANITIZE_STRING); - - if (!$nonce || !wp_verify_nonce($nonce, $action)) { + if (!is_string($nonce) || !wp_verify_nonce($nonce, $action)) { $this->end_request($is_ajax); } if (!$this->sitewide - && (empty($data['blog']) || get_current_blog_id() !== (int)$data['blog']) + && (empty($data['blog']) || get_current_blog_id() !== (int) $data['blog']) ) { $this->end_request($is_ajax); } diff --git a/inc/Notice/DropboxNotice.php b/inc/Notice/DropboxNotice.php index 3b2c3436..fe8526d2 100644 --- a/inc/Notice/DropboxNotice.php +++ b/inc/Notice/DropboxNotice.php @@ -4,35 +4,36 @@ use BackWPup_Option; -/** - * Class DropboxNotice - * - * @package Inpsyde\BackWPup\Notice - */ class DropboxNotice extends Notice { - const OPTION_NAME = 'backwpup_notice_dropbox_needs_reauthenticated'; - const ID = self::OPTION_NAME; + /** + * @var string + */ + public const OPTION_NAME = 'backwpup_notice_dropbox_needs_reauthenticated'; + /** + * @var string + */ + public const ID = self::OPTION_NAME; /** - * List of jobs that need to be reauthenticated + * List of jobs that need to be reauthenticated. * - * @var array + * @var array */ private $jobs = []; /** * {@inheritdoc} */ - protected function render(NoticeMessage $message) + protected function render(NoticeMessage $message): void { $this->view->warning($message, $this->getDismissActionUrl()); } -/** - * {@inheritdoc} - */ - protected function isScreenAllowed() + /** + * {@inheritdoc} + */ + protected function isScreenAllowed(): bool { return true; } @@ -40,7 +41,7 @@ protected function isScreenAllowed() /** * {@inheritdoc} */ - protected function shouldDisplay() + protected function shouldDisplay(): bool { if (!parent::shouldDisplay()) { return false; @@ -50,15 +51,18 @@ protected function shouldDisplay() foreach ($jobs as $job) { $token = BackWPup_Option::get($job, 'dropboxtoken'); - if (isset($token['access_token']) && !isset($token['refresh_token'])) { - $this->jobs[$job] = BackWPup_Option::get($job, 'name'); + if (is_array($token) && isset($token['access_token']) && !isset($token['refresh_token'])) { + $name = BackWPup_Option::get($job, 'name'); + if (is_string($name)) { + $this->jobs[$job] = $name; + } } } return !empty($this->jobs); } - protected function message() + protected function message(): NoticeMessage { $message = new NoticeMessage('dropbox'); $message->jobs = $this->jobs; diff --git a/inc/Notice/EnvironmentNotice.php b/inc/Notice/EnvironmentNotice.php index 5993d8a1..c04ba6cb 100644 --- a/inc/Notice/EnvironmentNotice.php +++ b/inc/Notice/EnvironmentNotice.php @@ -2,28 +2,24 @@ namespace Inpsyde\BackWPup\Notice; +use Inpsyde\EnvironmentChecker\Constraints\AbstractVersionConstraint; use Inpsyde\EnvironmentChecker\EnvironmentChecker; -use Inpsyde\EnvironmentChecker\Exception\ConstraintFailedException; +use Inpsyde\EnvironmentChecker\Exception\ConstraintFailedExceptionInterface; -/** - * Class EnvironmentNotice - * - * @package Inpsyde\BackWPup\Notice - */ abstract class EnvironmentNotice extends Notice { /** * {@inheritdoc} */ - protected function render(NoticeMessage $message) + protected function render(NoticeMessage $message): void { $this->view->warning($message, $this->getDismissActionUrl()); } -/** - * {@inheritdoc} - */ - protected function isScreenAllowed() + /** + * {@inheritdoc} + */ + protected function isScreenAllowed(): bool { return true; } @@ -31,7 +27,7 @@ protected function isScreenAllowed() /** * {@inheritdoc} */ - protected function shouldDisplay() + protected function shouldDisplay(): bool { if (parent::shouldDisplay()) { $checker = new EnvironmentChecker($this->getConstraints()); @@ -41,7 +37,7 @@ protected function shouldDisplay() // Passed constraints, so do not display return false; - } catch (ConstraintFailedException $e) { + } catch (ConstraintFailedExceptionInterface|\RuntimeException $e) { return true; } } @@ -50,9 +46,9 @@ protected function shouldDisplay() } /** - * Returns list of constraints to check + * Returns list of constraints to check. * - * @return array The list of constraints + * @return AbstractVersionConstraint[] The list of constraints */ - abstract protected function getConstraints(); + abstract protected function getConstraints(): array; } diff --git a/inc/Notice/Notice.php b/inc/Notice/Notice.php index a903de17..39bf6b46 100644 --- a/inc/Notice/Notice.php +++ b/inc/Notice/Notice.php @@ -2,25 +2,37 @@ namespace Inpsyde\BackWPup\Notice; -/** - * Class Notice - * - * @package Inpsyde\BackWPup\Notice - */ abstract class Notice { + /** + * @var string + */ + public const ID = 'notice'; + /** + * @var string + */ + public const CAPABILITY = 'backwpup'; - const CAPABILITY = 'backwpup'; - const MAIN_ADMIN_PAGE_ID = 'toplevel_page_backwpup'; - const NETWORK_ADMIN_PAGE_ID = 'toplevel_page_backwpup-network'; - const DEFAULT_LANGUAGE = 'en'; + /** + * @var string + */ + public const TYPE_ADMIN = 'admin'; + /** + * @var string + */ + public const TYPE_BACKWPUP = 'backwpup'; - // Notice typees - const TYPE_ADMIN = 'admin'; - const TYPE_BACKWPUP = 'backwpup'; + /** + * @var string + */ + private const MAIN_ADMIN_PAGE_ID = 'toplevel_page_backwpup'; + /** + * @var string + */ + private const NETWORK_ADMIN_PAGE_ID = 'toplevel_page_backwpup-network'; /** - * @var array + * @var string[] */ protected static $main_admin_page_ids = [ self::MAIN_ADMIN_PAGE_ID, @@ -28,146 +40,152 @@ abstract class Notice ]; /** - * @var \Inpsyde\BackWPup\Notice\NoticeView + * @var NoticeView */ protected $view; /** - * Whether this notice should be dismissible + * Whether this notice should be dismissible. * * @var bool */ - protected $dismissible; + protected $dismissible = false; - /** - * Notice constructor - * - * @param \Inpsyde\BackWPup\Notice\NoticeView $view - */ - public function __construct(NoticeView $view, $dismissible = true) + public function __construct(NoticeView $view, bool $dismissible = true) { $this->view = $view; $this->dismissible = $dismissible; } /** - * Initialize + * Initialize. * - * @param string $type The notice type, either Notice::TYPE_ADMIN or Notice::TYPE_BACKWPUP. - * Notice::TYPE_BACKWPUP makes the notice only visible on BackWPup pages. - * Notice::TYPE_ADMIN makes the notice available on all WP admin pages. + * @param self::TYPE_* $type The notice type, either Notice::TYPE_ADMIN or Notice::TYPE_BACKWPUP. + * Notice::TYPE_BACKWPUP makes the notice only visible on BackWPup pages. + * Notice::TYPE_ADMIN makes the notice available on all WP admin pages. */ - public function init($type = self::TYPE_BACKWPUP) + public function init(string $type = self::TYPE_BACKWPUP): void { - if (!is_admin() || !current_user_can(static::CAPABILITY)) { + if (!is_admin()) { return; } - - if ($type === static::TYPE_BACKWPUP) { - add_action('backwpup_admin_messages', [ $this, 'notice' ], 20); + if (!current_user_can(static::CAPABILITY)) { + return; + } + if ($type === self::TYPE_BACKWPUP) { + add_action('backwpup_admin_messages', function (): void { + $this->notice(); + }, 20); } elseif ($type === static::TYPE_ADMIN) { - add_action('admin_notices', [ $this, 'notice' ], 20); + add_action('admin_notices', function (): void { + $this->notice(); + }, 20); } else { throw new \InvalidArgumentException( __('Invalid notice type specified', 'backwpup') ); } - if ($this->dismissible === true) { - add_action('admin_enqueue_scripts', [ $this, 'enqueueScripts' ]); + if ($this->dismissible) { + add_action('admin_enqueue_scripts', function (): void { + $this->enqueueScripts(); + }); DismissibleNoticeOption::setup_actions(true, static::ID, static::CAPABILITY); } } /** - * Enqueue Scripts + * Enqueue Scripts. */ - public function enqueueScripts() + public function enqueueScripts(): void { $suffix = (defined('SCRIPT_DEBUG') && SCRIPT_DEBUG) ? '' : '.min'; wp_enqueue_script( 'backwpup-notice', - untrailingslashit(\BackWPup::get_plugin_data('URL')) . "/assets/js/notice{$suffix}.js", + untrailingslashit(\BackWPup::get_plugin_data('URL')) . sprintf('/assets/js/notice%s.js', $suffix), ['underscore', 'jquery'], - filemtime(untrailingslashit(\BackWPup::get_plugin_data('plugindir') . "/assets/js/notice{$suffix}.js")), + (string) filemtime(untrailingslashit(\BackWPup::get_plugin_data('plugindir') . sprintf('/assets/js/notice%s.js', $suffix))), true ); } /** - * Print Notice + * Print Notice. */ - public function notice() + public function notice(): void { - if (!$this->isScreenAllowed() || !$this->shouldDisplay()) { + if (!$this->isScreenAllowed()) { return; } - - $message = $this->message(); - if ($message === null) { + if (!$this->shouldDisplay()) { return; } - $this->render($message); + $this->render($this->message()); } /** - * Render the notice with the appropriate view type + * Render the notice with the appropriate view type. * * This method can specify whether the notice should be a success, error, * warning, info, or generic notice. * * @param NoticeMessage $message The message to render */ - protected function render(NoticeMessage $message) + protected function render(NoticeMessage $message): void { $this->view->notice($message, $this->getDismissActionUrl()); } /** - * Gets the dismissible action URL from DismissibleNoticeOption + * Gets the dismissible action URL from DismissibleNoticeOption. * - * @return string The URL to dismiss the notice + * @return string|null The URL to dismiss the notice */ - protected function getDismissActionUrl() + protected function getDismissActionUrl(): ?string { - if ($this->dismissible === true) { + if ($this->dismissible) { return DismissibleNoticeOption::dismiss_action_url( static::ID, DismissibleNoticeOption::FOR_USER_FOR_GOOD_ACTION ); } + + return null; } /** - * Return the message to display in the notice + * Return the message to display in the notice. * - * @return \Inpsyde\BackWPup\Notice\NoticeMessage The message to display + * @return NoticeMessage The message to display */ - abstract protected function message(); + abstract protected function message(): NoticeMessage; /** - * Returns whether the current screen should show the notice - * - * @return bool True if the notice should be displayed + * Returns whether the current screen should show the notice. */ - protected function isScreenAllowed() + protected function isScreenAllowed(): bool { - $screen_id = get_current_screen()->id; + $screen = get_current_screen(); + if (!$screen instanceof \WP_Screen) { + return false; + } + + $screen_id = $screen->id; + return in_array($screen_id, static::$main_admin_page_ids, true); } /** - * Whether to display the notice - * - * @return bool True if the notice should be displayed, false otherwise + * Whether to display the notice. */ - protected function shouldDisplay() + protected function shouldDisplay(): bool { - if ($this->dismissible === true) { + if ($this->dismissible) { $option = new DismissibleNoticeOption(true); - return (bool)$option->is_dismissed(static::ID) === false; + + return !$option->is_dismissed(static::ID); } return true; diff --git a/inc/Notice/NoticeMessage.php b/inc/Notice/NoticeMessage.php index 29005d4d..d3446079 100644 --- a/inc/Notice/NoticeMessage.php +++ b/inc/Notice/NoticeMessage.php @@ -1,42 +1,38 @@ -|null $jobs */ -class NoticeMessage +final class NoticeMessage { - /** - * Array of message data + * Array of message data. * - * @var array + * @var array|null> */ - private $data; + private $data = []; - /** - * NoticeMessage constructor - * - * @param string $template The notice template - */ - public function __construct($template, $buttonLabel = null, $buttonUrl = null) + public function __construct(string $template, ?string $buttonLabel = null, ?string $buttonUrl = null) { - $this->data['template'] = "/notice/$template.php"; + $this->data['template'] = sprintf('/notice/%s.php', $template); $this->data['buttonLabel'] = $buttonLabel; $this->data['buttonUrl'] = $buttonUrl; } /** - * Get a message variable + * Get a message variable. * * @param string $name * - * @return mixed + * @return string|array|null */ public function __get($name) { @@ -48,18 +44,18 @@ public function __get($name) } /** - * Sets a variable for the message + * Sets a variable for the message. * - * @param string $name The variable to set - * @param mixed $value The value to set + * @param string $name The variable to set + * @param string|array $value The value to set */ - public function __set($name, $value) + public function __set($name, $value): void { $this->data[$name] = $value; } /** - * Check if variable is set + * Check if variable is set. * * @param string $name The variable to check */ diff --git a/inc/Notice/NoticeView.php b/inc/Notice/NoticeView.php index d2c1c65d..8857270c 100644 --- a/inc/Notice/NoticeView.php +++ b/inc/Notice/NoticeView.php @@ -1,19 +1,33 @@ -id = $id; } /** - * @param \Inpsyde\BackWPup\Notice\NoticeMessage $message The contents of the notice - * @param string $dismiss_action_url The URL for dismissing the notice - * @param string $type The type of notice: one of NoticeView::SUCCESS, - * NoticeView::ERROR, NoticeView::WARNING, or NoticeView::INFO - * - * @return false|string + * @param NoticeMessage $message The contents of the notice + * @param string|null $dismissActionUrl The URL for dismissing the notice + * @param self::*|null $type The type of notice: one of NoticeView::SUCCESS, + * NoticeView::ERROR, NoticeView::WARNING, or NoticeView::INFO */ - public function notice(NoticeMessage $message, $dismissActionUrl = null, $type = null) + public function notice(NoticeMessage $message, ?string $dismissActionUrl = null, ?string $type = null): void { $message->id = $this->id; $message->dismissActionUrl = $dismissActionUrl; @@ -50,30 +60,11 @@ public function notice(NoticeMessage $message, $dismissActionUrl = null, $type = /** * Call notice() with the appropriate notice type. * - * @throws BadMethodCallException If 2 arguments not given, or called with invalid notice type + * @param 'success'|'error'|'warning'|'info' $name + * @param array{0: NoticeMessage, 1?: string|null} $args */ - public function __call($name, $args) + public function __call(string $name, array $args): void { - if (count($args) === 0) { - throw new \BadMethodCallException( - sprintf( - __('Method %1$s::%2$s() requires at least 1 argument; %3$d given', 'backwpup'), - __CLASS__, - $name, - count($args) - ) - ); - } elseif (count($args) > 2) { - throw new \BadMethodCallException( - sprintf( - __('Method %1$s::%2$s() takes at most 2 arguments; %3$d given', 'backwpup'), - __CLASS__, - $name, - count($args) - ) - ); - } - switch ($name) { case 'success': $args[] = self::SUCCESS; @@ -95,7 +86,7 @@ public function __call($name, $args) throw new \BadMethodCallException( sprintf( __('Call to undefined method %1$s::%2$s()', 'backwpup'), - __CLASS__, + self::class, $name ) ); diff --git a/inc/Notice/PhpNotice.php b/inc/Notice/PhpNotice.php index ff5a2d04..77b48f40 100644 --- a/inc/Notice/PhpNotice.php +++ b/inc/Notice/PhpNotice.php @@ -1,34 +1,34 @@ -settings = $settings; - BackWPup::load_text_domain(); + BackWPup::load_text_domain(); } /** - * Enqueues main css file + * Enqueues main css file. */ public function admin_css() { @@ -34,662 +38,710 @@ public function admin_css() wp_enqueue_style( 'backwpup', BackWPup::get_plugin_data('URL') . '/assets/css/main.min.css', - array(), + [], filemtime($filePath), 'screen' ); } - /** - * Load for all BackWPup pages - */ - public static function init_general() { - - add_thickbox(); - - $suffix = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min'; - - wp_register_script( - 'backwpupgeneral', - BackWPup::get_plugin_data( 'URL' ) . "/assets/js/general{$suffix}.js", - array( 'jquery' ), - ( $suffix ? BackWPup::get_plugin_data( 'Version' ) : time() ), - false - ); - - // Register clipboard.js script - wp_register_script( - 'backwpup_clipboard', - BackWPup::get_plugin_data( 'URL' ) . '/assets/js/vendor/clipboard.min.js', - array( 'jquery' ), - '1.7.1', - true - ); - - // Add Help. - BackWPup_Help::help(); - } - - /** - * Add Message (across site loadings) - * - * @param string $message string Message test. - * @param bool $error bool ist it a error message. - */ - public static function message( $message, $error = false ) { - - if ( empty( $message ) ) { - return; - } - - $saved_message = self::get_messages(); - - if ( $error ) { - $saved_message['error'][] = $message; - } else { - $saved_message['updated'][] = $message; - } - - update_site_option( 'backwpup_messages', $saved_message ); - } - - /** - * Get all Message that not displayed - * - * @return array - */ - public static function get_messages() { - - return get_site_option( 'backwpup_messages', array() ); - } - - /** - * Display Messages - * - * @param bool $echo - * - * @return string - */ - public static function display_messages( $echo = true ) { - - /** - * This hook can be used to display more messages in all BackWPup pages - */ - do_action( 'backwpup_admin_messages' ); - - $message_updated = ''; - $message_error = ''; - $saved_message = self::get_messages(); - $message_id = ' id="message"'; - - if ( empty( $saved_message ) ) { - return ''; - } - - if ( ! empty( $saved_message['updated'] ) ) { - foreach ( $saved_message['updated'] as $msg ) { - $message_updated .= '

' . $msg . '

'; - } - } - if ( ! empty( $saved_message['error'] ) ) { - foreach ( $saved_message['error'] as $msg ) { - $message_error .= '

' . $msg . '

'; - } - } - - update_site_option( 'backwpup_messages', array() ); - - if ( ! empty( $message_updated ) ) { - $message_updated = '' . $message_updated . ''; - $message_id = ''; - } - if ( ! empty( $message_error ) ) { - $message_error = '' . $message_error . ''; - } - - if ( $echo ) { - echo $message_updated . $message_error; - } - - return $message_updated . $message_error; - } - - /** - * Admin init function - */ - public function admin_init() { - - if ( ! is_admin() ) { - return; - } - if ( ! defined( 'DOING_AJAX' ) || ( defined( 'DOING_AJAX' ) && ! DOING_AJAX ) ) { - // Only init notices if this is not an AJAX request - $this->init_notices(); - - // Everything after this point applies to AJAX - return; - } - - $jobtypes = BackWPup::get_job_types(); - $destinations = BackWPup::get_registered_destinations(); - - add_action( 'wp_ajax_backwpup_working', array( 'BackWPup_Page_Jobs', 'ajax_working' ) ); - add_action( 'wp_ajax_backwpup_cron_text', array( 'BackWPup_Page_Editjob', 'ajax_cron_text' ) ); - add_action( 'wp_ajax_backwpup_view_log', array( 'BackWPup_Page_Logs', 'ajax_view_log' ) ); - add_action( 'wp_ajax_download_backup_file', array( 'BackWPup_Destination_Downloader', 'download_by_ajax' ) ); - - foreach ( $jobtypes as $id => $jobtypeclass ) { - add_action( 'wp_ajax_backwpup_jobtype_' . strtolower( $id ), array( $jobtypeclass, 'edit_ajax' ) ); - } - - foreach ( $destinations as $id => $dest ) { - if ( ! empty( $dest['class'] ) ) { - add_action( - 'wp_ajax_backwpup_dest_' . strtolower( $id ), - array( - BackWPup::get_destination( $id ), - 'edit_ajax', - ), 10, 0 - ); - } - } - - if ( \BackWPup::is_pro() ) { - $this->admin_init_pro(); - } - } - - private function admin_init_pro() { - - $ajax_encryption_key_handler = new Settings\AjaxEncryptionKeyHandler( - new \phpseclib\Crypt\RSA() - ); - - add_action( 'wp_ajax_encrypt_key_handler', array( $ajax_encryption_key_handler, 'handle' ) ); - } - - private function init_notices() { - // Show notice if PHP < 7.2 - $phpNotice = new Notice\PhpNotice( - new Notice\NoticeView( Notice\PhpNotice::ID ) - ); - $phpNotice->init( Notice\PhpNotice::TYPE_ADMIN ); - - // Show notice if WordPress < 5.0 - $wpNotice = new Notice\WordPressNotice( - new Notice\NoticeView( Notice\WordPressNotice::ID ) - ); - $wpNotice->init( Notice\WordPressNotice::TYPE_ADMIN ); - - // Show notice if Dropbox needs to be reauthenticated - $dropboxNotice = new Notice\DropboxNotice( - new Notice\NoticeView( Notice\DropboxNotice::ID ), - false // Not dismissible - ); - $dropboxNotice->init( Notice\DropboxNotice::TYPE_ADMIN ); - } - - /** - * - * Add Links in Plugins Menu to BackWPup - * - * @param $links - * @param $file - * - * @return array - */ - public function plugin_links( $links, $file ) { - - if ( $file == plugin_basename( BackWPup::get_plugin_data( 'MainFile' ) ) ) { - $links[] = '' . __( 'Documentation', - 'backwpup' ) . ''; - } - - return $links; - } - - /** - * Add menu entries - */ - public function admin_menu() { - - add_menu_page( BackWPup::get_plugin_data( 'name' ), - BackWPup::get_plugin_data( 'name' ), - 'backwpup', - 'backwpup', - array( - 'BackWPup_Page_BackWPup', - 'page', - ), - 'div' ); - $this->page_hooks['backwpup'] = add_submenu_page( 'backwpup', - __( 'BackWPup Dashboard', 'backwpup' ), - __( 'Dashboard', 'backwpup' ), - 'backwpup', - 'backwpup', - array( - 'BackWPup_Page_BackWPup', - 'page', - ) ); - add_action( 'load-' . $this->page_hooks['backwpup'], array( 'BackWPup_Admin', 'init_general' ) ); - add_action( 'load-' . $this->page_hooks['backwpup'], array( 'BackWPup_Page_BackWPup', 'load' ) ); - add_action( 'admin_print_scripts-' . $this->page_hooks['backwpup'], - array( - 'BackWPup_Page_BackWPup', - 'admin_print_scripts', - ) ); - - //Add pages form plugins - $this->page_hooks = apply_filters( 'backwpup_admin_pages', $this->page_hooks ); - - } - - /** - * @param $page_hooks - * - * @return mixed - */ - public function admin_page_jobs( $page_hooks ) { - - $this->page_hooks['backwpupjobs'] = add_submenu_page( 'backwpup', - __( 'Jobs', 'backwpup' ), - __( 'Jobs', 'backwpup' ), - 'backwpup_jobs', - 'backwpupjobs', - array( - 'BackWPup_Page_Jobs', - 'page', - ) ); - add_action( 'load-' . $this->page_hooks['backwpupjobs'], array( 'BackWPup_Admin', 'init_general' ) ); - add_action( 'load-' . $this->page_hooks['backwpupjobs'], array( 'BackWPup_Page_Jobs', 'load' ) ); - add_action( 'admin_print_styles-' . $this->page_hooks['backwpupjobs'], - array( - 'BackWPup_Page_Jobs', - 'admin_print_styles', - ) ); - add_action( 'admin_print_scripts-' . $this->page_hooks['backwpupjobs'], - array( - 'BackWPup_Page_Jobs', - 'admin_print_scripts', - ) ); - - return $page_hooks; - } - - /** - * @param $page_hooks - * - * @return mixed - */ - public function admin_page_editjob( $page_hooks ) { - - $this->page_hooks['backwpupeditjob'] = add_submenu_page( 'backwpup', - __( 'Add new job', 'backwpup' ), - __( 'Add new job', 'backwpup' ), - 'backwpup_jobs_edit', - 'backwpupeditjob', - array( - 'BackWPup_Page_Editjob', - 'page', - ) ); - add_action( 'load-' . $this->page_hooks['backwpupeditjob'], array( 'BackWPup_Admin', 'init_general' ) ); - add_action( 'load-' . $this->page_hooks['backwpupeditjob'], array( 'BackWPup_Page_Editjob', 'auth' ) ); - add_action( 'admin_print_styles-' . $this->page_hooks['backwpupeditjob'], - array( - 'BackWPup_Page_Editjob', - 'admin_print_styles', - ) ); - add_action( 'admin_print_scripts-' . $this->page_hooks['backwpupeditjob'], - array( - 'BackWPup_Page_Editjob', - 'admin_print_scripts', - ) ); - - return $page_hooks; - } - - /** - * @param $page_hooks - * - * @return mixed - */ - public function admin_page_logs( $page_hooks ) { - - $this->page_hooks['backwpuplogs'] = add_submenu_page( 'backwpup', - __( 'Logs', 'backwpup' ), - __( 'Logs', 'backwpup' ), - 'backwpup_logs', - 'backwpuplogs', - array( - 'BackWPup_Page_Logs', - 'page', - ) ); - add_action( 'load-' . $this->page_hooks['backwpuplogs'], array( 'BackWPup_Admin', 'init_general' ) ); - add_action( 'load-' . $this->page_hooks['backwpuplogs'], array( 'BackWPup_Page_Logs', 'load' ) ); - add_action( 'admin_print_styles-' . $this->page_hooks['backwpuplogs'], - array( - 'BackWPup_Page_Logs', - 'admin_print_styles', - ) ); - add_action( 'admin_print_scripts-' . $this->page_hooks['backwpuplogs'], - array( - 'BackWPup_Page_Logs', - 'admin_print_scripts', - ) ); - - return $page_hooks; - } - - /** - * @param $page_hooks - * - * @return mixed - */ - public function admin_page_backups( $page_hooks ) { - - $this->page_hooks['backwpupbackups'] = add_submenu_page( 'backwpup', - __( 'Backups', 'backwpup' ), - __( 'Backups', 'backwpup' ), - 'backwpup_backups', - 'backwpupbackups', - array( - 'BackWPup_Page_Backups', - 'page', - ) ); - add_action( 'load-' . $this->page_hooks['backwpupbackups'], array( 'BackWPup_Admin', 'init_general' ) ); - add_action( 'load-' . $this->page_hooks['backwpupbackups'], array( 'BackWPup_Page_Backups', 'load' ) ); - add_action( 'admin_print_styles-' . $this->page_hooks['backwpupbackups'], - array( - 'BackWPup_Page_Backups', - 'admin_print_styles', - ) ); - add_action( 'admin_print_scripts-' . $this->page_hooks['backwpupbackups'], - array( - 'BackWPup_Page_Backups', - 'admin_print_scripts', - ) ); - - return $page_hooks; - } - - /** - * @param $page_hooks - * - * @return mixed - */ - public function admin_page_settings( $page_hooks ) { - - $this->page_hooks['backwpupsettings'] = add_submenu_page( - 'backwpup', - esc_html__( 'Settings', 'backwpup' ), - esc_html__( 'Settings', 'backwpup' ), - 'backwpup_settings', - 'backwpupsettings', - array( $this->settings, 'page' ) - ); - add_action( 'load-' . $this->page_hooks['backwpupsettings'], array( 'BackWPup_Admin', 'init_general' ) ); - add_action( - 'admin_print_scripts-' . $this->page_hooks['backwpupsettings'], - array( $this->settings, 'admin_print_scripts' ) - ); - - return $page_hooks; - } - - /** - * @param $page_hooks - * - * @return mixed - */ - public function admin_page_about( $page_hooks ) { - - $this->page_hooks['backwpupabout'] = add_submenu_page( 'backwpup', - __( 'About', 'backwpup' ), - __( 'About', 'backwpup' ), - 'backwpup', - 'backwpupabout', - array( - 'BackWPup_Page_About', - 'page', - ) ); - add_action( 'load-' . $this->page_hooks['backwpupabout'], array( 'BackWPup_Admin', 'init_general' ) ); - add_action( 'admin_print_styles-' . $this->page_hooks['backwpupabout'], - array( - 'BackWPup_Page_About', - 'admin_print_styles', - ) ); - add_action( 'admin_print_scripts-' . $this->page_hooks['backwpupabout'], - array( - 'BackWPup_Page_About', - 'admin_print_scripts', - ) ); - - return $page_hooks; - } - - /** - * Called on save form. Only POST allowed. - */ - public function save_post_form() { - - $allowed_pages = array( - 'backwpupeditjob', - 'backwpupinformation', - 'backwpupsettings', - ); - - if ( ! in_array( $_POST['page'], $allowed_pages, true ) ) { - wp_die( esc_html__( 'Cheating, huh?', 'backwpup' ) ); - } - - //nonce check - check_admin_referer( $_POST['page'] . '_page' ); - - if ( ! current_user_can( 'backwpup' ) ) { - wp_die( esc_html__( 'Cheating, huh?', 'backwpup' ) ); - } - - //build query for redirect - if ( ! isset( $_POST['anchor'] ) ) { - $_POST['anchor'] = null; - } - $query_args = array(); - if ( isset( $_POST['page'] ) ) { - $query_args['page'] = $_POST['page']; - } - if ( isset( $_POST['tab'] ) ) { - $query_args['tab'] = $_POST['tab']; - } - if ( isset( $_POST['tab'] ) && isset( $_POST['nexttab'] ) && $_POST['tab'] !== $_POST['nexttab'] ) { - $query_args['tab'] = $_POST['nexttab']; - } - - $jobid = null; - if ( isset( $_POST['jobid'] ) ) { - $jobid = (int) $_POST['jobid']; - $query_args['jobid'] = $jobid; - } - - // Call method to save data - if ( $_POST['page'] === 'backwpupeditjob' ) { - BackWPup_Page_Editjob::save_post_form( $_POST['tab'], $jobid ); - } elseif ( $_POST['page'] === 'backwpupsettings' ) { - $this->settings->save_post_form(); - } - - //Back to topic - wp_safe_redirect( add_query_arg( $query_args, network_admin_url( 'admin.php' ) ) . $_POST['anchor'] ); - exit; - } - - /** - * Overrides WordPress text in Footer - * - * @param $admin_footer_text string - * - * @return string - */ - public function admin_footer_text( $admin_footer_text ) { - - $default_text = $admin_footer_text; - if ( isset( $_REQUEST['page'] ) && strstr( $_REQUEST['page'], 'backwpup' ) ) { - - $admin_footer_text = sprintf( - '', - '' - ); - - if ( ! class_exists( 'BackWPup_Pro', false ) ) { - $admin_footer_text .= sprintf( __( 'Get BackWPup Pro now.', - 'backwpup' ), - __( 'http://backwpup.com', 'backwpup' ) ); - } - - return $admin_footer_text . $default_text; - } - - return $admin_footer_text; - } - - /** - * Overrides WordPress Version in Footer - * - * @param $update_footer_text string - * - * @return string - */ - public function update_footer( $update_footer_text ) { - - $default_text = $update_footer_text; - - if ( isset( $_REQUEST['page'] ) && strstr( $_REQUEST['page'], 'backwpup' ) ) { - $update_footer_text = '' . BackWPup::get_plugin_data( 'Name' ) . ' ' . sprintf( __( 'version %s', - 'backwpup' ), - BackWPup::get_plugin_data( 'Version' ) ) . ''; - - return $update_footer_text . $default_text; - } - - return $update_footer_text; - } - - /** - * Add filed for selecting user role in user section - * - * @param $user WP_User - */ - public function user_profile_fields( $user ) { - - global $wp_roles; - - if ( ! is_super_admin() && ! current_user_can( 'backwpup_admin' ) ) { - return; - } - - //user is admin and has BackWPup rights - if ( $user->has_cap( 'administrator' ) && $user->has_cap( 'backwpup_settings' ) ) { - return; - } - - //get backwpup roles - $backwpup_roles = array(); - foreach ( $wp_roles->roles as $role => $role_value ) { - if ( substr( $role, 0, 8 ) != 'backwpup' ) { - continue; - } - $backwpup_roles[ $role ] = $role_value; - } - - //only if user has other than backwpup role - if ( ! empty( $user->roles[0] ) && in_array( $user->roles[0], array_keys( $backwpup_roles ), true ) ) { - return; - } - - ?> -

+ /** + * Load for all BackWPup pages. + */ + public static function init_general() + { + add_thickbox(); + + $suffix = (defined('SCRIPT_DEBUG') && SCRIPT_DEBUG) ? '' : '.min'; + + wp_register_script( + 'backwpupgeneral', + BackWPup::get_plugin_data('URL') . "/assets/js/general{$suffix}.js", + ['jquery'], + ($suffix ? BackWPup::get_plugin_data('Version') : time()), + false + ); + + // Register clipboard.js script + wp_register_script( + 'backwpup_clipboard', + BackWPup::get_plugin_data('URL') . '/assets/js/vendor/clipboard.min.js', + ['jquery'], + '1.7.1', + true + ); + + // Add Help. + BackWPup_Help::help(); + } + + /** + * Add Message (across site loadings). + * + * @param string $message string Message test + * @param bool $error bool ist it a error message + */ + public static function message($message, $error = false) + { + if (empty($message)) { + return; + } + + $saved_message = self::get_messages(); + + if ($error) { + $saved_message['error'][] = $message; + } else { + $saved_message['updated'][] = $message; + } + + update_site_option('backwpup_messages', $saved_message); + } + + /** + * Get all Message that not displayed. + * + * @return array + */ + public static function get_messages() + { + return get_site_option('backwpup_messages', []); + } + + /** + * Display Messages. + * + * @param bool $echo + * + * @return string + */ + public static function display_messages($echo = true) + { + /** + * This hook can be used to display more messages in all BackWPup pages. + */ + do_action('backwpup_admin_messages'); + + $message_updated = ''; + $message_error = ''; + $saved_message = self::get_messages(); + $message_id = ' id="message"'; + + if (empty($saved_message)) { + return ''; + } + + if (!empty($saved_message['updated'])) { + foreach ($saved_message['updated'] as $msg) { + $message_updated .= '

' . $msg . '

'; + } + } + if (!empty($saved_message['error'])) { + foreach ($saved_message['error'] as $msg) { + $message_error .= '

' . $msg . '

'; + } + } + + update_site_option('backwpup_messages', []); + + if (!empty($message_updated)) { + $message_updated = '' . $message_updated . ''; + $message_id = ''; + } + if (!empty($message_error)) { + $message_error = '' . $message_error . ''; + } + + if ($echo) { + echo $message_updated . $message_error; + } + + return $message_updated . $message_error; + } + + /** + * Admin init function. + */ + public function admin_init() + { + if (!is_admin()) { + return; + } + if (!defined('DOING_AJAX') || (defined('DOING_AJAX') && !DOING_AJAX)) { + // Only init notices if this is not an AJAX request + $this->init_notices(); + + // Everything after this point applies to AJAX + return; + } + + $jobtypes = BackWPup::get_job_types(); + $destinations = BackWPup::get_registered_destinations(); + + add_action('wp_ajax_backwpup_working', [\BackWPup_Page_Jobs::class, 'ajax_working']); + add_action('wp_ajax_backwpup_cron_text', [\BackWPup_Page_Editjob::class, 'ajax_cron_text']); + add_action('wp_ajax_backwpup_view_log', [\BackWPup_Page_Logs::class, 'ajax_view_log']); + add_action('wp_ajax_download_backup_file', [\BackWPup_Destination_Downloader::class, 'download_by_ajax']); + + foreach ($jobtypes as $id => $jobtypeclass) { + add_action('wp_ajax_backwpup_jobtype_' . strtolower($id), [$jobtypeclass, 'edit_ajax']); + } + + foreach ($destinations as $id => $dest) { + if (!empty($dest['class'])) { + add_action( + 'wp_ajax_backwpup_dest_' . strtolower($id), + [ + BackWPup::get_destination($id), + 'edit_ajax', + ], + 10, + 0 + ); + } + } + + if (\BackWPup::is_pro()) { + $this->admin_init_pro(); + } + } + + private function admin_init_pro() + { + $ajax_encryption_key_handler = new AjaxEncryptionKeyHandler(); + + add_action('wp_ajax_encrypt_key_handler', [$ajax_encryption_key_handler, 'handle']); + } + + private function init_notices() + { + // Show notice if PHP < 7.2 + $phpNotice = new PhpNotice( + new NoticeView(PhpNotice::ID) + ); + $phpNotice->init(PhpNotice::TYPE_ADMIN); + + // Show notice if WordPress < 5.0 + $wpNotice = new WordPressNotice( + new NoticeView(WordPressNotice::ID) + ); + $wpNotice->init(WordPressNotice::TYPE_ADMIN); + + // Show notice if Dropbox needs to be reauthenticated + $dropboxNotice = new DropboxNotice( + new NoticeView(DropboxNotice::ID), + false // Not dismissible + ); + $dropboxNotice->init(DropboxNotice::TYPE_ADMIN); + } + + /** + * Add Links in Plugins Menu to BackWPup. + * + * @param $links + * @param $file + * + * @return array + */ + public function plugin_links($links, $file) + { + if ($file == plugin_basename(BackWPup::get_plugin_data('MainFile'))) { + $links[] = '' . __( + 'Documentation', + 'backwpup' + ) . ''; + } + + return $links; + } + + /** + * Add menu entries. + */ + public function admin_menu() + { + add_menu_page( + BackWPup::get_plugin_data('name'), + BackWPup::get_plugin_data('name'), + 'backwpup', + 'backwpup', + [ + \BackWPup_Page_BackWPup::class, + 'page', + ], + 'div' + ); + $this->page_hooks['backwpup'] = add_submenu_page( + 'backwpup', + __('BackWPup Dashboard', 'backwpup'), + __('Dashboard', 'backwpup'), + 'backwpup', + 'backwpup', + [ + \BackWPup_Page_BackWPup::class, + 'page', + ] + ); + add_action('load-' . $this->page_hooks['backwpup'], [\BackWPup_Admin::class, 'init_general']); + add_action('load-' . $this->page_hooks['backwpup'], [\BackWPup_Page_BackWPup::class, 'load']); + add_action( + 'admin_print_scripts-' . $this->page_hooks['backwpup'], + [ + \BackWPup_Page_BackWPup::class, + 'admin_print_scripts', + ] + ); + + //Add pages form plugins + $this->page_hooks = apply_filters('backwpup_admin_pages', $this->page_hooks); + } + + /** + * @param $page_hooks + * + * @return mixed + */ + public function admin_page_jobs($page_hooks) + { + $this->page_hooks['backwpupjobs'] = add_submenu_page( + 'backwpup', + __('Jobs', 'backwpup'), + __('Jobs', 'backwpup'), + 'backwpup_jobs', + 'backwpupjobs', + [ + \BackWPup_Page_Jobs::class, + 'page', + ] + ); + add_action('load-' . $this->page_hooks['backwpupjobs'], [\BackWPup_Admin::class, 'init_general']); + add_action('load-' . $this->page_hooks['backwpupjobs'], [\BackWPup_Page_Jobs::class, 'load']); + add_action( + 'admin_print_styles-' . $this->page_hooks['backwpupjobs'], + [ + \BackWPup_Page_Jobs::class, + 'admin_print_styles', + ] + ); + add_action( + 'admin_print_scripts-' . $this->page_hooks['backwpupjobs'], + [ + \BackWPup_Page_Jobs::class, + 'admin_print_scripts', + ] + ); + + return $page_hooks; + } + + /** + * @param $page_hooks + * + * @return mixed + */ + public function admin_page_editjob($page_hooks) + { + $this->page_hooks['backwpupeditjob'] = add_submenu_page( + 'backwpup', + __('Add new job', 'backwpup'), + __('Add new job', 'backwpup'), + 'backwpup_jobs_edit', + 'backwpupeditjob', + [ + \BackWPup_Page_Editjob::class, + 'page', + ] + ); + add_action('load-' . $this->page_hooks['backwpupeditjob'], [\BackWPup_Admin::class, 'init_general']); + add_action('load-' . $this->page_hooks['backwpupeditjob'], [\BackWPup_Page_Editjob::class, 'auth']); + add_action( + 'admin_print_styles-' . $this->page_hooks['backwpupeditjob'], + [ + \BackWPup_Page_Editjob::class, + 'admin_print_styles', + ] + ); + add_action( + 'admin_print_scripts-' . $this->page_hooks['backwpupeditjob'], + [ + \BackWPup_Page_Editjob::class, + 'admin_print_scripts', + ] + ); + + return $page_hooks; + } + + /** + * @param $page_hooks + * + * @return mixed + */ + public function admin_page_logs($page_hooks) + { + $this->page_hooks['backwpuplogs'] = add_submenu_page( + 'backwpup', + __('Logs', 'backwpup'), + __('Logs', 'backwpup'), + 'backwpup_logs', + 'backwpuplogs', + [ + \BackWPup_Page_Logs::class, + 'page', + ] + ); + add_action('load-' . $this->page_hooks['backwpuplogs'], [\BackWPup_Admin::class, 'init_general']); + add_action('load-' . $this->page_hooks['backwpuplogs'], [\BackWPup_Page_Logs::class, 'load']); + add_action( + 'admin_print_styles-' . $this->page_hooks['backwpuplogs'], + [ + \BackWPup_Page_Logs::class, + 'admin_print_styles', + ] + ); + add_action( + 'admin_print_scripts-' . $this->page_hooks['backwpuplogs'], + [ + \BackWPup_Page_Logs::class, + 'admin_print_scripts', + ] + ); + + return $page_hooks; + } + + /** + * @param $page_hooks + * + * @return mixed + */ + public function admin_page_backups($page_hooks) + { + $this->page_hooks['backwpupbackups'] = add_submenu_page( + 'backwpup', + __('Backups', 'backwpup'), + __('Backups', 'backwpup'), + 'backwpup_backups', + 'backwpupbackups', + [ + \BackWPup_Page_Backups::class, + 'page', + ] + ); + add_action('load-' . $this->page_hooks['backwpupbackups'], [\BackWPup_Admin::class, 'init_general']); + add_action('load-' . $this->page_hooks['backwpupbackups'], [\BackWPup_Page_Backups::class, 'load']); + add_action( + 'admin_print_styles-' . $this->page_hooks['backwpupbackups'], + [ + \BackWPup_Page_Backups::class, + 'admin_print_styles', + ] + ); + add_action( + 'admin_print_scripts-' . $this->page_hooks['backwpupbackups'], + [ + \BackWPup_Page_Backups::class, + 'admin_print_scripts', + ] + ); + + return $page_hooks; + } + + /** + * @param $page_hooks + * + * @return mixed + */ + public function admin_page_settings($page_hooks) + { + $this->page_hooks['backwpupsettings'] = add_submenu_page( + 'backwpup', + esc_html__('Settings', 'backwpup'), + esc_html__('Settings', 'backwpup'), + 'backwpup_settings', + 'backwpupsettings', + [$this->settings, 'page'] + ); + add_action('load-' . $this->page_hooks['backwpupsettings'], [\BackWPup_Admin::class, 'init_general']); + add_action( + 'admin_print_scripts-' . $this->page_hooks['backwpupsettings'], + [$this->settings, 'admin_print_scripts'] + ); + + return $page_hooks; + } + + /** + * @param $page_hooks + * + * @return mixed + */ + public function admin_page_about($page_hooks) + { + $this->page_hooks['backwpupabout'] = add_submenu_page( + 'backwpup', + __('About', 'backwpup'), + __('About', 'backwpup'), + 'backwpup', + 'backwpupabout', + [ + \BackWPup_Page_About::class, + 'page', + ] + ); + add_action('load-' . $this->page_hooks['backwpupabout'], [\BackWPup_Admin::class, 'init_general']); + add_action( + 'admin_print_styles-' . $this->page_hooks['backwpupabout'], + [ + \BackWPup_Page_About::class, + 'admin_print_styles', + ] + ); + add_action( + 'admin_print_scripts-' . $this->page_hooks['backwpupabout'], + [ + \BackWPup_Page_About::class, + 'admin_print_scripts', + ] + ); + + return $page_hooks; + } + + /** + * Called on save form. Only POST allowed. + */ + public function save_post_form() + { + $allowed_pages = [ + 'backwpupeditjob', + 'backwpupinformation', + 'backwpupsettings', + ]; + + if (!in_array($_POST['page'], $allowed_pages, true)) { + wp_die(esc_html__('Cheating, huh?', 'backwpup')); + } + + //nonce check + check_admin_referer($_POST['page'] . '_page'); + + if (!current_user_can('backwpup')) { + wp_die(esc_html__('Cheating, huh?', 'backwpup')); + } + + //build query for redirect + if (!isset($_POST['anchor'])) { + $_POST['anchor'] = null; + } + $query_args = []; + if (isset($_POST['page'])) { + $query_args['page'] = $_POST['page']; + } + if (isset($_POST['tab'])) { + $query_args['tab'] = $_POST['tab']; + } + if (isset($_POST['tab'], $_POST['nexttab']) && $_POST['tab'] !== $_POST['nexttab']) { + $query_args['tab'] = $_POST['nexttab']; + } + + $jobid = null; + if (isset($_POST['jobid'])) { + $jobid = (int) $_POST['jobid']; + $query_args['jobid'] = $jobid; + } + + // Call method to save data + if ($_POST['page'] === 'backwpupeditjob') { + BackWPup_Page_Editjob::save_post_form($_POST['tab'], $jobid); + } elseif ($_POST['page'] === 'backwpupsettings') { + $this->settings->save_post_form(); + } + + //Back to topic + wp_safe_redirect(add_query_arg($query_args, network_admin_url('admin.php')) . $_POST['anchor']); + + exit; + } + + /** + * Overrides WordPress text in Footer. + * + * @param $admin_footer_text string + * + * @return string + */ + public function admin_footer_text($admin_footer_text) + { + $default_text = $admin_footer_text; + if (isset($_REQUEST['page']) && strstr($_REQUEST['page'], 'backwpup')) { + $admin_footer_text = sprintf( + '', + '' + ); + + if (!class_exists(\BackWPup_Pro::class, false)) { + $admin_footer_text .= sprintf( + __( + 'Get BackWPup Pro now.', + 'backwpup' + ), + __('http://backwpup.com', 'backwpup') + ); + } + + return $admin_footer_text . $default_text; + } + + return $admin_footer_text; + } + + /** + * Overrides WordPress Version in Footer. + * + * @param $update_footer_text string + * + * @return string + */ + public function update_footer($update_footer_text) + { + $default_text = $update_footer_text; + + if (isset($_REQUEST['page']) && strstr($_REQUEST['page'], 'backwpup')) { + $update_footer_text = '' . BackWPup::get_plugin_data('Name') . ' ' . sprintf( + __( + 'version %s', + 'backwpup' + ), + BackWPup::get_plugin_data('Version') + ) . ''; + + return $update_footer_text . $default_text; + } + + return $update_footer_text; + } + + /** + * Add filed for selecting user role in user section. + * + * @param $user WP_User + */ + public function user_profile_fields($user) + { + global $wp_roles; + + if (!is_super_admin() && !current_user_can('backwpup_admin')) { + return; + } + + //user is admin and has BackWPup rights + if ($user->has_cap('administrator') && $user->has_cap('backwpup_settings')) { + return; + } + + //get backwpup roles + $backwpup_roles = []; + + foreach ($wp_roles->roles as $role => $role_value) { + if (substr($role, 0, 8) != 'backwpup') { + continue; + } + $backwpup_roles[$role] = $role_value; + } + + //only if user has other than backwpup role + if (!empty($user->roles[0]) && in_array($user->roles[0], array_keys($backwpup_roles), true)) { + return; + } ?> +

- +
roles ) as $role ) { - if ( ! strstr( $role, 'backwpup_' ) ) { - continue; - } - $backwpup_roles[] = $role; - } - - //get user for adding/removing role - $user = new WP_User( $user_id ); - //a admin needs no extra role - if ( $user->has_cap( 'administrator' ) && $user->has_cap( 'backwpup_settings' ) ) { - $backwpup_role = ''; - } - - //remove BackWPup role from user if it not the actual - foreach ( $user->roles as $role ) { - if ( ! strstr( $role, 'backwpup_' ) ) { - continue; - } - if ( $role !== $backwpup_role ) { - $user->remove_role( $role ); - } else { - $backwpup_role = ''; - } - } - - //add new role to user if it not the actual - if ( $backwpup_role && in_array( $backwpup_role, $backwpup_roles, true ) ) { - $user->add_role( $backwpup_role ); - } - - return; - } + } + + /** + * Save for user role adding. + * + * @param $user_id int + */ + public function save_profile_update($user_id) + { + global $wp_roles; + + if (!is_super_admin() && !current_user_can('backwpup_admin')) { + return; + } + + if (empty($user_id)) { + return; + } + + if (!isset($_POST['backwpup_role'])) { + return; + } + + $backwpup_role = esc_attr($_POST['backwpup_role']); + + //get BackWPup roles + $backwpup_roles = []; + + foreach (array_keys($wp_roles->roles) as $role) { + if (!strstr($role, 'backwpup_')) { + continue; + } + $backwpup_roles[] = $role; + } + + //get user for adding/removing role + $user = new WP_User($user_id); + //a admin needs no extra role + if ($user->has_cap('administrator') && $user->has_cap('backwpup_settings')) { + $backwpup_role = ''; + } + + //remove BackWPup role from user if it not the actual + foreach ($user->roles as $role) { + if (!strstr($role, 'backwpup_')) { + continue; + } + if ($role !== $backwpup_role) { + $user->remove_role($role); + } else { + $backwpup_role = ''; + } + } + + //add new role to user if it not the actual + if ($backwpup_role && in_array($backwpup_role, $backwpup_roles, true)) { + $user->add_role($backwpup_role); + } + } public function init() { @@ -715,9 +767,9 @@ public function init() //Save Form posts general add_action('admin_post_backwpup', [$this, 'save_post_form']); //Save Form posts wizard - add_action('admin_post_backwpup_wizard', ['BackWPup_Pro_Page_Wizard', 'save_post_form']); + add_action('admin_post_backwpup_wizard', [\BackWPup_Pro_Page_Wizard::class, 'save_post_form']); // Save form posts for support - add_action('admin_post_backwpup_support', ['BackWPup_Pro_Page_Support', 'save_post_form']); + add_action('admin_post_backwpup_support', [\BackWPup_Pro_Page_Support::class, 'save_post_form']); //Admin Footer Text replacement add_filter('admin_footer_text', [$this, 'admin_footer_text'], 100); add_filter('update_footer', [$this, 'update_footer'], 100); @@ -730,5 +782,4 @@ public function init() private function __clone() { } - } diff --git a/inc/class-adminbar.php b/inc/class-adminbar.php index 9d9c05f3..7f339c79 100644 --- a/inc/class-adminbar.php +++ b/inc/class-adminbar.php @@ -1,20 +1,20 @@ admin = $admin; } - public function init() + public function init() { BackWPup::load_text_domain(); @@ -22,101 +22,107 @@ public function init() add_action('wp_head', [$this->admin, 'admin_css']); } - /** - * @global $wp_admin_bar WP_Admin_Bar - */ - public function adminbar() { + /** + * @global $wp_admin_bar WP_Admin_Bar + */ + public function adminbar() + { if (!is_admin_bar_showing()) { return; } - global $wp_admin_bar; - /* @var WP_Admin_Bar $wp_admin_bar */ + /** @var WP_Admin_Bar $wp_admin_bar */ + global $wp_admin_bar; - $menu_title = ''; - $menu_herf = network_admin_url( 'admin.php?page=backwpup' ); - if ( file_exists( BackWPup::get_plugin_data( 'running_file' ) ) && current_user_can( 'backwpup_jobs_start' ) ) { - $menu_title = '' . esc_html( BackWPup::get_plugin_data( 'name' ) ) . ' ' . esc_html__( 'running', 'backwpup' ) . ''; - $menu_herf = network_admin_url( 'admin.php?page=backwpupjobs' ); - } - - if ( current_user_can( 'backwpup' ) ) - $wp_admin_bar->add_menu( array( - 'id' => 'backwpup', - 'title' => $menu_title, - 'href' => $menu_herf, - 'meta' => array( 'title' => BackWPup::get_plugin_data( 'name' ) ) - ) ); + $menu_title = ''; + $menu_herf = network_admin_url('admin.php?page=backwpup'); + if (file_exists(BackWPup::get_plugin_data('running_file')) && current_user_can('backwpup_jobs_start')) { + $menu_title = '' . esc_html(BackWPup::get_plugin_data('name')) . ' ' . esc_html__('running', 'backwpup') . ''; + $menu_herf = network_admin_url('admin.php?page=backwpupjobs'); + } - if ( file_exists( BackWPup::get_plugin_data( 'running_file' ) ) && current_user_can( 'backwpup_jobs_start' ) ) { - $wp_admin_bar->add_menu( array( - 'id' => 'backwpup_working', - 'parent' => 'backwpup_jobs', - 'title' => __( 'Now Running', 'backwpup' ), - 'href' => network_admin_url( 'admin.php?page=backwpupjobs' ) - ) ); - $wp_admin_bar->add_menu( array( - 'id' => 'backwpup_working_abort', - 'parent' => 'backwpup_working', - 'title' => __( 'Abort!', 'backwpup' ), - 'href' => wp_nonce_url( network_admin_url( 'admin.php?page=backwpup&action=abort' ), 'abort-job' ) - ) ); - } + if (current_user_can('backwpup')) { + $wp_admin_bar->add_menu([ + 'id' => 'backwpup', + 'title' => $menu_title, + 'href' => $menu_herf, + 'meta' => ['title' => BackWPup::get_plugin_data('name')], + ]); + } - if ( current_user_can( 'backwpup_jobs' ) ) - $wp_admin_bar->add_menu( array( - 'id' => 'backwpup_jobs', - 'parent' => 'backwpup', - 'title' => __( 'Jobs', 'backwpup' ), - 'href' => network_admin_url( 'admin.php?page=backwpupjobs' ) - ) ); + if (file_exists(BackWPup::get_plugin_data('running_file')) && current_user_can('backwpup_jobs_start')) { + $wp_admin_bar->add_menu([ + 'id' => 'backwpup_working', + 'parent' => 'backwpup_jobs', + 'title' => __('Now Running', 'backwpup'), + 'href' => network_admin_url('admin.php?page=backwpupjobs'), + ]); + $wp_admin_bar->add_menu([ + 'id' => 'backwpup_working_abort', + 'parent' => 'backwpup_working', + 'title' => __('Abort!', 'backwpup'), + 'href' => wp_nonce_url(network_admin_url('admin.php?page=backwpup&action=abort'), 'abort-job'), + ]); + } - if ( current_user_can( 'backwpup_jobs_edit' ) ) - $wp_admin_bar->add_menu( array( - 'id' => 'backwpup_jobs_new', - 'parent' => 'backwpup_jobs', - 'title' => __( 'Add new', 'backwpup' ), - 'href' => network_admin_url( 'admin.php?page=backwpupeditjob&tab=job' ) - ) ); + if (current_user_can('backwpup_jobs')) { + $wp_admin_bar->add_menu([ + 'id' => 'backwpup_jobs', + 'parent' => 'backwpup', + 'title' => __('Jobs', 'backwpup'), + 'href' => network_admin_url('admin.php?page=backwpupjobs'), + ]); + } - if ( current_user_can( 'backwpup_logs' ) ) - $wp_admin_bar->add_menu( array( - 'id' => 'backwpup_logs', - 'parent' => 'backwpup', - 'title' => __( 'Logs', 'backwpup' ), - 'href' => network_admin_url( 'admin.php?page=backwpuplogs' ) - ) ); + if (current_user_can('backwpup_jobs_edit')) { + $wp_admin_bar->add_menu([ + 'id' => 'backwpup_jobs_new', + 'parent' => 'backwpup_jobs', + 'title' => __('Add new', 'backwpup'), + 'href' => network_admin_url('admin.php?page=backwpupeditjob&tab=job'), + ]); + } - if ( current_user_can( 'backwpup_backups' ) ) - $wp_admin_bar->add_menu( array( - 'id' => 'backwpup_backups', - 'parent' => 'backwpup', - 'title' => __( 'Backups', 'backwpup' ), - 'href' => network_admin_url( 'admin.php?page=backwpupbackups' ) - ) ); + if (current_user_can('backwpup_logs')) { + $wp_admin_bar->add_menu([ + 'id' => 'backwpup_logs', + 'parent' => 'backwpup', + 'title' => __('Logs', 'backwpup'), + 'href' => network_admin_url('admin.php?page=backwpuplogs'), + ]); + } + if (current_user_can('backwpup_backups')) { + $wp_admin_bar->add_menu([ + 'id' => 'backwpup_backups', + 'parent' => 'backwpup', + 'title' => __('Backups', 'backwpup'), + 'href' => network_admin_url('admin.php?page=backwpupbackups'), + ]); + } - //add jobs - $jobs = (array)BackWPup_Option::get_job_ids(); - foreach ( $jobs as $jobid ) { - if ( current_user_can( 'backwpup_jobs_edit' ) ) { - $name = BackWPup_Option::get( $jobid, 'name' ); - $wp_admin_bar->add_menu( array( - 'id' => 'backwpup_jobs_' . $jobid, - 'parent' => 'backwpup_jobs', - 'title' => $name, - 'href' => wp_nonce_url( network_admin_url( 'admin.php?page=backwpupeditjob&tab=job&jobid=' . $jobid ) , 'edit-job' ) - ) ); - } - if ( current_user_can( 'backwpup_jobs_start' ) ) { - $url = BackWPup_Job::get_jobrun_url( 'runnowlink', $jobid ); - $wp_admin_bar->add_menu( array( - 'id' => 'backwpup_jobs_runnow_' . $jobid, - 'parent' => 'backwpup_jobs_' . $jobid, - 'title' => __( 'Run Now', 'backwpup' ), - 'href' => esc_url( $url[ 'url' ] ) - ) ); - } - } - } + //add jobs + $jobs = (array) BackWPup_Option::get_job_ids(); + + foreach ($jobs as $jobid) { + if (current_user_can('backwpup_jobs_edit')) { + $name = BackWPup_Option::get($jobid, 'name'); + $wp_admin_bar->add_menu([ + 'id' => 'backwpup_jobs_' . $jobid, + 'parent' => 'backwpup_jobs', + 'title' => $name, + 'href' => wp_nonce_url(network_admin_url('admin.php?page=backwpupeditjob&tab=job&jobid=' . $jobid), 'edit-job'), + ]); + } + if (current_user_can('backwpup_jobs_start')) { + $url = BackWPup_Job::get_jobrun_url('runnowlink', $jobid); + $wp_admin_bar->add_menu([ + 'id' => 'backwpup_jobs_runnow_' . $jobid, + 'parent' => 'backwpup_jobs_' . $jobid, + 'title' => __('Run Now', 'backwpup'), + 'href' => esc_url($url['url']), + ]); + } + } + } } diff --git a/inc/class-create-archive-exception.php b/inc/class-create-archive-exception.php index 42813f73..67f0b606 100644 --- a/inc/class-create-archive-exception.php +++ b/inc/class-create-archive-exception.php @@ -1,11 +1,11 @@ file = trim( $file ); - - // TAR.GZ - if ( - (! $this->filehandler && '.tar.gz' === strtolower( substr( $this->file, - 7 ) )) - || ( ! $this->filehandler && '.tar.bz2' === strtolower( substr( $this->file, - 8 ) ) ) - ) { - if ( ! function_exists( 'gzencode' ) ) { - throw new BackWPup_Create_Archive_Exception( - __( 'Functions for gz compression not available', 'backwpup' ) - ); - } - - $this->method = 'TarGz'; - $this->handlertype = 'gz'; - $this->filehandler = $this->fopen( $this->file, 'ab' ); - } - - // .TAR - if ( ! $this->filehandler && '.tar' === strtolower( substr( $this->file, - 4 ) ) ) { - $this->method = 'Tar'; - $this->filehandler = $this->fopen( $this->file, 'ab' ); // phpcs:ignore - } - - // .ZIP - if ( ! $this->filehandler && '.zip' === strtolower( substr( $this->file, - 4 ) ) ) { - $this->method = 'ZipArchive'; - - // Switch to PclZip if ZipArchive isn't supported. - if ( ! class_exists( 'ZipArchive' ) ) { - $this->method = 'PclZip'; - } - - // GzEncode supported? - if ( 'PclZip' === $this->method && ! function_exists( 'gzencode' ) ) { - throw new BackWPup_Create_Archive_Exception( - esc_html__( 'Functions for gz compression not available', 'backwpup' ) - ); - } - - if ( 'ZipArchive' === $this->method ) { - $this->ziparchive = new ZipArchive(); - $ziparchive_open = $this->ziparchive->open( $this->file, ZipArchive::CREATE ); - - if ( $ziparchive_open !== true ) { - $this->ziparchive_status(); - - throw new BackWPup_Create_Archive_Exception( - sprintf( - /* translators: $1 is a directory name */ - esc_html_x( 'Cannot create zip archive: %d', 'ZipArchive open() result', 'backwpup' ), - $ziparchive_open - ) - ); - } - } - - if ( 'PclZip' === $this->method ) { - $this->method = 'PclZip'; - - if ( ! defined( 'PCLZIP_TEMPORARY_DIR' ) ) { - define( 'PCLZIP_TEMPORARY_DIR', BackWPup::get_plugin_data( 'TEMP' ) ); - } - - require_once ABSPATH . 'wp-admin/includes/class-pclzip.php'; - - $this->pclzip = new PclZip( $this->file ); - } - - // Must be set to true to prevent issues. Monkey patch. - $this->filehandler = true; - } - - // .GZ - if ( - ( ! $this->filehandler && '.gz' === strtolower( substr( $this->file, - 3 ) ) ) - || ( ! $this->filehandler && '.bz2' === strtolower( substr( $this->file, - 4 ) ) ) - ) { - if ( ! function_exists( 'gzencode' ) ) { - throw new BackWPup_Create_Archive_Exception( - __( 'Functions for gz compression not available', 'backwpup' ) - ); - } - - $this->method = 'gz'; - $this->handlertype = 'gz'; - $this->filehandler = $this->fopen( $this->file, 'w' ); - } - - if ( '' === $this->method ) { - throw new BackWPup_Create_Archive_Exception( - sprintf( - /* translators: the $1 is the type of the archive file */ - esc_html_x( 'Method to archive file %s not detected', '%s = file name', 'backwpup' ), - basename( $this->file ) - ) - ); - } - - if ( null === $this->filehandler ) { - throw new BackWPup_Create_Archive_Exception( __( 'Cannot open archive file', 'backwpup' ) ); - } - } - - /** - * Destruct - * - * Closes open archive on shutdown. - */ - public function __destruct() { - - // Close PclZip. - if ( is_object( $this->pclzip ) ) { - if ( count( $this->pclzip_file_list ) > 0 ) { - if ( 0 == $this->pclzip->add( $this->pclzip_file_list ) ) { - trigger_error( - sprintf( - /* translatores: $1 is the error string */ - esc_html__( 'PclZip archive add error: %s', 'backwpup' ), - $this->pclzip->errorInfo( true ) - ), - E_USER_ERROR - ); - } - } - unset( $this->pclzip ); - } - - // Close ZipArchive. - if ( null !== $this->ziparchive ) { - if ( ! $this->ziparchive->close() ) { - $this->ziparchive_status(); - - sleep( 1 ); - } - $this->ziparchive = null; - } - - // Close file if open. - if ( is_resource( $this->filehandler ) ) { - $this->fclose(); - } - } - - /** - * Close - * - * Closing the archive - * - * @return void - */ - public function close() { - - if ( $this->ziparchive instanceof \ZipArchive ) { - $this->ziparchive->close(); - $this->ziparchive = null; - } - - if ( ! is_resource( $this->filehandler ) ) { - return; - } - - // Write tar file end. - if ( in_array( $this->method, array( 'Tar', 'TarGz' ), true ) ) { - $this->fwrite( pack( 'a1024', '' ) ); - } - - $this->fclose(); - } - - /** - * Get Method - * - * Get method that the archive uses - * - * @return string The compression method - */ - public function get_method() { - - return $this->method; - } - - /** - * Adds a file to Archive - * - * @param string $file_name The file name path. - * @param string $name_in_archive The name of the file to use within the archive. - * - * @return bool True on success, false on error. - */ - public function add_file( $file_name, $name_in_archive = '' ) { - - $file_name = trim( $file_name ); - - if ( ! is_string( $file_name ) || empty( $file_name ) ) { - trigger_error( - esc_html__( 'File name cannot be empty.', 'backwpup' ), - E_USER_WARNING - ); - - return false; - } - - clearstatcache( true, $file_name ); - - if ( ! is_readable( $file_name ) ) { - trigger_error( - sprintf( - /* translators: The $1 is the name of the file to add to the archive. */ - esc_html_x( 'File %s does not exist or is not readable', 'File to add to archive', 'backwpup' ), - $file_name - ), - E_USER_WARNING - ); - - return true; - } - - if ( empty( $name_in_archive ) ) { - $name_in_archive = $file_name; - } - - switch ( $this->method ) { - case 'gz': - if ( ! is_resource( $this->filehandler ) ) { - return false; - } - - if ( $this->file_count > 0 ) { - trigger_error( - esc_html__( 'This archive method can only add one file', 'backwpup' ), - E_USER_WARNING - ); - - return false; - } - - $fd = $this->fopen( $file_name, 'rb' ); - if ( ! $fd ) { - return false; - } - - while ( ! feof( $fd ) ) { - $this->fwrite( fread( $fd, 8192 ) ); // phpcs:ignore - } - fclose( $fd ); // phpcs:ignore - - $this->file_count ++; - break; - - case 'Tar': - case 'TarGz': - // Convert chars for archives file names - if ( function_exists( 'iconv' ) && stripos( PHP_OS, 'win' ) === 0 ) { - $test = @iconv( 'ISO-8859-1', 'UTF-8', $name_in_archive ); - if ( $test ) { - $name_in_archive = $test; - } - } - - return $this->tar_file( $file_name, $name_in_archive ); - break; - - case 'ZipArchive': - // Convert chars for archives file names. - if ( function_exists( 'iconv' ) && stripos( PHP_OS, 'win' ) === 0 ) { - $test = @iconv( 'UTF-8', 'CP437', $name_in_archive ); - if ( $test ) { - $name_in_archive = $test; - } - } - - $file_size = filesize( $file_name ); - if ( false === $file_size ) { - return false; - } - - $zip_file_stat = $this->ziparchive->statName( $name_in_archive ); - // If the file is allready in the archive doing anything else. - if ( isset( $zip_file_stat['size'] ) && $zip_file_stat['size'] === $file_size ) { - return true; - } - - // The file is in the archive but the size is different than the one we - // want to store. So delete the old and store the new one. - if ( $zip_file_stat ) { - $this->ziparchive->deleteName( $name_in_archive ); - // Reopen on deletion. - $this->file_count = 21; - } - - // Close and reopen, all added files are open on fs. - // 35 works with PHP 5.2.4 on win. - if ( $this->file_count > 20 ) { - if ( ! $this->ziparchive->close() ) { - $this->ziparchive_status(); - trigger_error( - esc_html__( 'ZIP archive cannot be closed correctly', 'backwpup' ), - E_USER_ERROR - ); - - sleep( 1 ); - } - - $this->ziparchive = null; - - if ( ! $this->check_archive_filesize() ) { - return false; - } - - $this->ziparchive = new ZipArchive(); - $ziparchive_open = $this->ziparchive->open( $this->file, ZipArchive::CREATE ); - - if ( $ziparchive_open !== true ) { - $this->ziparchive_status(); - - return false; - } - - $this->file_count = 0; - } - - if ( $file_size < ( 1024 * 1024 * 2 ) ) { - if ( ! $this->ziparchive->addFromString( $name_in_archive, file_get_contents( $file_name ) ) ) { - $this->ziparchive_status(); - trigger_error( - sprintf( - /* translators: the $1 is the name of the archive. */ - esc_html__( 'Cannot add "%s" to zip archive!', 'backwpup' ), - $name_in_archive - ), - E_USER_ERROR - ); - - return false; - } else { - $file_factor = round( $file_size / ( 1024 * 1024 ), 4 ) * 2; - $this->file_count = $this->file_count + $file_factor; - } - } else { - if ( ! $this->ziparchive->addFile( $file_name, $name_in_archive ) ) { - $this->ziparchive_status(); - trigger_error( - sprintf( - /* translators: the $1 is the name of the archive. */ - esc_html__( 'Cannot add "%s" to zip archive!', 'backwpup' ), - $name_in_archive - ), - E_USER_ERROR - ); - - return false; - } else { - $this->file_count ++; - } - } - break; - - case 'PclZip': - $this->pclzip_file_list[] = array( - PCLZIP_ATT_FILE_NAME => $file_name, - PCLZIP_ATT_FILE_NEW_FULL_NAME => $name_in_archive, - ); - - if ( count( $this->pclzip_file_list ) >= 100 ) { - if ( 0 == $this->pclzip->add( $this->pclzip_file_list ) ) { - trigger_error( - sprintf( - /* translators: The $1 is the tecnical error string from pclzip. */ - esc_html__( 'PclZip archive add error: %s', 'backwpup' ), - $this->pclzip->errorInfo( true ) - ), - E_USER_ERROR - ); - - return false; - } - $this->pclzip_file_list = array(); - } - break; - } - - return true; - } - - /** - * Add a empty Folder to archive - * - * @param string $folder_name Name of folder to add to archive. - * @param string $name_in_archive The name of archive to use within the archive. - * - * @return bool - */ - public function add_empty_folder( $folder_name, $name_in_archive ) { - - $folder_name = trim( $folder_name ); - - if ( empty( $folder_name ) ) { - trigger_error( - esc_html__( 'Folder name cannot be empty', 'backwpup' ), - E_USER_WARNING - ); - - return false; - } - - if ( ! is_dir( $folder_name ) || ! is_readable( $folder_name ) ) { - trigger_error( - sprintf( - /* translators: $1 is the folder name */ - esc_html_x( - 'Folder %s does not exist or is not readable', - 'Folder path to add to archive', - 'backwpup' - ), - $folder_name - ), - E_USER_WARNING - ); - - return false; - } - - if ( empty( $name_in_archive ) ) { - return false; - } - - // Remove reserved chars. - $name_in_archive = remove_invalid_characters_from_directory_name( $name_in_archive ); - - switch ( $this->method ) { - case 'gz': - trigger_error( - esc_html__( 'This archive method can only add one file', 'backwpup' ), - E_USER_ERROR - ); - - return false; - break; - - case 'Tar': - case 'TarGz': - $this->tar_empty_folder( $folder_name, $name_in_archive ); - - return false; - break; - - case 'ZipArchive': - if ( ! $this->ziparchive->addEmptyDir( $name_in_archive ) ) { - trigger_error( - sprintf( - /* translators: $1 is the name of the archive. */ - esc_html__( 'Cannot add "%s" to zip archive!', 'backwpup' ), - $name_in_archive - ), - E_USER_WARNING - ); - - return false; - } - break; - - case 'PclZip': - return true; - break; - } - - return true; - } - - /** - * Output status of ZipArchive - * - * @return bool - */ - private function ziparchive_status() { - - if ( $this->ziparchive->status === 0 ) { - return true; - } - - trigger_error( - sprintf( - /* translators. $1 is the status returned by a call to a ZipArchive method. */ - esc_html_x( 'ZipArchive returns status: %s', 'Text of ZipArchive status Message', 'backwpup' ), - $this->ziparchive->getStatusString() - ), - E_USER_ERROR - ); - - return false; - } - - /** - * Tar a file to archive - * - * @param string $file_name The file to store in the archive. - * @param string $name_in_archive The file name to use within the archive. - * - * @return bool True on success, false on failure - */ - private function tar_file( $file_name, $name_in_archive ) { - - if ( ! is_resource( $this->filehandler ) ) { - return false; - } - - if ( ! $this->check_archive_filesize( $file_name ) ) { - return false; - } - - $chunk_size = 1024 * 1024 * 4; - $filename = $name_in_archive; - $filename_prefix = ''; - - // Split filename larger than 100 chars - if ( 100 < strlen( $name_in_archive ) ) { - $filename_offset = strlen( $name_in_archive ) - 100; - $split_pos = strpos( $name_in_archive, '/', $filename_offset ); - - if ( $split_pos === false ) { - $split_pos = strrpos( $name_in_archive, '/' ); - } - - $filename = substr( $name_in_archive, $split_pos + 1 ); - $filename_prefix = substr( $name_in_archive, 0, $split_pos ); - - if ( strlen( $filename ) > 100 ) { - $filename = substr( $filename, - 100 ); - trigger_error( - sprintf( - /* translators: $1 is the file name. */ - esc_html__( 'File name "%1$s" is too long to be saved correctly in %2$s archive!', 'backwpup' ), - $name_in_archive, - $this->method - ), - E_USER_WARNING - ); - } - - if ( 155 < strlen( $filename_prefix ) ) { - trigger_error( - sprintf( - /* translators: $1 is the file name to use in the archive. */ - esc_html__( 'File path "%1$s" is too long to be saved correctly in %2$s archive!', 'backwpup' ), - $name_in_archive, - $this->method - ), - E_USER_WARNING - ); - } - } - - // Get file stat. - $file_stat = stat( $file_name ); - if ( ! $file_stat ) { - return true; - } - - // Sanitize values. - $file_stat['size'] = abs( (int) $file_stat['size'] ); - - // Retrieve owner and group for the file. - list( $owner, $group ) = $this->posix_getpwuid( $file_stat['uid'], $file_stat['gid'] ); - - // Generate the TAR header for this file - $chunk = $this->make_tar_headers( - $filename, - $file_stat['mode'], - $file_stat['uid'], - $file_stat['gid'], - $file_stat['size'], - $file_stat['mtime'], - 0, - $owner, - $group, - $filename_prefix - ); - - $fd = false; - if ( $file_stat['size'] > 0 ) { - $fd = fopen( $file_name, 'rb' ); - - if ( ! is_resource( $fd ) ) { - trigger_error( - sprintf( - esc_html__( 'Cannot open source file %s for archiving. Writing an empty file.', 'backwpup' ), - $file_name - ), - E_USER_WARNING - ); - } - } - - if ( $fd ) { - // Read/write files in 512 bit Blocks. - while ( ( $content = fread( $fd, 512 ) ) != '' ) { // phpcs:ignore - $chunk .= pack( 'a512', $content ); - - if ( strlen( $chunk ) >= $chunk_size ) { - $this->fwrite( $chunk ); - - $chunk = ''; - } - } - fclose( $fd ); // phpcs:ignore - } - - if ( ! empty( $chunk ) ) { - $this->fwrite( $chunk ); - } - - return true; - } - - /** - * Tar an empty Folder to archive - * - * @return bool True on success, false on failure. - */ - private function tar_empty_folder( $folder_name, $name_in_archive ) { - - if ( ! is_resource( $this->filehandler ) ) { - return false; - } - - $name_in_archive = trailingslashit( $name_in_archive ); - - $tar_filename = $name_in_archive; - $tar_filename_prefix = ''; - - // Split filename larger than 100 chars. - if ( 100 < strlen( $name_in_archive ) ) { - $filename_offset = strlen( $name_in_archive ) - 100; - $split_pos = strpos( $name_in_archive, '/', $filename_offset ); - - if ( $split_pos === false ) { - $split_pos = strrpos( untrailingslashit( $name_in_archive ), '/' ); - } - - $tar_filename = substr( $name_in_archive, $split_pos + 1 ); - $tar_filename_prefix = substr( $name_in_archive, 0, $split_pos ); - - if ( strlen( $tar_filename ) > 100 ) { - $tar_filename = substr( $tar_filename, - 100 ); - trigger_error( - sprintf( - /* translators: $1 is the name of the folder. $2 is the archive name.*/ - esc_html__( - 'Folder name "%1$s" is too long to be saved correctly in %2$s archive!', - 'backwpup' - ), - $name_in_archive, - $this->method - ), - E_USER_WARNING - ); - } - - if ( strlen( $tar_filename_prefix ) > 155 ) { - trigger_error( - sprintf( - /* translators: $1 is the name of the folder. $2 is the archive name.*/ - esc_html__( - 'Folder path "%1$s" is too long to be saved correctly in %2$s archive!', - 'backwpup' - ), - $name_in_archive, - $this->method - ), - E_USER_WARNING - ); - } - } - - $file_stat = @stat( $folder_name ); - // Retrieve owner and group for the file. - list( $owner, $group ) = $this->posix_getpwuid( $file_stat['uid'], $file_stat['gid'] ); - - // Generate the TAR header for this file - $header = $this->make_tar_headers( - $tar_filename, - $file_stat['mode'], - $file_stat['uid'], - $file_stat['gid'], - $file_stat['size'], - $file_stat['mtime'], - 5, - $owner, - $group, - $tar_filename_prefix - ); - - $this->fwrite( $header ); - - return true; - } - - /** - * Check Archive File size - * - * @param string $file_to_add THe file to check - * - * @return bool True if the file size is less than PHP_INT_MAX false otherwise. - */ - public function check_archive_filesize( $file_to_add = '' ) { - - $file_to_add_size = 0; - - if ( ! empty( $file_to_add ) ) { - $file_to_add_size = filesize( $file_to_add ); - - if ( $file_to_add_size === false ) { - $file_to_add_size = 0; - } - } - - if ( is_resource( $this->filehandler ) ) { - $stats = fstat( $this->filehandler ); - $archive_size = $stats['size']; - } else { - $archive_size = filesize( $this->file ); - if ( $archive_size === false ) { - $archive_size = PHP_INT_MAX; - } - } - - $archive_size = $archive_size + $file_to_add_size; - if ( $archive_size >= PHP_INT_MAX ) { - trigger_error( - sprintf( - esc_html__( - 'If %s will be added to your backup archive, the archive will be too large for operations with this PHP Version. You might want to consider splitting the backup job in multiple jobs with less files each.', - 'backwpup' - ), - $file_to_add - ), - E_USER_ERROR - ); - - return false; - } - - return true; - } - - /** - * Make Tar Headers - * - * @param string $name The name of the file or directory. Known as Item. - * @param string $mode The permissions for the item. - * @param integer $uid The owner ID. - * @param integer $gid The group ID. - * @param integer $size The size of the item. - * @param integer $mtime The time of the last modification. - * @param integer $typeflag The type of the item. 0 for File and 5 for Directory. - * @param string $owner The owner Name. - * @param string $group The group Name. - * @param string $prefix The item prefix. - * - * @return mixed|string - */ - private function make_tar_headers( $name, $mode, $uid, $gid, $size, $mtime, $typeflag, $owner, $group, $prefix ) { - - // Generate the TAR header for this file - $chunk = pack( - "a100a8a8a8a12a12a8a1a100a6a2a32a32a8a8a155a12", - $name, //name of file 100 - sprintf( "%07o", $mode ), //file mode 8 - sprintf( "%07o", $uid ), //owner user ID 8 - sprintf( "%07o", $gid ), //owner group ID 8 - sprintf( "%011o", $size ), //length of file in bytes 12 - sprintf( "%011o", $mtime ), //modify time of file 12 - " ", //checksum for header 8 - $typeflag, //type of file 0 or null = File, 5=Dir - "", //name of linked file 100 - "ustar", //USTAR indicator 6 - "00", //USTAR version 2 - $owner, //owner user name 32 - $group, //owner group name 32 - "", //device major number 8 - "", //device minor number 8 - $prefix, //prefix for file name 155 - "" - ); //fill block 12 - - // Computes the unsigned Checksum of a file's header - $checksum = 0; - for ( $i = 0; $i < 512; $i ++ ) { - $checksum += ord( substr( $chunk, $i, 1 ) ); - } - - $checksum = pack( "a8", sprintf( "%07o", $checksum ) ); - $chunk = substr_replace( $chunk, $checksum, 148, 8 ); - - return $chunk; - } - - /** - * Posix Get PW ID - * - * @param integer $uid The user ID. - * @param integer $gid The group ID. - * - * @return array The owner and group in posix format - */ - private function posix_getpwuid( $uid, $gid ) { - - // Set file user/group name if linux. - $owner = esc_html__( 'Unknown', 'backwpup' ); - $group = esc_html__( 'Unknown', 'backwpup' ); - - if ( function_exists( 'posix_getpwuid' ) ) { - $info = posix_getpwuid( $uid ); - $owner = $info['name']; - $info = posix_getgrgid( $gid ); - $group = $info['name']; - } - - return array( - $owner, - $group, - ); - } - - /** - * Fopen - * - * @param string $filename The file to open in mode. - * @param string $mode The mode to open the file. - * - * @return bool|resource The resources or false if file cannot be opened. - */ - private function fopen( $filename, $mode ) { - - $fd = fopen( $filename, $mode ); - - if ( ! $fd ) { - trigger_error( - sprintf( - /* translators: $1 is the filename to add into the archive. */ - esc_html__( 'Cannot open source file %s.', 'backwpup' ), - $filename - ), - E_USER_WARNING - ); - } - - return $fd; - } - - /** - * Write Content in File - * - * @param string $content The content to write into the file. - * - * @return int The number of bit wrote into the file. - */ - private function fwrite( $content ) { - - switch ( $this->handlertype ) { - case 'bz': - $content = bzcompress( $content ); - break; - case 'gz': - $content = gzencode( $content ); - break; - default: - break; - } - - return (int) fwrite( $this->filehandler, $content ); - } - - /** - * Close file handler - * - * @return void - */ - private function fclose() { - - fclose( $this->filehandler ); - } +class BackWPup_Create_Archive +{ + /** + * Achieve file with full path. + * + * @var string + */ + private $file = ''; + + /** + * Compression method. + * + * @var string Method off compression Methods are ZipArchive, PclZip, Tar, TarGz, gz + */ + private $method = ''; + + /** + * File Handel. + * + * Open handel for files. + */ + private $filehandler; + + /** + * Handler Type. + * + * @var string can be 'bz', 'gz' or empty string + */ + private $handlertype = ''; + + /** + * ZipArchive. + * + * @var ZipArchive + */ + private $ziparchive; + + /** + * PclZip. + * + * @var PclZip + */ + private $pclzip; + + /** + * PclZip File List. + * + * @var array() + */ + private $pclzip_file_list = []; + + /** + * File Count. + * + * File cont off added files to handel somethings that depends on it + * + * @var int number of files added + */ + private $file_count = 0; + + /** + * BackWPup_Create_Archive constructor. + * + * @param string $file file with full path of the archive + * + * @throws BackWPup_Create_Archive_Exception if the file is empty or not a valid string + */ + public function __construct($file) + { + if (!is_string($file) || empty($file)) { + throw new BackWPup_Create_Archive_Exception( + __('The file name of an archive cannot be empty.', 'backwpup') + ); + } + + // Check folder can used. + if (!is_dir(dirname($file)) || !is_writable(dirname($file))) { + throw new BackWPup_Create_Archive_Exception( + sprintf( + // translators: $1 is the file path + esc_html_x('Folder %s for archive not found', '%s = Folder name', 'backwpup'), + dirname($file) + ) + ); + } + + $this->file = trim($file); + + // TAR.GZ + if ( + (!$this->filehandler && '.tar.gz' === strtolower(substr($this->file, -7))) + || (!$this->filehandler && '.tar.bz2' === strtolower(substr($this->file, -8))) + ) { + if (!function_exists('gzencode')) { + throw new BackWPup_Create_Archive_Exception( + __('Functions for gz compression not available', 'backwpup') + ); + } + + $this->method = 'TarGz'; + $this->handlertype = 'gz'; + $this->filehandler = $this->fopen($this->file, 'ab'); + } + + // .TAR + if (!$this->filehandler && '.tar' === strtolower(substr($this->file, -4))) { + $this->method = 'Tar'; + $this->filehandler = $this->fopen($this->file, 'ab'); // phpcs:ignore + } + + // .ZIP + if (!$this->filehandler && '.zip' === strtolower(substr($this->file, -4))) { + $this->method = \ZipArchive::class; + + // Switch to PclZip if ZipArchive isn't supported. + if (!class_exists(\ZipArchive::class)) { + $this->method = \PclZip::class; + } + + // GzEncode supported? + if (\PclZip::class === $this->method && !function_exists('gzencode')) { + throw new BackWPup_Create_Archive_Exception( + esc_html__('Functions for gz compression not available', 'backwpup') + ); + } + + if (\ZipArchive::class === $this->method) { + $this->ziparchive = new ZipArchive(); + $ziparchive_open = $this->ziparchive->open($this->file, ZipArchive::CREATE); + + if ($ziparchive_open !== true) { + $this->ziparchive_status(); + + throw new BackWPup_Create_Archive_Exception( + sprintf( + // translators: $1 is a directory name + esc_html_x('Cannot create zip archive: %d', 'ZipArchive open() result', 'backwpup'), + $ziparchive_open + ) + ); + } + } + + if (\PclZip::class === $this->method) { + $this->method = \PclZip::class; + + if (!defined('PCLZIP_TEMPORARY_DIR')) { + define('PCLZIP_TEMPORARY_DIR', BackWPup::get_plugin_data('TEMP')); + } + + require_once ABSPATH . 'wp-admin/includes/class-pclzip.php'; + + $this->pclzip = new PclZip($this->file); + } + + // Must be set to true to prevent issues. Monkey patch. + $this->filehandler = true; + } + + // .GZ + if ( + (!$this->filehandler && '.gz' === strtolower(substr($this->file, -3))) + || (!$this->filehandler && '.bz2' === strtolower(substr($this->file, -4))) + ) { + if (!function_exists('gzencode')) { + throw new BackWPup_Create_Archive_Exception( + __('Functions for gz compression not available', 'backwpup') + ); + } + + $this->method = 'gz'; + $this->handlertype = 'gz'; + $this->filehandler = $this->fopen($this->file, 'w'); + } + + if ('' === $this->method) { + throw new BackWPup_Create_Archive_Exception( + sprintf( + // translators: the $1 is the type of the archive file + esc_html_x('Method to archive file %s not detected', '%s = file name', 'backwpup'), + basename($this->file) + ) + ); + } + + if (null === $this->filehandler) { + throw new BackWPup_Create_Archive_Exception(__('Cannot open archive file', 'backwpup')); + } + } + + /** + * Destruct. + * + * Closes open archive on shutdown. + */ + public function __destruct() + { + // Close PclZip. + if (is_object($this->pclzip)) { + if (count($this->pclzip_file_list) > 0) { + if (0 == $this->pclzip->add($this->pclzip_file_list)) { + trigger_error( + sprintf( + // translatores: $1 is the error string + esc_html__('PclZip archive add error: %s', 'backwpup'), + $this->pclzip->errorInfo(true) + ), + E_USER_ERROR + ); + } + } + unset($this->pclzip); + } + + // Close ZipArchive. + if (null !== $this->ziparchive) { + if (!$this->ziparchive->close()) { + $this->ziparchive_status(); + + sleep(1); + } + $this->ziparchive = null; + } + + // Close file if open. + if (is_resource($this->filehandler)) { + $this->fclose(); + } + } + + /** + * Close. + * + * Closing the archive + */ + public function close() + { + if ($this->ziparchive instanceof \ZipArchive) { + $this->ziparchive->close(); + $this->ziparchive = null; + } + + if (!is_resource($this->filehandler)) { + return; + } + + // Write tar file end. + if (in_array($this->method, ['Tar', 'TarGz'], true)) { + $this->fwrite(pack('a1024', '')); + } + + $this->fclose(); + } + + /** + * Get Method. + * + * Get method that the archive uses + * + * @return string The compression method + */ + public function get_method() + { + return $this->method; + } + + /** + * Adds a file to Archive. + * + * @param string $file_name the file name path + * @param string $name_in_archive the name of the file to use within the archive + * + * @return bool true on success, false on error + */ + public function add_file($file_name, $name_in_archive = '') + { + $file_name = trim($file_name); + + if (!is_string($file_name) || empty($file_name)) { + trigger_error( + esc_html__('File name cannot be empty.', 'backwpup'), + E_USER_WARNING + ); + + return false; + } + + clearstatcache(true, $file_name); + + if (!is_readable($file_name)) { + trigger_error( + sprintf( + // translators: The $1 is the name of the file to add to the archive. + esc_html_x('File %s does not exist or is not readable', 'File to add to archive', 'backwpup'), + $file_name + ), + E_USER_WARNING + ); + + return true; + } + + if (empty($name_in_archive)) { + $name_in_archive = $file_name; + } + + switch ($this->method) { + case 'gz': + if (!is_resource($this->filehandler)) { + return false; + } + + if ($this->file_count > 0) { + trigger_error( + esc_html__('This archive method can only add one file', 'backwpup'), + E_USER_WARNING + ); + + return false; + } + + $fd = $this->fopen($file_name, 'rb'); + if (!$fd) { + return false; + } + + while (!feof($fd)) { + $this->fwrite(fread($fd, 8192)); // phpcs:ignore + } + fclose($fd); // phpcs:ignore + + ++$this->file_count; + break; + + case 'Tar': + case 'TarGz': + // Convert chars for archives file names + if (function_exists('iconv') && stripos(PHP_OS, 'win') === 0) { + $test = @iconv('ISO-8859-1', 'UTF-8', $name_in_archive); + if ($test) { + $name_in_archive = $test; + } + } + + return $this->tar_file($file_name, $name_in_archive); + break; + + case \ZipArchive::class: + // Convert chars for archives file names. + if (function_exists('iconv') && stripos(PHP_OS, 'win') === 0) { + $test = @iconv('UTF-8', 'CP437', $name_in_archive); + if ($test) { + $name_in_archive = $test; + } + } + + $file_size = filesize($file_name); + if (false === $file_size) { + return false; + } + + $zip_file_stat = $this->ziparchive->statName($name_in_archive); + // If the file is allready in the archive doing anything else. + if (isset($zip_file_stat['size']) && $zip_file_stat['size'] === $file_size) { + return true; + } + + // The file is in the archive but the size is different than the one we + // want to store. So delete the old and store the new one. + if ($zip_file_stat) { + $this->ziparchive->deleteName($name_in_archive); + // Reopen on deletion. + $this->file_count = 21; + } + + // Close and reopen, all added files are open on fs. + // 35 works with PHP 5.2.4 on win. + if ($this->file_count > 20) { + if (!$this->ziparchive->close()) { + $this->ziparchive_status(); + trigger_error( + esc_html__('ZIP archive cannot be closed correctly', 'backwpup'), + E_USER_ERROR + ); + + sleep(1); + } + + $this->ziparchive = null; + + if (!$this->check_archive_filesize()) { + return false; + } + + $this->ziparchive = new ZipArchive(); + $ziparchive_open = $this->ziparchive->open($this->file, ZipArchive::CREATE); + + if ($ziparchive_open !== true) { + $this->ziparchive_status(); + + return false; + } + + $this->file_count = 0; + } + + if ($file_size < (1024 * 1024 * 2)) { + if (!$this->ziparchive->addFromString($name_in_archive, file_get_contents($file_name))) { + $this->ziparchive_status(); + trigger_error( + sprintf( + // translators: the $1 is the name of the archive. + esc_html__('Cannot add "%s" to zip archive!', 'backwpup'), + $name_in_archive + ), + E_USER_ERROR + ); + + return false; + } + $file_factor = round($file_size / (1024 * 1024), 4) * 2; + $this->file_count = $this->file_count + $file_factor; + } else { + if (!$this->ziparchive->addFile($file_name, $name_in_archive)) { + $this->ziparchive_status(); + trigger_error( + sprintf( + // translators: the $1 is the name of the archive. + esc_html__('Cannot add "%s" to zip archive!', 'backwpup'), + $name_in_archive + ), + E_USER_ERROR + ); + + return false; + } + ++$this->file_count; + } + break; + + case \PclZip::class: + $this->pclzip_file_list[] = [ + PCLZIP_ATT_FILE_NAME => $file_name, + PCLZIP_ATT_FILE_NEW_FULL_NAME => $name_in_archive, + ]; + + if (count($this->pclzip_file_list) >= 100) { + if (0 == $this->pclzip->add($this->pclzip_file_list)) { + trigger_error( + sprintf( + // translators: The $1 is the tecnical error string from pclzip. + esc_html__('PclZip archive add error: %s', 'backwpup'), + $this->pclzip->errorInfo(true) + ), + E_USER_ERROR + ); + + return false; + } + $this->pclzip_file_list = []; + } + break; + } + + return true; + } + + /** + * Add a empty Folder to archive. + * + * @param string $folder_name name of folder to add to archive + * @param string $name_in_archive the name of archive to use within the archive + * + * @return bool + */ + public function add_empty_folder($folder_name, $name_in_archive) + { + $folder_name = trim($folder_name); + + if (empty($folder_name)) { + trigger_error( + esc_html__('Folder name cannot be empty', 'backwpup'), + E_USER_WARNING + ); + + return false; + } + + if (!is_dir($folder_name) || !is_readable($folder_name)) { + trigger_error( + sprintf( + // translators: $1 is the folder name + esc_html_x( + 'Folder %s does not exist or is not readable', + 'Folder path to add to archive', + 'backwpup' + ), + $folder_name + ), + E_USER_WARNING + ); + + return false; + } + + if (empty($name_in_archive)) { + return false; + } + + // Remove reserved chars. + $name_in_archive = remove_invalid_characters_from_directory_name($name_in_archive); + + switch ($this->method) { + case 'gz': + trigger_error( + esc_html__('This archive method can only add one file', 'backwpup'), + E_USER_ERROR + ); + + return false; + break; + + case 'Tar': + case 'TarGz': + $this->tar_empty_folder($folder_name, $name_in_archive); + + return false; + break; + + case \ZipArchive::class: + if (!$this->ziparchive->addEmptyDir($name_in_archive)) { + trigger_error( + sprintf( + // translators: $1 is the name of the archive. + esc_html__('Cannot add "%s" to zip archive!', 'backwpup'), + $name_in_archive + ), + E_USER_WARNING + ); + + return false; + } + break; + + case \PclZip::class: + return true; + break; + } + + return true; + } + + /** + * Output status of ZipArchive. + * + * @return bool + */ + private function ziparchive_status() + { + if ($this->ziparchive->status === 0) { + return true; + } + + trigger_error( + sprintf( + // translators. $1 is the status returned by a call to a ZipArchive method. + esc_html_x('ZipArchive returns status: %s', 'Text of ZipArchive status Message', 'backwpup'), + $this->ziparchive->getStatusString() + ), + E_USER_ERROR + ); + + return false; + } + + /** + * Tar a file to archive. + * + * @param string $file_name the file to store in the archive + * @param string $name_in_archive the file name to use within the archive + * + * @return bool True on success, false on failure + */ + private function tar_file($file_name, $name_in_archive) + { + if (!is_resource($this->filehandler)) { + return false; + } + + if (!$this->check_archive_filesize($file_name)) { + return false; + } + + $chunk_size = 1024 * 1024 * 4; + $filename = $name_in_archive; + $filename_prefix = ''; + + // Split filename larger than 100 chars + if (100 < strlen($name_in_archive)) { + $filename_offset = strlen($name_in_archive) - 100; + $split_pos = strpos($name_in_archive, '/', $filename_offset); + + if ($split_pos === false) { + $split_pos = strrpos($name_in_archive, '/'); + } + + $filename = substr($name_in_archive, $split_pos + 1); + $filename_prefix = substr($name_in_archive, 0, $split_pos); + + if (strlen($filename) > 100) { + $filename = substr($filename, -100); + trigger_error( + sprintf( + // translators: $1 is the file name. + esc_html__('File name "%1$s" is too long to be saved correctly in %2$s archive!', 'backwpup'), + $name_in_archive, + $this->method + ), + E_USER_WARNING + ); + } + + if (155 < strlen($filename_prefix)) { + trigger_error( + sprintf( + // translators: $1 is the file name to use in the archive. + esc_html__('File path "%1$s" is too long to be saved correctly in %2$s archive!', 'backwpup'), + $name_in_archive, + $this->method + ), + E_USER_WARNING + ); + } + } + + // Get file stat. + $file_stat = stat($file_name); + if (!$file_stat) { + return true; + } + + // Sanitize values. + $file_stat['size'] = abs((int) $file_stat['size']); + + // Retrieve owner and group for the file. + [$owner, $group] = $this->posix_getpwuid($file_stat['uid'], $file_stat['gid']); + + // Generate the TAR header for this file + $chunk = $this->make_tar_headers( + $filename, + $file_stat['mode'], + $file_stat['uid'], + $file_stat['gid'], + $file_stat['size'], + $file_stat['mtime'], + 0, + $owner, + $group, + $filename_prefix + ); + + $fd = false; + if ($file_stat['size'] > 0) { + $fd = fopen($file_name, 'rb'); + + if (!is_resource($fd)) { + trigger_error( + sprintf( + esc_html__('Cannot open source file %s for archiving. Writing an empty file.', 'backwpup'), + $file_name + ), + E_USER_WARNING + ); + } + } + + if ($fd) { + // Read/write files in 512 bit Blocks. + while (($content = fread($fd, 512)) != '') { // phpcs:ignore + $chunk .= pack('a512', $content); + + if (strlen($chunk) >= $chunk_size) { + $this->fwrite($chunk); + + $chunk = ''; + } + } + fclose($fd); // phpcs:ignore + } + + if (!empty($chunk)) { + $this->fwrite($chunk); + } + + return true; + } + + /** + * Tar an empty Folder to archive. + * + * @return bool true on success, false on failure + */ + private function tar_empty_folder($folder_name, $name_in_archive) + { + if (!is_resource($this->filehandler)) { + return false; + } + + $name_in_archive = trailingslashit($name_in_archive); + + $tar_filename = $name_in_archive; + $tar_filename_prefix = ''; + + // Split filename larger than 100 chars. + if (100 < strlen($name_in_archive)) { + $filename_offset = strlen($name_in_archive) - 100; + $split_pos = strpos($name_in_archive, '/', $filename_offset); + + if ($split_pos === false) { + $split_pos = strrpos(untrailingslashit($name_in_archive), '/'); + } + + $tar_filename = substr($name_in_archive, $split_pos + 1); + $tar_filename_prefix = substr($name_in_archive, 0, $split_pos); + + if (strlen($tar_filename) > 100) { + $tar_filename = substr($tar_filename, -100); + trigger_error( + sprintf( + // translators: $1 is the name of the folder. $2 is the archive name. + esc_html__( + 'Folder name "%1$s" is too long to be saved correctly in %2$s archive!', + 'backwpup' + ), + $name_in_archive, + $this->method + ), + E_USER_WARNING + ); + } + + if (strlen($tar_filename_prefix) > 155) { + trigger_error( + sprintf( + // translators: $1 is the name of the folder. $2 is the archive name. + esc_html__( + 'Folder path "%1$s" is too long to be saved correctly in %2$s archive!', + 'backwpup' + ), + $name_in_archive, + $this->method + ), + E_USER_WARNING + ); + } + } + + $file_stat = @stat($folder_name); + // Retrieve owner and group for the file. + [$owner, $group] = $this->posix_getpwuid($file_stat['uid'], $file_stat['gid']); + + // Generate the TAR header for this file + $header = $this->make_tar_headers( + $tar_filename, + $file_stat['mode'], + $file_stat['uid'], + $file_stat['gid'], + $file_stat['size'], + $file_stat['mtime'], + 5, + $owner, + $group, + $tar_filename_prefix + ); + + $this->fwrite($header); + + return true; + } + + /** + * Check Archive File size. + * + * @param string $file_to_add THe file to check + * + * @return bool true if the file size is less than PHP_INT_MAX false otherwise + */ + public function check_archive_filesize($file_to_add = '') + { + $file_to_add_size = 0; + + if (!empty($file_to_add)) { + $file_to_add_size = filesize($file_to_add); + + if ($file_to_add_size === false) { + $file_to_add_size = 0; + } + } + + if (is_resource($this->filehandler)) { + $stats = fstat($this->filehandler); + $archive_size = $stats['size']; + } else { + $archive_size = filesize($this->file); + if ($archive_size === false) { + $archive_size = PHP_INT_MAX; + } + } + + $archive_size = $archive_size + $file_to_add_size; + if ($archive_size >= PHP_INT_MAX) { + trigger_error( + sprintf( + esc_html__( + 'If %s will be added to your backup archive, the archive will be too large for operations with this PHP Version. You might want to consider splitting the backup job in multiple jobs with less files each.', + 'backwpup' + ), + $file_to_add + ), + E_USER_ERROR + ); + + return false; + } + + return true; + } + + /** + * Make Tar Headers. + * + * @param string $name The name of the file or directory. Known as Item. + * @param string $mode the permissions for the item + * @param int $uid the owner ID + * @param int $gid the group ID + * @param int $size the size of the item + * @param int $mtime the time of the last modification + * @param int $typeflag The type of the item. 0 for File and 5 for Directory. + * @param string $owner the owner Name + * @param string $group the group Name + * @param string $prefix the item prefix + * + * @return mixed|string + */ + private function make_tar_headers($name, $mode, $uid, $gid, $size, $mtime, $typeflag, $owner, $group, $prefix) + { + // Generate the TAR header for this file + $chunk = pack( + 'a100a8a8a8a12a12a8a1a100a6a2a32a32a8a8a155a12', + $name, //name of file 100 + sprintf('%07o', $mode), //file mode 8 + sprintf('%07o', $uid), //owner user ID 8 + sprintf('%07o', $gid), //owner group ID 8 + sprintf('%011o', $size), //length of file in bytes 12 + sprintf('%011o', $mtime), //modify time of file 12 + ' ', //checksum for header 8 + $typeflag, //type of file 0 or null = File, 5=Dir + '', //name of linked file 100 + 'ustar', //USTAR indicator 6 + '00', //USTAR version 2 + $owner, //owner user name 32 + $group, //owner group name 32 + '', //device major number 8 + '', //device minor number 8 + $prefix, //prefix for file name 155 + '' + ); //fill block 12 + + // Computes the unsigned Checksum of a file's header + $checksum = 0; + + for ($i = 0; $i < 512; ++$i) { + $checksum += ord(substr($chunk, $i, 1)); + } + + $checksum = pack('a8', sprintf('%07o', $checksum)); + + return substr_replace($chunk, $checksum, 148, 8); + } + + /** + * Posix Get PW ID. + * + * @param int $uid the user ID + * @param int $gid the group ID + * + * @return array The owner and group in posix format + */ + private function posix_getpwuid($uid, $gid) + { + // Set file user/group name if linux. + $owner = esc_html__('Unknown', 'backwpup'); + $group = esc_html__('Unknown', 'backwpup'); + + if (function_exists('posix_getpwuid')) { + $info = posix_getpwuid($uid); + $owner = $info['name']; + $info = posix_getgrgid($gid); + $group = $info['name']; + } + + return [ + $owner, + $group, + ]; + } + + /** + * Fopen. + * + * @param string $filename the file to open in mode + * @param string $mode the mode to open the file + * + * @return bool|resource the resources or false if file cannot be opened + */ + private function fopen($filename, $mode) + { + $fd = fopen($filename, $mode); + + if (!$fd) { + trigger_error( + sprintf( + // translators: $1 is the filename to add into the archive. + esc_html__('Cannot open source file %s.', 'backwpup'), + $filename + ), + E_USER_WARNING + ); + } + + return $fd; + } + + /** + * Write Content in File. + * + * @param string $content the content to write into the file + * + * @return int the number of bit wrote into the file + */ + private function fwrite($content) + { + switch ($this->handlertype) { + case 'bz': + $content = bzcompress($content); + break; + + case 'gz': + $content = gzencode($content); + break; + + default: + break; + } + + return (int) fwrite($this->filehandler, $content); + } + + /** + * Close file handler. + */ + private function fclose() + { + fclose($this->filehandler); + } } diff --git a/inc/class-cron.php b/inc/class-cron.php index 3c1206fc..d4738bc1 100644 --- a/inc/class-cron.php +++ b/inc/class-cron.php @@ -1,351 +1,371 @@ 'restart' ) ); - //restart job if not working or a restart imitated - self::cron_active( array( 'run' => 'restart' ) ); - - return; - } - - $arg = is_numeric( $arg ) ? abs( (int) $arg ) : 0; - if ( ! $arg ) { - return; - } - - //check that job exits - $jobids = BackWPup_Option::get_job_ids( 'activetype', 'wpcron' ); - if ( ! in_array( $arg, $jobids, true ) ) { - return; - } - - //delay other job start for 5 minutes if already one is running - $job_object = BackWPup_Job::get_working_data(); - if ( $job_object ) { - wp_schedule_single_event( time() + 300, 'backwpup_cron', array( 'arg' => $arg ) ); - - return; - } - - //reschedule next job run - $cron_next = self::cron_next( BackWPup_Option::get( $arg, 'cron' ) ); - wp_schedule_single_event( $cron_next, 'backwpup_cron', array( 'arg' => $arg ) ); - - //start job - self::cron_active( array( - 'run' => 'cronrun', - 'jobid' => $arg, - ) ); - - } - - /** - * Check Jobs worked and Cleanup logs and so on - */ - public static function check_cleanup() { - - $job_object = BackWPup_Job::get_working_data(); - $log_folder = get_site_option( 'backwpup_cfg_logfolder' ); - $log_folder = BackWPup_File::get_absolute_path( $log_folder ); - - // check aborted jobs for longer than a tow hours, abort them courtly and send mail - if ( is_object( $job_object ) && ! empty( $job_object->logfile ) ) { - $not_worked_time = microtime( true ) - $job_object->timestamp_last_update; - if ( $not_worked_time > 3600 ) { - $job_object->log( E_USER_ERROR, - __( 'Aborted, because no progress for one hour!', 'backwpup' ), - __FILE__, - __LINE__ ); - unlink( BackWPup::get_plugin_data( 'running_file' ) ); - $job_object->update_working_data(); - } - } - - //Compress not compressed logs - if ( is_readable( $log_folder ) && function_exists( 'gzopen' ) - && get_site_option( 'backwpup_cfg_gzlogs' ) && ! is_object( $job_object ) ) { - //Compress old not compressed logs - try { - $dir = new BackWPup_Directory( $log_folder ); - - $jobids = BackWPup_Option::get_job_ids(); - foreach ( $dir as $file ) { - if ( $file->isWritable() && '.html' == substr( $file->getFilename(), - 5 ) ) { - $compress = new BackWPup_Create_Archive( $file->getPathname() . '.gz' ); - if ( $compress->add_file( $file->getPathname() ) ) { - unlink( $file->getPathname() ); - //change last logfile in jobs - foreach ( $jobids as $jobid ) { - $job_logfile = BackWPup_Option::get( $jobid, 'logfile' ); - if ( ! empty( $job_logfile ) && $job_logfile === $file->getPathname() ) { - BackWPup_Option::update( $jobid, 'logfile', $file->getPathname() . '.gz' ); - } - } - } - $compress->close(); - unset( $compress ); - } - } - } catch ( UnexpectedValueException $e ) { - $job_object->log( sprintf( __( "Could not open path: %s", 'backwpup' ), $e->getMessage() ), - E_USER_WARNING ); - } - } - - //Jobs cleanings - if ( ! $job_object ) { - //remove restart cron - wp_clear_scheduled_hook( 'backwpup_cron', array( 'arg' => 'restart' ) ); - //temp cleanup - BackWPup_Job::clean_temp_folder(); - } - - //check scheduling jobs that not found will removed because there are single scheduled - $activejobs = BackWPup_Option::get_job_ids( 'activetype', 'wpcron' ); - foreach ( $activejobs as $jobid ) { - $cron_next = wp_next_scheduled( 'backwpup_cron', array( 'arg' => $jobid ) ); - if ( ! $cron_next || $cron_next < time() ) { - wp_unschedule_event( $cron_next, 'backwpup_cron', array( 'arg' => $jobid ) ); - $cron_next = BackWPup_Cron::cron_next( BackWPup_Option::get( $jobid, 'cron' ) ); - wp_schedule_single_event( $cron_next, 'backwpup_cron', array( 'arg' => $jobid ) ); - } - } - - } - - /** - * Start job if in cron and run query args are set. - */ - public static function cron_active( $args = array() ) { - - //only if cron active - if ( ! defined( 'DOING_CRON' ) || ! DOING_CRON ) { - return; - } - - if ( ! is_array( $args ) ) { - $args = array(); - } - - if ( isset( $_GET['backwpup_run'] ) ) { - $args['run'] = sanitize_text_field( $_GET['backwpup_run'] ); - } - - if ( isset( $_GET['_nonce'] ) ) { - $args['nonce'] = sanitize_text_field( $_GET['_nonce'] ); - } - - if ( isset( $_GET['jobid'] ) ) { - $args['jobid'] = absint( $_GET['jobid'] ); - } - - $args = array_merge( array( - 'run' => '', - 'nonce' => '', - 'jobid' => 0, - ), - $args ); - - if ( ! in_array( $args['run'], - array( 'test', 'restart', 'runnow', 'runnowalt', 'runext', 'cronrun' ), - true ) ) { - return; - } - - //special header - @session_write_close(); - @header( 'Content-Type: text/html; charset=' . get_bloginfo( 'charset' ), true ); - @header( 'X-Robots-Tag: noindex, nofollow', true ); - nocache_headers(); - - //on test die for fast feedback - if ( $args['run'] === 'test' ) { - die( 'BackWPup test request' ); - } - - if ( $args['run'] === 'restart' ) { - $job_object = BackWPup_Job::get_working_data(); - // Restart if cannot find job - if ( ! $job_object ) { - BackWPup_Job::start_http( 'restart' ); - - return; - } - //restart job if not working or a restart wished - $not_worked_time = microtime( true ) - $job_object->timestamp_last_update; - if ( ! $job_object->pid || $not_worked_time > 300 ) { - BackWPup_Job::start_http( 'restart' ); - - return; - } - } - - // generate normal nonce - $nonce = substr( wp_hash( wp_nonce_tick() . 'backwpup_job_run-' . $args['run'], 'nonce' ), - 12, 10 ); - //special nonce on external start - if ( $args['run'] === 'runext' ) { - $nonce = get_site_option( 'backwpup_cfg_jobrunauthkey' ); - } - if ( $args['run'] === 'cronrun' ) { - $nonce = ''; - } - // check nonce - if ( $nonce !== $args['nonce'] ) { - return; - } - - //check runext is allowed for job - if ( $args['run'] === 'runext' ) { - $jobids_link = BackWPup_Option::get_job_ids( 'activetype', 'link' ); - $jobids_easycron = BackWPup_Option::get_job_ids( 'activetype', 'easycron' ); - $jobids_external = array_merge( $jobids_link, $jobids_easycron ); - if ( ! in_array( $args['jobid'], $jobids_external, true ) ) { - return; - } - } - - //run BackWPup job - BackWPup_Job::start_http( $args['run'], $args['jobid'] ); - } - - /** - * - * Get the local time timestamp of the next cron execution - * - * @param string $cronstring cron (* * * * *). - * - * @return int Timestamp - */ - public static function cron_next( $cronstring ) { - - $cron = array(); - $cronarray = array(); - //Cron string - list( $cronstr['minutes'], $cronstr['hours'], $cronstr['mday'], $cronstr['mon'], $cronstr['wday'] ) = explode( ' ', - trim( $cronstring ), - 5 ); - - //make arrays form string - foreach ( $cronstr as $key => $value ) { - if ( strstr( $value, ',' ) ) { - $cronarray[ $key ] = explode( ',', $value ); - } else { - $cronarray[ $key ] = array( 0 => $value ); - } - } - - //make arrays complete with ranges and steps - foreach ( $cronarray as $cronarraykey => $cronarrayvalue ) { - $cron[ $cronarraykey ] = array(); - foreach ( $cronarrayvalue as $value ) { - //steps - $step = 1; - if ( strstr( $value, '/' ) ) { - list( $value, $step ) = explode( '/', $value, 2 ); - } - //replace weekday 7 with 0 for sundays - if ( $cronarraykey === 'wday' ) { - $value = str_replace( '7', '0', $value ); - } - //ranges - if ( strstr( $value, '-' ) ) { - list( $first, $last ) = explode( '-', $value, 2 ); - if ( ! is_numeric( $first ) || ! is_numeric( $last ) || $last > 60 || $first > 60 ) { //check - return PHP_INT_MAX; - } - if ( $cronarraykey === 'minutes' && $step < 5 ) { //set step minimum to 5 min. - $step = 5; - } - $range = array(); - for ( $i = $first; $i <= $last; $i = $i + $step ) { - $range[] = $i; - } - $cron[ $cronarraykey ] = array_merge( $cron[ $cronarraykey ], $range ); - } elseif ( $value === '*' ) { - $range = array(); - if ( $cronarraykey === 'minutes' ) { - if ( $step < 10 ) { //set step minimum to 5 min. - $step = 10; - } - for ( $i = 0; $i <= 59; $i = $i + $step ) { - $range[] = $i; - } - } - if ( $cronarraykey === 'hours' ) { - for ( $i = 0; $i <= 23; $i = $i + $step ) { - $range[] = $i; - } - } - if ( $cronarraykey === 'mday' ) { - for ( $i = $step; $i <= 31; $i = $i + $step ) { - $range[] = $i; - } - } - if ( $cronarraykey === 'mon' ) { - for ( $i = $step; $i <= 12; $i = $i + $step ) { - $range[] = $i; - } - } - if ( $cronarraykey === 'wday' ) { - for ( $i = 0; $i <= 6; $i = $i + $step ) { - $range[] = $i; - } - } - $cron[ $cronarraykey ] = array_merge( $cron[ $cronarraykey ], $range ); - } else { - if ( ! is_numeric( $value ) || (int) $value > 60 ) { - return PHP_INT_MAX; - } - $cron[ $cronarraykey ] = array_merge( $cron[ $cronarraykey ], array( 0 => absint( $value ) ) ); - } - } - } - - //generate years - $year = (int) gmdate( 'Y' ); - for ( $i = $year; $i < $year + 100; $i ++ ) { - $cron['year'][] = $i; - } - - //calc next timestamp - $current_timestamp = (int) current_time( 'timestamp' ); - foreach ( $cron['year'] as $year ) { - foreach ( $cron['mon'] as $mon ) { - foreach ( $cron['mday'] as $mday ) { - if ( ! checkdate( $mon, $mday, $year ) ) { - continue; - } - foreach ( $cron['hours'] as $hours ) { - foreach ( $cron['minutes'] as $minutes ) { - $timestamp = gmmktime( $hours, $minutes, 0, $mon, $mday, $year ); - if ( $timestamp && in_array( (int) gmdate( 'j', $timestamp ), - $cron['mday'], - true ) && in_array( (int) gmdate( 'w', $timestamp ), - $cron['wday'], - true ) && $timestamp > $current_timestamp ) { - return $timestamp - ( (int) get_option( 'gmt_offset' ) * 3600 ); - } - } - } - } - } - } - - return PHP_INT_MAX; - } +class BackWPup_Cron +{ + /** + * @param string $arg + */ + public static function run($arg = 'restart') + { + if (!is_main_site(get_current_blog_id())) { + return; + } + + if ($arg === 'restart') { + //reschedule restart + wp_schedule_single_event(time() + 60, 'backwpup_cron', ['arg' => 'restart']); + //restart job if not working or a restart imitated + self::cron_active(['run' => 'restart']); + + return; + } + + $arg = is_numeric($arg) ? abs((int) $arg) : 0; + if (!$arg) { + return; + } + + //check that job exits + $jobids = BackWPup_Option::get_job_ids('activetype', 'wpcron'); + if (!in_array($arg, $jobids, true)) { + return; + } + + //delay other job start for 5 minutes if already one is running + $job_object = BackWPup_Job::get_working_data(); + if ($job_object) { + wp_schedule_single_event(time() + 300, 'backwpup_cron', ['arg' => $arg]); + + return; + } + + //reschedule next job run + $cron_next = self::cron_next(BackWPup_Option::get($arg, 'cron')); + wp_schedule_single_event($cron_next, 'backwpup_cron', ['arg' => $arg]); + + //start job + self::cron_active([ + 'run' => 'cronrun', + 'jobid' => $arg, + ]); + } + + /** + * Check Jobs worked and Cleanup logs and so on. + */ + public static function check_cleanup() + { + $job_object = BackWPup_Job::get_working_data(); + $log_folder = get_site_option('backwpup_cfg_logfolder'); + $log_folder = BackWPup_File::get_absolute_path($log_folder); + + // check aborted jobs for longer than a tow hours, abort them courtly and send mail + if (is_object($job_object) && !empty($job_object->logfile)) { + $not_worked_time = microtime(true) - $job_object->timestamp_last_update; + if ($not_worked_time > 3600) { + $job_object->log( + E_USER_ERROR, + __('Aborted, because no progress for one hour!', 'backwpup'), + __FILE__, + __LINE__ + ); + unlink(BackWPup::get_plugin_data('running_file')); + $job_object->update_working_data(); + } + } + + //Compress not compressed logs + if (is_readable($log_folder) && function_exists('gzopen') + && get_site_option('backwpup_cfg_gzlogs') && !is_object($job_object)) { + //Compress old not compressed logs + try { + $dir = new BackWPup_Directory($log_folder); + + $jobids = BackWPup_Option::get_job_ids(); + + foreach ($dir as $file) { + if ($file->isWritable() && '.html' == substr($file->getFilename(), -5)) { + $compress = new BackWPup_Create_Archive($file->getPathname() . '.gz'); + if ($compress->add_file($file->getPathname())) { + unlink($file->getPathname()); + //change last logfile in jobs + foreach ($jobids as $jobid) { + $job_logfile = BackWPup_Option::get($jobid, 'logfile'); + if (!empty($job_logfile) && $job_logfile === $file->getPathname()) { + BackWPup_Option::update($jobid, 'logfile', $file->getPathname() . '.gz'); + } + } + } + $compress->close(); + unset($compress); + } + } + } catch (UnexpectedValueException $e) { + $job_object->log( + sprintf(__('Could not open path: %s', 'backwpup'), $e->getMessage()), + E_USER_WARNING + ); + } + } + + //Jobs cleanings + if (!$job_object) { + //remove restart cron + wp_clear_scheduled_hook('backwpup_cron', ['arg' => 'restart']); + //temp cleanup + BackWPup_Job::clean_temp_folder(); + } + + //check scheduling jobs that not found will removed because there are single scheduled + $activejobs = BackWPup_Option::get_job_ids('activetype', 'wpcron'); + + foreach ($activejobs as $jobid) { + $cron_next = wp_next_scheduled('backwpup_cron', ['arg' => $jobid]); + if (!$cron_next || $cron_next < time()) { + wp_unschedule_event($cron_next, 'backwpup_cron', ['arg' => $jobid]); + $cron_next = BackWPup_Cron::cron_next(BackWPup_Option::get($jobid, 'cron')); + wp_schedule_single_event($cron_next, 'backwpup_cron', ['arg' => $jobid]); + } + } + } + + /** + * Start job if in cron and run query args are set. + */ + public static function cron_active($args = []) + { + //only if cron active + if (!defined('DOING_CRON') || !DOING_CRON) { + return; + } + + if (!is_array($args)) { + $args = []; + } + + if (isset($_GET['backwpup_run'])) { + $args['run'] = sanitize_text_field($_GET['backwpup_run']); + } + + if (isset($_GET['_nonce'])) { + $args['nonce'] = sanitize_text_field($_GET['_nonce']); + } + + if (isset($_GET['jobid'])) { + $args['jobid'] = absint($_GET['jobid']); + } + + $args = array_merge( + [ + 'run' => '', + 'nonce' => '', + 'jobid' => 0, + ], + $args + ); + + if (!in_array( + $args['run'], + ['test', 'restart', 'runnow', 'runnowalt', 'runext', 'cronrun'], + true + )) { + return; + } + + //special header + @session_write_close(); + @header('Content-Type: text/html; charset=' . get_bloginfo('charset'), true); + @header('X-Robots-Tag: noindex, nofollow', true); + nocache_headers(); + + //on test die for fast feedback + if ($args['run'] === 'test') { + exit('BackWPup test request'); + } + + if ($args['run'] === 'restart') { + $job_object = BackWPup_Job::get_working_data(); + // Restart if cannot find job + if (!$job_object) { + BackWPup_Job::start_http('restart'); + + return; + } + //restart job if not working or a restart wished + $not_worked_time = microtime(true) - $job_object->timestamp_last_update; + if (!$job_object->pid || $not_worked_time > 300) { + BackWPup_Job::start_http('restart'); + + return; + } + } + + // generate normal nonce + $nonce = substr(wp_hash(wp_nonce_tick() . 'backwpup_job_run-' . $args['run'], 'nonce'), -12, 10); + //special nonce on external start + if ($args['run'] === 'runext') { + $nonce = get_site_option('backwpup_cfg_jobrunauthkey'); + } + if ($args['run'] === 'cronrun') { + $nonce = ''; + } + // check nonce + if ($nonce !== $args['nonce']) { + return; + } + + //check runext is allowed for job + if ($args['run'] === 'runext') { + $jobids_link = BackWPup_Option::get_job_ids('activetype', 'link'); + $jobids_easycron = BackWPup_Option::get_job_ids('activetype', 'easycron'); + $jobids_external = array_merge($jobids_link, $jobids_easycron); + if (!in_array($args['jobid'], $jobids_external, true)) { + return; + } + } + + //run BackWPup job + BackWPup_Job::start_http($args['run'], $args['jobid']); + } + + /** + * Get the local time timestamp of the next cron execution. + * + * @param string $cronstring cron (* * * * *) + * + * @return int Timestamp + */ + public static function cron_next($cronstring) + { + $cronstr = []; + $cron = []; + $cronarray = []; + //Cron string + [$cronstr['minutes'], $cronstr['hours'], $cronstr['mday'], $cronstr['mon'], $cronstr['wday']] = explode( + ' ', + trim($cronstring), + 5 + ); + + //make arrays form string + foreach ($cronstr as $key => $value) { + if (strstr($value, ',')) { + $cronarray[$key] = explode(',', $value); + } else { + $cronarray[$key] = [0 => $value]; + } + } + + //make arrays complete with ranges and steps + foreach ($cronarray as $cronarraykey => $cronarrayvalue) { + $cron[$cronarraykey] = []; + + foreach ($cronarrayvalue as $value) { + //steps + $step = 1; + if (strstr($value, '/')) { + [$value, $step] = explode('/', $value, 2); + } + //replace weekday 7 with 0 for sundays + if ($cronarraykey === 'wday') { + $value = str_replace('7', '0', $value); + } + //ranges + if (strstr($value, '-')) { + [$first, $last] = explode('-', $value, 2); + if (!is_numeric($first) || !is_numeric($last) || $last > 60 || $first > 60) { //check + return PHP_INT_MAX; + } + if ($cronarraykey === 'minutes' && $step < 5) { //set step minimum to 5 min. + $step = 5; + } + $range = []; + + for ($i = $first; $i <= $last; $i = $i + $step) { + $range[] = $i; + } + $cron[$cronarraykey] = array_merge($cron[$cronarraykey], $range); + } elseif ($value === '*') { + $range = []; + if ($cronarraykey === 'minutes') { + if ($step < 10) { //set step minimum to 5 min. + $step = 10; + } + + for ($i = 0; $i <= 59; $i = $i + $step) { + $range[] = $i; + } + } + if ($cronarraykey === 'hours') { + for ($i = 0; $i <= 23; $i = $i + $step) { + $range[] = $i; + } + } + if ($cronarraykey === 'mday') { + for ($i = $step; $i <= 31; $i = $i + $step) { + $range[] = $i; + } + } + if ($cronarraykey === 'mon') { + for ($i = $step; $i <= 12; $i = $i + $step) { + $range[] = $i; + } + } + if ($cronarraykey === 'wday') { + for ($i = 0; $i <= 6; $i = $i + $step) { + $range[] = $i; + } + } + $cron[$cronarraykey] = array_merge($cron[$cronarraykey], $range); + } else { + if (!is_numeric($value) || (int) $value > 60) { + return PHP_INT_MAX; + } + $cron[$cronarraykey] = array_merge($cron[$cronarraykey], [0 => absint($value)]); + } + } + } + + //generate years + $year = (int) gmdate('Y'); + + for ($i = $year; $i < $year + 100; ++$i) { + $cron['year'][] = $i; + } + + //calc next timestamp + $current_timestamp = (int) current_time('timestamp'); + + foreach ($cron['year'] as $year) { + foreach ($cron['mon'] as $mon) { + foreach ($cron['mday'] as $mday) { + if (!checkdate($mon, $mday, $year)) { + continue; + } + + foreach ($cron['hours'] as $hours) { + foreach ($cron['minutes'] as $minutes) { + $timestamp = gmmktime($hours, $minutes, 0, $mon, $mday, $year); + if ($timestamp && in_array( + (int) gmdate('j', $timestamp), + $cron['mday'], + true + ) && in_array( + (int) gmdate('w', $timestamp), + $cron['wday'], + true + ) && $timestamp > $current_timestamp) { + return $timestamp - ((int) get_option('gmt_offset') * 3600); + } + } + } + } + } + } + + return PHP_INT_MAX; + } } diff --git a/inc/class-destination-connect-exception.php b/inc/class-destination-connect-exception.php index 39145d5f..57745b85 100644 --- a/inc/class-destination-connect-exception.php +++ b/inc/class-destination-connect-exception.php @@ -1,17 +1,15 @@ job_id = $job_id; - $this->source_file_path = $source_file_path; - $this->local_file_path = $local_file_path; - } +class BackWpUp_Destination_Downloader_Data +{ + /** + * @var int + */ + private $job_id; - /** - * @return mixed - */ - public function job_id() { + /** + * @var string + */ + private $local_file_path; - return $this->job_id; - } + /** + * @var string + */ + private $source_file_path; - /** - * Retrieve the local file path, where the backup have to be downloaded - * - * @return string - */ - public function local_file_path() { + /** + * BackWpUp_Destination_Downloader_Data constructor. + * + * @param int $job_id + * @param string $source_file_path + * @param string $local_file_path + */ + public function __construct($job_id, $source_file_path, $local_file_path) + { + $this->job_id = $job_id; + $this->source_file_path = $source_file_path; + $this->local_file_path = $local_file_path; + } - return $this->local_file_path; - } + /** + * @return mixed + */ + public function job_id() + { + return $this->job_id; + } - /** - * Retrieve the remote/source file path of the backup - * - * @return string - */ - public function source_file_path() { + /** + * Retrieve the local file path, where the backup have to be downloaded. + * + * @return string + */ + public function local_file_path() + { + return $this->local_file_path; + } - return $this->source_file_path; - } + /** + * Retrieve the remote/source file path of the backup. + * + * @return string + */ + public function source_file_path() + { + return $this->source_file_path; + } } diff --git a/inc/class-destination-downloader-factory.php b/inc/class-destination-downloader-factory.php index b2f0be6c..aeb7181e 100644 --- a/inc/class-destination-downloader-factory.php +++ b/inc/class-destination-downloader-factory.php @@ -1,57 +1,55 @@ file_download( - $job_id, - trim( sanitize_text_field( $file ) ), - trim( sanitize_text_field( $file_local ) ) - ); - } - - /** - * BackWPup_Downloader constructor - * - * @param \BackWpUp_Destination_Downloader_Data $data - * @param \BackWPup_Destination_Downloader_Interface $destination - */ - public function __construct( - BackWpUp_Destination_Downloader_Data $data, - BackWPup_Destination_Downloader_Interface $destination - ) { - - $this->data = $data; - $this->destination = $destination; - } - - /** - * @return bool - */ - public function download_by_chunks() { - - $this->ensure_user_can_download(); - - $source_file_path = $this->data->source_file_path(); - $local_file_path = $this->data->local_file_path(); - $size = $this->destination->calculate_size(); - $start_byte = 0; - $chunk_size = 2 * 1024 * 1024; - $end_byte = $start_byte + $chunk_size - 1; - - if ( $end_byte >= $size ) { - $end_byte = $size - 1; - } - - try { - while ( $end_byte <= $size ) { - $this->destination->download_chunk( $start_byte, $end_byte ); - self::send_message( - array( - 'state' => self::STATE_DOWNLOADING, - 'start_byte' => $start_byte, - 'end_byte' => $end_byte, - 'size' => $size, - 'download_percent' => round( ( $end_byte + 1 ) / $size * 100 ), - 'filename' => basename( $source_file_path ), - ) - ); - - if ( $end_byte === $size - 1 ) { - break; - } - - $start_byte = $end_byte + 1; - $end_byte = $start_byte + $chunk_size - 1; - - if ( $start_byte < $size && $end_byte >= $size ) { - $end_byte = $size - 1; - } - } - - if ( BackWPup::is_pro() ) { - $decrypter = \Inpsyde\BackWPup\Pro\Restore\Functions\restore_container( 'decrypter' ); - if ( $decrypter->maybe_decrypted( $local_file_path ) ) { - throw new DecryptException( DecryptController::STATE_NEED_DECRYPTION_KEY ); - } - } - } catch ( \Exception $e ) { - self::send_message( - array( - 'state' => self::STATE_ERROR, - 'message' => $e->getMessage(), - ), - 'log' - ); - - return false; - } - - self::send_message( array( - 'state' => self::STATE_DONE, - 'message' => esc_html__( 'Your download is being generated …', 'backwpup' ), - ) ); - - return true; - } - - /** - * Ensure user capability - */ - private function ensure_user_can_download() { - - if ( ! current_user_can( self::CAPABILITY ) ) { - wp_die(); - } - } - - /** - * @param $data - * @param string $event - */ - private static function send_message( $data, $event = 'message' ) { - - echo "event: {$event}\n"; - echo "data: " . wp_json_encode( $data ) . "\n\n"; - flush(); - } +class BackWPup_Destination_Downloader +{ + public const ARCHIVE_ENCRYPT_OPTION = 'archiveencryption'; + public const CAPABILITY = 'backwpup_backups_download'; + + public const STATE_DOWNLOADING = 'downloading'; + public const STATE_ERROR = 'error'; + public const STATE_DONE = 'done'; + + /** + * @var \BackWpUp_Destination_Downloader_Data + */ + private $data; + + /** + * @var \BackWPup_Destination_Downloader_Interface + */ + private $destination; + + /** + * BackWPup_Downloader constructor. + * + * @param \BackWpUp_Destination_Downloader_Data $data + * @param \BackWPup_Destination_Downloader_Interface $destination + */ + public function __construct( + BackWpUp_Destination_Downloader_Data $data, + BackWPup_Destination_Downloader_Interface $destination + ) { + $this->data = $data; + $this->destination = $destination; + } + + /** + * Download file via ajax. + */ + public static function download_by_ajax() + { + $dest = (string) filter_input(INPUT_GET, 'destination', FILTER_SANITIZE_STRING); + if (!$dest) { + return; + } + + $job_id = (int) filter_input(INPUT_GET, 'jobid', FILTER_SANITIZE_NUMBER_INT); + if (!$job_id) { + return; + } + + $file = (string) filter_input(INPUT_GET, 'file', FILTER_SANITIZE_STRING); + $file_local = (string) filter_input(INPUT_GET, 'local_file', FILTER_SANITIZE_STRING); + if (!$file || !$file_local) { + return; + } + + set_time_limit(0); + // Set up eventsource headers + header('Content-Type: text/event-stream'); + header('Cache-Control: no-cache'); + header('X-Accel-Buffering: no'); + header('Content-Encoding: none'); + + // 2KB padding for IE + echo ':' . str_repeat(' ', 2048) . "\n\n"; // phpcs:ignore + + // Ensure we're not buffered. + wp_ob_end_flush_all(); + flush(); + + /** @var \BackWPup_Destinations $dest_class */ + $dest_class = BackWPup::get_destination($dest); + $dest_class->file_download( + $job_id, + trim(sanitize_text_field($file)), + trim(sanitize_text_field($file_local)) + ); + } + + /** + * @return bool + */ + public function download_by_chunks() + { + $this->ensure_user_can_download(); + + $source_file_path = $this->data->source_file_path(); + $local_file_path = $this->data->local_file_path(); + $size = $this->destination->calculate_size(); + $start_byte = 0; + $chunk_size = 2 * 1024 * 1024; + $end_byte = $start_byte + $chunk_size - 1; + + if ($end_byte >= $size) { + $end_byte = $size - 1; + } + + try { + while ($end_byte <= $size) { + $this->destination->download_chunk($start_byte, $end_byte); + self::send_message( + [ + 'state' => self::STATE_DOWNLOADING, + 'start_byte' => $start_byte, + 'end_byte' => $end_byte, + 'size' => $size, + 'download_percent' => round(($end_byte + 1) / $size * 100), + 'filename' => basename($source_file_path), + ] + ); + + if ($end_byte === $size - 1) { + break; + } + + $start_byte = $end_byte + 1; + $end_byte = $start_byte + $chunk_size - 1; + + if ($start_byte < $size && $end_byte >= $size) { + $end_byte = $size - 1; + } + } + + if (BackWPup::is_pro()) { + /** @var \Inpsyde\Restore\Api\Module\Decryption\Decrypter $decrypter */ + $decrypter = restore_container('decrypter'); + if ($decrypter->isEncrypted($local_file_path)) { + throw new DecryptException(DecryptController::STATE_NEED_DECRYPTION_KEY); + } + } + } catch (Exception $e) { + self::send_message( + [ + 'state' => self::STATE_ERROR, + 'message' => $e->getMessage(), + ], + 'log' + ); + + return false; + } + + self::send_message([ + 'state' => self::STATE_DONE, + 'message' => esc_html__('Your download is being generated …', 'backwpup'), + ]); + + return true; + } + + /** + * Ensure user capability. + */ + private function ensure_user_can_download() + { + if (!current_user_can(self::CAPABILITY)) { + wp_die(); + } + } + + /** + * @param $data + * @param string $event + */ + private static function send_message($data, $event = 'message') + { + echo "event: {$event}\n"; + echo 'data: ' . wp_json_encode($data) . "\n\n"; + flush(); + } } diff --git a/inc/class-destination-dropbox-api-exception.php b/inc/class-destination-dropbox-api-exception.php index 3602804b..0af7886d 100644 --- a/inc/class-destination-dropbox-api-exception.php +++ b/inc/class-destination-dropbox-api-exception.php @@ -1,7 +1,7 @@ error = $error; + parent::__construct($message, $code, $previous); + } - public function __construct( $message, $code = 0, $previous = null, $error = null ) { - - $this->error = $error; - parent::__construct( $message, $code, $previous ); - } - - public function getError() { - - return $this->error; - } + public function getError(): ?array + { + return $this->error; + } } diff --git a/inc/class-destination-dropbox-api.php b/inc/class-destination-dropbox-api.php index 2ee846c2..9c33fb57 100755 --- a/inc/class-destination-dropbox-api.php +++ b/inc/class-destination-dropbox-api.php @@ -1,1383 +1,1403 @@ oauthAppKey = get_site_option( - 'backwpup_cfg_dropboxappkey', - base64_decode('NXdtdXl0cm5qZzB5aHhw') - ); - $this->oauthAppSecret = BackWPup_Encryption::decrypt( - get_site_option('backwpup_cfg_dropboxappsecret', base64_decode('cXYzZmp2N2IxcG1rbWxy')) - ); - } else { - $this->oauthAppKey = get_site_option( - 'backwpup_cfg_dropboxsandboxappkey', - base64_decode('a3RqeTJwdXFwZWVydW92') - ); - $this->oauthAppSecret = BackWPup_Encryption::decrypt( - get_site_option('backwpup_cfg_dropboxsandboxappsecret', base64_decode('aXJ1eDF3Ym9mMHM5eGp6')) - ); - } - - if (empty($this->oauthAppKey) || empty($this->oauthAppSecret)) { - throw new BackWPup_Destination_Dropbox_API_Exception("No App key or App Secret specified."); - } - - $this->jobObject = $jobObject; - } - - /** - * List a folder - * - * This is a functions method to use filesListFolder and - * filesListFolderContinue to construct an array of files within a given - * folder path. - * - * @param string $path - * - * @return array - */ - public function listFolder($path) - { - $files = []; - $result = $this->filesListFolder([ 'path' => $path ]); - - if (!$result) { - return []; - } - - $files = array_merge($files, $result['entries']); - - $args = [ 'cursor' => $result['cursor'] ]; - - while ($result['has_more'] == true) { - $result = $this->filesListFolderContinue($args); - $files = array_merge($files, $result['entries']); - } - - return $files; - } - - /** - * Uploads a file to Dropbox. - * - * @param $file - * @param string $path - * @param bool $overwrite - * - * @return array - * @throws BackWPup_Destination_Dropbox_API_Exception - */ - public function upload($file, $path = '', $overwrite = true) - { - $file = str_replace("\\", "/", $file); - - if (!is_readable($file)) { - throw new BackWPup_Destination_Dropbox_API_Exception( - "Error: File \"$file\" is not readable or doesn't exist." - ); - } - - if (filesize($file) < 5242880) { //chunk transfer on bigger uploads - $output = $this->filesUpload( - [ - 'contents' => file_get_contents($file), - 'path' => $path, - 'mode' => ( $overwrite ) ? 'overwrite' : 'add', - ] - ); - } else { - $output = $this->multipartUpload($file, $path, $overwrite); - } - - return $output; - } - - /** - * @param $file - * @param string $path - * @param bool $overwrite - * - * @return array|mixed|string - * @throws BackWPup_Destination_Dropbox_API_Exception - */ - public function multipartUpload($file, $path = '', $overwrite = true) - { - $file = str_replace("\\", "/", $file); - - if (!is_readable($file)) { - throw new BackWPup_Destination_Dropbox_API_Exception( - "Error: File \"$file\" is not readable or doesn't exist." - ); - } - - $chunkSize = 4194304; //4194304 = 4MB - - $fileHandle = fopen($file, 'rb'); - if (!$fileHandle) { - throw new BackWPup_Destination_Dropbox_API_Exception("Can not open source file for transfer."); - } - - if (!isset($this->jobObject->steps_data[ $this->jobObject->step_working ]['uploadid'])) { - $this->jobObject->log(__('Beginning new file upload session', 'backwpup')); - $session = $this->filesUploadSessionStart( - ); - $this->jobObject->steps_data[ $this->jobObject->step_working ]['uploadid'] = $session['session_id']; - } - if (!isset($this->jobObject->steps_data[ $this->jobObject->step_working ]['offset'])) { - $this->jobObject->steps_data[ $this->jobObject->step_working ]['offset'] = 0; - } - if (!isset($this->jobObject->steps_data[ $this->jobObject->step_working ]['totalread'])) { - $this->jobObject->steps_data[ $this->jobObject->step_working ]['totalread'] = 0; - } - - //seek to current position - if ($this->jobObject->steps_data[ $this->jobObject->step_working ]['offset'] > 0) { - fseek($fileHandle, $this->jobObject->steps_data[ $this->jobObject->step_working ]['offset']); - } - - while ($data = fread($fileHandle, $chunkSize)) { - $chunkUploadStart = microtime(true); - - if ($this->jobObject->is_debug()) { - $this->jobObject->log( - sprintf(__('Uploading %s of data', 'backwpup'), size_format(strlen($data))) - ); - } - - $this->filesUploadSessionAppendV2( - [ - 'contents' => $data, - 'cursor' => [ - 'session_id' => $this->jobObject->steps_data[ $this->jobObject->step_working ]['uploadid'], - 'offset' => $this->jobObject->steps_data[ $this->jobObject->step_working ]['offset'], - ], - ] - ); - $chunkUploadTime = microtime( - true - ) - $chunkUploadStart; - $this->jobObject->steps_data[ $this->jobObject->step_working ]['totalread'] += strlen($data); - - //args for next chunk - $this->jobObject->steps_data[ $this->jobObject->step_working ]['offset'] += $chunkSize; - if ($this->jobObject->job['backuptype'] === 'archive') { - $this->jobObject->substeps_done = $this->jobObject->steps_data[ $this->jobObject->step_working ]['offset']; - if (strlen($data) == $chunkSize) { - $timeRemaining = $this->jobObject->do_restart_time(); - //calc next chunk - if ($timeRemaining < $chunkUploadTime) { - $chunkSize = floor($chunkSize / $chunkUploadTime * ( $timeRemaining - 3 )); - if ($chunkSize < 0) { - $chunkSize = 1024; - } - if ($chunkSize > 4194304) { - $chunkSize = 4194304; - } - } - } - } - $this->jobObject->update_working_data(); - //correct position - fseek($fileHandle, $this->jobObject->steps_data[ $this->jobObject->step_working ]['offset']); - } - - fclose($fileHandle); - - $this->jobObject->log( - sprintf( - __('Finishing upload session with a total of %s uploaded', 'backwpup'), - size_format($this->jobObject->steps_data[ $this->jobObject->step_working ]['totalread']) - ) - ); - $response = $this->filesUploadSessionFinish( - [ - 'cursor' => [ - 'session_id' => $this->jobObject->steps_data[ $this->jobObject->step_working ]['uploadid'], - 'offset' => $this->jobObject->steps_data[ $this->jobObject->step_working ]['totalread'], - ], - 'commit' => [ - 'path' => $path, - 'mode' => ( $overwrite ) ? 'overwrite' : 'add', - ], - ] - ); - - unset($this->jobObject->steps_data[ $this->jobObject->step_working ]['uploadid']); - unset($this->jobObject->steps_data[ $this->jobObject->step_working ]['offset']); - - return $response; - } - - /** - * Set the oauth tokens for this request. - * - * @param array $token The array with access and refresh tokens - * @param callable $listener The callback to be called when a new token is fetched - * - * @throws BackWPup_Destination_Dropbox_API_Exception - */ - public function setOAuthTokens(array $token, $listener = null) - { - if (empty($token['access_token'])) { - throw new BackWPup_Destination_Dropbox_API_Exception( - __('No access token provided', 'backwpup') - ); - } - - if (empty($token['refresh_token'])) { - throw new BackWPup_Destination_Dropbox_API_Exception( - __('No refresh token provided. You may need to reauthenticate with Dropbox', 'backwpup') - ); - } - - $this->oauthToken = $token; - - if (isset($listener) && is_callable($listener)) { - $this->listener = $listener; - } - - if (isset($token['expires']) && time() > $token['expires']) { - $token = $this->refresh($token['refresh_token']); - $this->notifyRefresh($token); - } - } - - /** - * Get the current token array - * - * Also modifies expires_in to match how much time is left until the access token expires. - * - * @throws BadMethodCallException If tokens have not been set - * @return array The token array - */ - public function getTokens() - { - $now = time(); - $tokens = $this->oauthToken; - if (empty($tokens)) { - throw new \BadMethodCallException( - __('OAuth tokens have not been set.', 'backwpup') - ); - } - - if ($tokens['expires'] > $now) { - $tokens['expires_in'] = $tokens['expires'] - $now; - } else { - $tokens = $this->refresh($tokens['refresh_token']); - $this->notifyRefresh($tokens); - } - - return $tokens; - } - - /** - * Returns the URL to authorize the user. - * - * @return string The authorization URL - */ - public function oAuthAuthorize() - { - return self::API_WWW_URL . 'oauth2/authorize?response_type=code&client_id=' . $this->oauthAppKey . '&token_access_type=offline'; - } - - /** - * Takes the oauth code and returns the access token. - * - * @param string $code The oauth code - * - * @return array An array including the access token, account ID, expiration, and - * other information. - */ - public function oAuthToken($code) - { - $token = $this->request( - 'oauth2/token', - [ - 'code' => trim($code), - 'grant_type' => 'authorization_code', - ], - 'oauth' - ); - - $token['expires'] = time() + $token['expires_in']; - - return $token; - } - - /** - * Returns a new access token given the refresh token. - * - * @param string $refreshToken The refresh token - * - * @return array An array including the access token, account ID, expiration, and - * other information. - */ - public function refresh($refreshToken) - { - $token = $this->request( - 'oauth2/token', - [ - 'refresh_token' => trim($refreshToken), - 'grant_type' => 'refresh_token', - ], - 'oauth' - ); - - $token['expires'] = time() + $token['expires_in']; - - $this->oauthToken = array_merge($this->oauthToken, $token); - - $this->log( - __('Token has expired; new token has been obtained', 'backwpup') - ); - - return $this->oauthToken; - } - - /** - * Notifies the listener that the access token was refreshed - * - * @param array $token The new token - */ - private function notifyRefresh(array $token) - { - if (isset($this->listener)) { - call_user_func($this->listener, $token); - } - } - - /** - * Revokes the auth token. - * - * @return array - */ - public function authTokenRevoke() - { - return $this->request('auth/token/revoke'); - } - - /** - * Download - * - * @param array $args Argument for the api request. - * - * @throws BackWPup_Destination_Dropbox_API_Exception Because of a rate limit. - * @return mixed Whatever the api request returns. - */ - public function download($args, $startByte = null, $endByte = null) - { - $args['path'] = $this->formatPath($args['path']); - - if ($startByte !== null && $endByte !== null) { - return $this->request('files/download', $args, 'download', false, "{$startByte}-{$endByte}"); - } - - return $this->request('files/download', $args, 'download'); - } - - /** - * Deletes a file. - * - * @param array $args An array of arguments - * - * @return array Information on the deleted file - */ - public function filesDelete($args) - { - $args['path'] = $this->formatPath($args['path']); - - try { - return $this->request('files/delete', $args); - } catch (BackWPup_Destination_Dropbox_API_Request_Exception $e) { - $this->handleFilesDeleteError($e->getError()); - } - } - - /** - * Gets the metadata of a file. - * - * @param array $args An array of arguments - * - * @return array The file's metadata - */ - public function filesGetMetadata($args) - { - $args['path'] = $this->formatPath($args['path']); - try { - return $this->request('files/get_metadata', $args); - } catch (BackWPup_Destination_Dropbox_API_Request_Exception $e) { - $this->handleFilesGetMetadataError($e->getError()); - } - } - - /** - * Gets a temporary link from Dropbox to access the file. - * - * @param array $args An array of arguments - * - * @return array Information on the file and link - */ - public function filesGetTemporaryLink($args) - { - $args['path'] = $this->formatPath($args['path']); - try { - return $this->request('files/get_temporary_link', $args); - } catch (BackWPup_Destination_Dropbox_API_Request_Exception $e) { - $this->handleFilesGetTemporaryLinkError($e->getError()); - } - } - - /** - * Lists all the files within a folder. - * - * @param array $args An array of arguments - * - * @return array A list of files - */ - public function filesListFolder($args) - { - $args['path'] = $this->formatPath($args['path']); - try { - return $this->request('files/list_folder', $args); - } catch (BackWPup_Destination_Dropbox_API_Request_Exception $e) { - $this->handleFilesListFolderError($e->getError()); - } - } - - /** - * Continue to list more files. - * - * When a folder has a lot of files, the API won't return all at once. - * So this method is to fetch more of them. - * - * @param array $args An array of arguments - * - * @return array An array of files - */ - public function filesListFolderContinue($args) - { - try { - return $this->request('files/list_folder/continue', $args); - } catch (BackWPup_Destination_Dropbox_API_Request_Exception $e) { - $this->handleFilesListFolderContinueError($e->getError()); - } - } - - /** - * Uploads a file to Dropbox. - * - * The file must be no greater than 150 MB. - * - * @param array $args An array of arguments - * - * @return array The uploaded file's information. - */ - public function filesUpload($args) - { - $args['path'] = $this->formatPath($args['path']); - - if (isset($args['client_modified']) - && $args['client_modified'] instanceof DateTime - ) { - $args['client_modified'] = $args['client_modified']->format('Y-m-d\TH:m:s\Z'); - } - - try { - return $this->request('files/upload', $args, 'upload'); - } catch (BackWPup_Destination_Dropbox_API_Request_Exception $e) { - $this->handleFilesUploadError($e->getError()); - } - } - - /** - * Append more data to an uploading file - * - * @param array $args An array of arguments - */ - public function filesUploadSessionAppendV2($args) - { - try { - return $this->request( - 'files/upload_session/append_v2', - $args, - 'upload' - ); - } catch (BackWPup_Destination_Dropbox_API_Request_Exception $e) { - $error = $e->getError(); - - // See if we can fix the error first - if ($error['.tag'] === 'incorrect_offset') { - $args['cursor']['offset'] = $error['correct_offset']; - - return $this->request( - 'files/upload_session/append_v2', - $args, - 'upload' - ); - } - - // Otherwise, can't fix - $this->handleFilesUploadSessionLookupError($error); - } - } - - /** - * Finish an upload session. - * - * @param array $args - * - * @return array Information on the uploaded file - */ - public function filesUploadSessionFinish($args) - { - $args['commit']['path'] = $this->formatPath($args['commit']['path']); - - try { - return $this->request('files/upload_session/finish', $args, 'upload'); - } catch (BackWPup_Destination_Dropbox_API_Request_Exception $e) { - $error = $e->getError(); - if ($error['.tag'] === 'lookup_failed') { - if ($error['lookup_failed']['.tag'] === 'incorrect_offset') { - $args['cursor']['offset'] = $error['lookup_failed']['correct_offset']; - - return $this->request('files/upload_session/finish', $args, 'upload'); - } - } - $this->handleFilesUploadSessionFinishError($e->getError()); - } - } - - /** - * Starts an upload session. - * - * When a file larger than 150 MB needs to be uploaded, then this API - * endpoint is used to start a session to allow the file to be uploaded in - * chunks. - * - * @param array $args - * - * @return array An array containing the session's ID. - */ - public function filesUploadSessionStart($args = []) - { - return $this->request('files/upload_session/start', $args, 'upload'); - } - - /** - * Get user's current account info. - * - * @return array - */ - public function usersGetCurrentAccount() - { - return $this->request('users/get_current_account'); - } - - /** - * Get quota info for this user. - * - * @return array - */ - public function usersGetSpaceUsage() - { - return $this->request('users/get_space_usage'); - } - - /** - * Get the user agent - * - * If no user agent has been provided, defaults to `BackWPup::get_plugin_data('User-Agent')`. - * - * @return string The user agent - */ - public function getUserAgent() - { - return $this->userAgent ?: \BackWPup::get_plugin_data('User-Agent'); - } - - /** - * Set the user agent - * - * @param string $userAgent - */ - public function setUserAgent($userAgent) - { - $this->userAgent = $userAgent; - } - - /** - * Get the SSL ca-bundle path - * - * If no ca-bundle has been provided, defaults to `BackWPup::get_plugin_data('cacert')`. - * - * @return string The SSL ca-bundle - */ - public function getCaBundle() - { - return $this->caBundle ?: \BackWPup::get_plugin_data('cacert'); - } - - /** - * Set the path to the SSL ca-bundle - * - * @param string $caBundle The path to the ca-bundle file - */ - public function setCaBundle($caBundle) - { - $this->caBundle = $caBundle; - } - - /** - * Set the job object - * - * @param BackWPup_Job $jobObject The job object to set - */ - public function setJobObject(BackWPup_Job $jobObject) - { - $this->jobObject = $jobObject; - } - - /** - * Logs a message to the current job - * - * @param string $message The message to log - * @param int $level The log level - * - * @return bool|null True on success, null if no job object set - */ - protected function log($message, $level = E_USER_NOTICE) - { - if (!isset($this->jobObject)) { - return null; - } - - return $this->jobObject->log($message, $level); - } - - /** - * Logs debug info about the current request - * - * @param string $endpoint The current request endpoint - * @param array $args The request args - * - * @return bool|null True on success, null if no job object set or debug is not enabled - */ - protected function logRequest($endpoint, array $args) - { - if (!isset($this->jobObject) || !$this->jobObject->is_debug()) { - return null; - } - - $message = "Call to $endpoint"; - - if (isset($args['contents'])) { - $message .= ' with ' . size_format(strlen($args['contents'])) . ' of data,'; - unset($args['contents']); - } - - if (!empty($args)) { - $message .= ' with parameters ' . json_encode($args); - } - - return $this->log($message); - } - - /** - * Request - * - * @param string $url - * @param array $args - * @param string $endpointFormat - * @param bool $echo - * @param string $bytes - * - * @throws BackWPup_Destination_Dropbox_API_Exception - * - * @return array|mixed|string - */ - public function request($endpoint, $args = [], $endpointFormat = 'rpc', $echo = false, $bytes = null) - { - // Log request - $this->logRequest($endpoint, $args); - - if ($bytes !== null) { - $args['bytes'] = $bytes; - } - - $request = $this->buildRequest($endpoint, $args, $endpointFormat); - $client = $this->createClient(); - - $response = $client->sendRequest($request); - - if ($response->getStatusCode() >= 500) { - $this->handleServerException($response); - } elseif ($response->getStatusCode() >= 400) { - $this->handleRequestException($response); - - // If we're still here, then recurse - return $this->request($endpoint, $args, $endpointFormat, $echo, $bytes); - } - - if ($echo === true) { - echo $response->getBody(); // phpcs:ignore - } - - if ($response->getHeaderLine('Content-Type') === 'application/json') { - return json_decode($response->getBody(), true); - } else { - return $response->getBody()->getContents(); - } - } - - /** - * Gets the full URL to the Dropbox endpoint - * - * @param string $endpoint The API endpoint - * @param string $format The endpoint Format - * - * @return string The full URL - */ - private function getUrl($endpoint, $format) - { - switch ($format) { - case 'oauth': - $url = self::API_URL . $endpoint; - break; - - case 'rpc': - $url = self::API_URL . self::API_VERSION_URL . $endpoint; - break; - - case 'upload': - case 'download': - $url = self::API_CONTENT_URL . self::API_VERSION_URL . $endpoint; - break; - } - - return $url; - } - - /** - * Builds the options for the request - * - * @param string $endpoint The endpoint to call - * @param array $args The arguments for the request - * @param string $format The endpoint format - * - * @return \Psr\Http\Message\RequestInterface The HTTP request - */ - private function buildRequest($endpoint, array &$args, $format) - { - $url = $this->getUrl($endpoint, $format); - - $request = $this->createRequestFactory() - ->createRequest('POST', $url); - - if ($format !== 'oauth') { - $request = new AuthorizationRequest($request); - $request = $request->withOAuthToken($this->getTokens()['access_token']); - } - - $streamFactory = new StreamFactory(); - - switch ($format) { - case 'oauth': - $request = new AuthorizationRequest(new FormRequest($request, $streamFactory)); - $request = $request - ->withBasicAuth($this->oauthAppKey, $this->oauthAppSecret) - ->withFormParams($args) - ->withHeader('Accept', 'application/json'); - break; - - case 'rpc': - $request = new JsonRequest($request, $streamFactory); - $request = $request - ->withJsonData($args ?: null) - ->withHeader('Accept', 'application/json'); - break; - - case 'upload': - if (isset($args['contents'])) { - $stream = $streamFactory->createStream($args['contents']); - $request = $request->withBody($stream); - unset($args['contents']); - } - - $request = $request - ->withHeader('Content-Type', 'application/octet-stream') - ->withHeader('Dropbox-API-Arg', json_encode($args, JSON_FORCE_OBJECT)); - break; - - case 'download': - if (isset($args['bytes'])) { - $request = $request - ->withHeader('Range', 'bytes=' . $args['bytes']); - unset($args['bytes']); - } - - $request = $request - ->withHeader('Content-Type', 'text/plain') - ->withHeader('Accept', 'application/octet-stream') - ->withHeader('Dropbox-API-Arg', json_encode($args, JSON_FORCE_OBJECT)); - break; - } - - return $request; - } - - /** - * Handle request exception - * - * Called for 4xx responses. - * - * @param \Psr\Http\Message\ResponseInterface $response The returned response - * - * @throws BackWPup_Destination_Dropbox_API_Exception If the error cannot be handled - * @throws BackWPup_Destination_Dropbox_API_Request_Exception For endpoint-specific errors - * to be passed up the chain - */ - private function handleRequestException(ResponseInterface $response) - { - switch ($response->getStatusCode()) { - case 400: - case 401: - case 403: - case 409: - case 429: - $callback = [$this, 'handle' . $response->getStatusCode() . 'Error']; - call_user_func($callback, $response); - break; - - default: - throw new BackWPup_Destination_Dropbox_API_Exception( - sprintf( - __( - '(%1$s) An unknown error has occurred. Response from server: %2$s', - 'backwpup' - ), - $response->getStatusCode(), - $response->getBody()->getContents() - ) - ); - } - } - - /** - * Handle server exception - * - * Called for 5xx responses. - * - * @param \Psr\Http\Message\ResponseInterface $response The returned response - * - * @throws BackWPup_Destination_Dropbox_API_Exception - */ - protected function handleServerException(ResponseInterface $response) - { - throw new BackWPup_Destination_Dropbox_API_Exception( - sprintf( - __( - '(%1$d) An unexpected server error was encountered. Response from server: %2$s', - 'backwpup' - ), - $response->getStatusCode(), - $response->getBody()->getContents() - ) - ); - } - - /** - * Handle 400 response error - * - * @param \Psr\Http\Message\ResponseInterface The returned response - * - * @throws BackWPup_Destination_Dropbox_API_Exception - */ - protected function handle400Error(ResponseInterface $response) - { - throw new BackWPup_Destination_Dropbox_API_Exception( - sprintf( - __( - '(400) Bad input parameter. Response from server: %s', - 'backwpup' - ), - $response->getBody()->getContents() - ) - ); - } - - /** - * Handle 401 response error - * - * @param \Psr\Http\Message\ResponseInterface The returned response - * - * @throws BackWPup_Destination_Dropbox_API_Exception If token is invalid - */ - protected function handle401Error(ResponseInterface $response) - { - $error = json_decode($response->getBody()->getContents(), true); - if ($error['error']['.tag'] === 'expired_access_token') { - $this->refresh($this->oauthToken['refresh_token']); - } else { - throw new BackWPup_Destination_Dropbox_API_Exception( - sprintf( - __( - '(401) Bad or expired token. Response from server: %s', - 'backwpup' - ), - $error['error']['.tag'] - ) - ); - } - } - - /** - * Handle 403 response error - * - * @param \Psr\Http\Message\ResponseInterface The returned response - * - * @throws BackWPup_Destination_Dropbox_API_Exception - */ - protected function handle403Error(ResponseInterface $response) - { - $error = json_decode($response->getBody(), true); - - if ($error['error']['.tag'] === 'invalid_account_type') { - // InvalidAccountTypeError - if ($error['error']['invalid_account_type']['.tag'] === 'endpoint') { - throw new BackWPup_Destination_Dropbox_API_Exception( - __( - '(403) You do not have permission to access this endpoint.', - 'backwpup' - ) - ); - } elseif ($error['error']['invalid_account_type']['.tag'] === 'feature') { - throw new BackWPup_Destination_Dropbox_API_Exception( - __( - '(403) You do not have permission to access this feature.', - 'backwpup' - ) - ); - } - } - - // Catch all - throw new BackWPup_Destination_Dropbox_API_Exception( - sprintf( - __( - '(403) You do not have permission to access this resource. Response from server: %s', - 'backwpup' - ), - $error['error_summary'] - ) - ); - } - - /** - * Handle 409 response error - * - * @param \Psr\Http\Message\ResponseInterface The returned response - * - * @throws BackWPup_Destination_Dropbox_API_Request_Exception - */ - protected function handle409Error(ResponseInterface $response) - { - $error = json_decode($response->getBody(), true); - - throw new BackWPup_Destination_Dropbox_API_Request_Exception( - sprintf( - __( - '(409) Endpoint-specific error. Response from server: %s', - 'backwpup' - ), - $error['error_summary'] - ), - $response->getStatusCode(), - null, - $error['error'] - ); - } - - /** - * Handle 429 response error - * - * This error is encountered when requests are being rate limited. - * - * @param \Psr\Http\Message\ResponseInterface The returned response - * - * @throws BackWPup_Destination_Dropbox_API_Exception If unable to detect time to wait - */ - protected function handle429Error(ResponseInterface $response) - { - if (!$response->hasHeader('Retry-After')) { - throw new BackWPup_Destination_Dropbox_API_Exception( - __( - '(429) Requests are being rate limited. Please try again later.', - 'backwpup' - ) - ); - } else { - sleep(intval($response->getHeaderLine('Retry-After'))); - } - } - - /** - * Creates a new HTTP client - * - * @return \Http\Client\HttpClient - */ - protected function createClient() - { - $options = []; - if (empty($this->getCaBundle())) { - $options['sslverify'] = false; - } else { - $options += [ - 'sslverify' => true, - 'sslcertificates' => $this->getCaBundle(), - ]; - } - - if (!empty($this->getUserAgent())) { - $options['user-agent'] = $this->getUserAgent(); - } - - return new WpHttpClient(new ResponseFactory(), new StreamFactory(), $options); - } - - /** - * Creates a request factory for creating requests - * - * @return \Http\Message\RequestFactory - */ - protected function createRequestFactory() - { - return new RequestFactory(); - } - - /** - * Formats a path to be valid for Dropbox. - * - * @param string $path - * - * @return string The formatted path - */ - private function formatPath($path) - { - if (!empty($path) && substr($path, 0, 1) !== '/') { - $path = '/' . rtrim($path, '/'); - } elseif ($path === '/') { - $path = ''; - } - - return $path; - } - - // Error Handlers - - private function handleFilesDeleteError($error) - { - switch ($error['.tag']) { - case 'path_lookup': - $this->handleFilesLookupError($error['path_lookup']); - break; - - case 'path_write': - $this->handleFilesWriteError($error['path_write']); - break; - - case 'other': - trigger_error('Could not delete file.', E_USER_WARNING); - break; - } - } - - private function handleFilesGetMetadataError($error) - { - switch ($error['.tag']) { - case 'path': - $this->handleFilesLookupError($error['path']); - break; - - case 'other': - trigger_error('Cannot look up file metadata.', E_USER_WARNING); - break; - } - } - - private function handleFilesGetTemporaryLinkError($error) - { - switch ($error['.tag']) { - case 'path': - $this->handleFilesLookupError($error['path']); - break; - - case 'other': - trigger_error('Cannot get temporary link.', E_USER_WARNING); - break; - } - } - - private function handleFilesListFolderError($error) - { - switch ($error['.tag']) { - case 'path': - $this->handleFilesLookupError($error['path']); - break; - - case 'other': - trigger_error('Cannot list files in folder.', E_USER_WARNING); - break; - } - } - - private function handleFilesListFolderContinueError($error) - { - switch ($error['.tag']) { - case 'path': - $this->handleFilesLookupError($error['path']); - break; - - case 'reset': - trigger_error('This cursor has been invalidated.', E_USER_WARNING); - break; - - case 'other': - trigger_error('Cannot list files in folder.', E_USER_WARNING); - break; - } - } - - private function handleFilesLookupError($error) - { - switch ($error['.tag']) { - case 'malformed_path': - trigger_error('The path was malformed.', E_USER_WARNING); - break; - - case 'not_found': - trigger_error('File could not be found.', E_USER_WARNING); - break; - - case 'not_file': - trigger_error('That is not a file.', E_USER_WARNING); - break; - - case 'not_folder': - trigger_error('That is not a folder.', E_USER_WARNING); - break; - - case 'restricted_content': - trigger_error('This content is restricted.', E_USER_WARNING); - break; - - case 'invalid_path_root': - trigger_error('Path root is invalid.', E_USER_WARNING); - break; - - case 'other': - trigger_error('File could not be found.', E_USER_WARNING); - break; - } - } - - private function handleFilesUploadSessionFinishError($error) - { - switch ($error['.tag']) { - case 'lookup_failed': - $this->handleFilesUploadSessionLookupError( - $error['lookup_failed'] - ); - break; - - case 'path': - $this->handleFilesWriteError($error['path']); - break; - - case 'too_many_shared_folder_targets': - trigger_error('Too many shared folder targets.', E_USER_WARNING); - break; - - case 'other': - trigger_error('The file could not be uploaded.', E_USER_WARNING); - break; - } - } - - private function handleFilesUploadSessionLookupError($error) - { - switch ($error['.tag']) { - case 'not_found': - trigger_error('Session not found.', E_USER_WARNING); - break; - - case 'incorrect_offset': - trigger_error( - 'Incorrect offset given. Correct offset is ' . - intval($error['correct_offset']) . '.', - E_USER_WARNING - ); - break; - - case 'closed': - trigger_error( - 'This session has been closed already.', - E_USER_WARNING - ); - break; - - case 'not_closed': - trigger_error('This session is not closed.', E_USER_WARNING); - break; - - case 'other': - trigger_error( - 'Could not look up the file session.', - E_USER_WARNING - ); - break; - } - } - - private function handleFilesUploadError($error) - { - switch ($error['.tag']) { - case 'path': - $this->handleFilesUploadWriteFailed($error['path']); - break; - - case 'other': - trigger_error('There was an unknown error when uploading the file.', E_USER_WARNING); - break; - } - } - - private function handleFilesUploadWriteFailed($error) - { - $this->handleFilesWriteError($error['reason']); - } - - private function handleFilesWriteError($error) - { - $message = ''; - - // Type of error - switch ($error['.tag']) { - case 'malformed_path': - $message = 'The path was malformed.'; - break; - - case 'conflict': - $message = 'Cannot write to the target path due to conflict.'; - break; - - case 'no_write_permission': - $message = 'You do not have permission to save to this location.'; - break; - - case 'insufficient_space': - $message = 'You do not have enough space in your Dropbox.'; - break; - - case 'disallowed_name': - $message = 'The given name is disallowed by Dropbox.'; - break; - - case 'team_folder': - $message = 'Unable to modify team folders.'; - break; - - case 'other': - $message = 'There was an unknown error when uploading the file.'; - break; - } - - trigger_error($message, E_USER_WARNING); // phpcs:ignore - } + /** + * URL to Dropbox API endpoint. + */ + public const API_URL = 'https://api.dropboxapi.com/'; + + /** + * URL to Dropbox content endpoint. + */ + public const API_CONTENT_URL = 'https://content.dropboxapi.com/'; + + /** + * URL to Dropbox for authentication. + */ + public const API_WWW_URL = 'https://www.dropbox.com/'; + + /** + * API version. + */ + public const API_VERSION_URL = '2/'; + + /** + * oAuth vars. + * + * @var string + */ + private $oauthAppKey = ''; + + /** + * @var string + */ + private $oauthAppSecret = ''; + + /** + * @var string + */ + private $oauthToken = []; + + /** + * Job object for logging. + * + * @var BackWPup_Job + */ + private $jobObject; + + /** + * Callback to call when token is refreshed. + * + * @var callable + */ + private $listener; + + /** + * The user agent to use in Dropbox requests. + * + * @var string + */ + private $userAgent; + + /** + * A path to the SSL ca-bundle file to use in Dropbox requests. + * + * @var string + */ + private $caBundle; + + /** + * @param string $boxtype + * + * @throws BackWPup_Destination_Dropbox_API_Exception + */ + public function __construct($boxtype = 'dropbox', BackWPup_Job $jobObject = null) + { + if ($boxtype === 'dropbox') { + $this->oauthAppKey = get_site_option( + 'backwpup_cfg_dropboxappkey', + base64_decode('NXdtdXl0cm5qZzB5aHhw') + ); + $this->oauthAppSecret = BackWPup_Encryption::decrypt( + get_site_option('backwpup_cfg_dropboxappsecret', base64_decode('cXYzZmp2N2IxcG1rbWxy')) + ); + } else { + $this->oauthAppKey = get_site_option( + 'backwpup_cfg_dropboxsandboxappkey', + base64_decode('a3RqeTJwdXFwZWVydW92') + ); + $this->oauthAppSecret = BackWPup_Encryption::decrypt( + get_site_option('backwpup_cfg_dropboxsandboxappsecret', base64_decode('aXJ1eDF3Ym9mMHM5eGp6')) + ); + } + + if (empty($this->oauthAppKey) || empty($this->oauthAppSecret)) { + throw new BackWPup_Destination_Dropbox_API_Exception('No App key or App Secret specified.'); + } + + $this->jobObject = $jobObject; + } + + /** + * List a folder. + * + * This is a functions method to use filesListFolder and + * filesListFolderContinue to construct an array of files within a given + * folder path. + * + * @param string $path + * + * @return array + */ + public function listFolder($path) + { + $files = []; + $result = $this->filesListFolder(['path' => $path]); + + if (!$result) { + return []; + } + + $files = array_merge($files, $result['entries']); + + $args = ['cursor' => $result['cursor']]; + + while ($result['has_more'] === true) { + $result = $this->filesListFolderContinue($args); + $files = array_merge($files, $result['entries']); + $args['cursor'] = $result['cursor']; + } + + return $files; + } + + /** + * Uploads a file to Dropbox. + * + * @throws BackWPup_Destination_Dropbox_API_Exception + * + * @return array + */ + public function upload(string $file, string $path = '', bool $overwrite = true) + { + $file = str_replace('\\', '/', $file); + + if (!is_readable($file)) { + throw new BackWPup_Destination_Dropbox_API_Exception( + "Error: File \"{$file}\" is not readable or doesn't exist." + ); + } + + if (filesize($file) < 5242880) { //chunk transfer on bigger uploads + return $this->filesUpload( + [ + 'contents' => file_get_contents($file), + 'path' => $path, + 'mode' => ($overwrite) ? 'overwrite' : 'add', + ] + ); + } + + return $this->multipartUpload($file, $path, $overwrite); + } + + /** + * @param $file + * @param string $path + * @param bool $overwrite + * + * @throws BackWPup_Destination_Dropbox_API_Exception + * + * @return array|mixed|string + */ + public function multipartUpload($file, $path = '', $overwrite = true) + { + $file = str_replace('\\', '/', $file); + + if (!is_readable($file)) { + throw new BackWPup_Destination_Dropbox_API_Exception( + "Error: File \"{$file}\" is not readable or doesn't exist." + ); + } + + $chunkSize = 4194304; //4194304 = 4MB + + $fileHandle = fopen($file, 'rb'); + if (!$fileHandle) { + throw new BackWPup_Destination_Dropbox_API_Exception('Can not open source file for transfer.'); + } + + if (!isset($this->jobObject->steps_data[$this->jobObject->step_working]['uploadid'])) { + $this->jobObject->log(__('Beginning new file upload session', 'backwpup')); + $session = $this->filesUploadSessionStart( + ); + $this->jobObject->steps_data[$this->jobObject->step_working]['uploadid'] = $session['session_id']; + } + if (!isset($this->jobObject->steps_data[$this->jobObject->step_working]['offset'])) { + $this->jobObject->steps_data[$this->jobObject->step_working]['offset'] = 0; + } + if (!isset($this->jobObject->steps_data[$this->jobObject->step_working]['totalread'])) { + $this->jobObject->steps_data[$this->jobObject->step_working]['totalread'] = 0; + } + + //seek to current position + if ($this->jobObject->steps_data[$this->jobObject->step_working]['offset'] > 0) { + fseek($fileHandle, $this->jobObject->steps_data[$this->jobObject->step_working]['offset']); + } + + while ($data = fread($fileHandle, $chunkSize)) { + $chunkUploadStart = microtime(true); + + if ($this->jobObject->is_debug()) { + $this->jobObject->log( + sprintf(__('Uploading %s of data', 'backwpup'), size_format(strlen($data))) + ); + } + + $this->filesUploadSessionAppendV2( + [ + 'contents' => $data, + 'cursor' => [ + 'session_id' => $this->jobObject->steps_data[$this->jobObject->step_working]['uploadid'], + 'offset' => $this->jobObject->steps_data[$this->jobObject->step_working]['offset'], + ], + ] + ); + $chunkUploadTime = microtime( + true + ) - $chunkUploadStart; + $this->jobObject->steps_data[$this->jobObject->step_working]['totalread'] += strlen($data); + + //args for next chunk + $this->jobObject->steps_data[$this->jobObject->step_working]['offset'] += $chunkSize; + if ($this->jobObject->job['backuptype'] === 'archive') { + $this->jobObject->substeps_done = $this->jobObject->steps_data[$this->jobObject->step_working]['offset']; + if (strlen($data) == $chunkSize) { + $timeRemaining = $this->jobObject->do_restart_time(); + //calc next chunk + if ($timeRemaining < $chunkUploadTime) { + $chunkSize = floor($chunkSize / $chunkUploadTime * ($timeRemaining - 3)); + if ($chunkSize < 0) { + $chunkSize = 1024; + } + if ($chunkSize > 4194304) { + $chunkSize = 4194304; + } + } + } + } + $this->jobObject->update_working_data(); + //correct position + fseek($fileHandle, $this->jobObject->steps_data[$this->jobObject->step_working]['offset']); + } + + fclose($fileHandle); + + $this->jobObject->log( + sprintf( + __('Finishing upload session with a total of %s uploaded', 'backwpup'), + size_format($this->jobObject->steps_data[$this->jobObject->step_working]['totalread']) + ) + ); + + $response = $this->filesUploadSessionFinish( + [ + 'cursor' => [ + 'session_id' => $this->jobObject->steps_data[$this->jobObject->step_working]['uploadid'], + 'offset' => $this->jobObject->steps_data[$this->jobObject->step_working]['totalread'], + ], + 'commit' => [ + 'path' => $path, + 'mode' => ($overwrite) ? 'overwrite' : 'add', + ], + ] + ); + + unset($this->jobObject->steps_data[$this->jobObject->step_working]['uploadid'], $this->jobObject->steps_data[$this->jobObject->step_working]['offset']); + + return $response; + } + + /** + * Set the oauth tokens for this request. + * + * @param array $token The array with access and refresh tokens + * @param callable $listener The callback to be called when a new token is fetched + * + * @throws BackWPup_Destination_Dropbox_API_Exception + */ + public function setOAuthTokens(array $token, $listener = null) + { + if (empty($token['access_token'])) { + throw new BackWPup_Destination_Dropbox_API_Exception( + __('No access token provided', 'backwpup') + ); + } + + if (empty($token['refresh_token'])) { + throw new BackWPup_Destination_Dropbox_API_Exception( + __('No refresh token provided. You may need to reauthenticate with Dropbox', 'backwpup') + ); + } + + $this->oauthToken = $token; + + if (isset($listener) && is_callable($listener)) { + $this->listener = $listener; + } + + if (isset($token['expires']) && time() > $token['expires']) { + $token = $this->refresh($token['refresh_token']); + $this->notifyRefresh($token); + } + } + + /** + * Get the current token array. + * + * Also modifies expires_in to match how much time is left until the access token expires. + * + * @throws BadMethodCallException If tokens have not been set + * + * @return array The token array + */ + public function getTokens() + { + $now = time(); + $tokens = $this->oauthToken; + if (empty($tokens)) { + throw new \BadMethodCallException( + __('OAuth tokens have not been set.', 'backwpup') + ); + } + + if ($tokens['expires'] > $now) { + $tokens['expires_in'] = $tokens['expires'] - $now; + } else { + $tokens = $this->refresh($tokens['refresh_token']); + $this->notifyRefresh($tokens); + } + + return $tokens; + } + + /** + * Returns the URL to authorize the user. + * + * @return string The authorization URL + */ + public function oAuthAuthorize() + { + return self::API_WWW_URL . 'oauth2/authorize?response_type=code&client_id=' . $this->oauthAppKey . '&token_access_type=offline'; + } + + /** + * Takes the oauth code and returns the access token. + * + * @param string $code The oauth code + * + * @return array an array including the access token, account ID, expiration, and + * other information + */ + public function oAuthToken($code) + { + $token = $this->request( + 'oauth2/token', + [ + 'code' => trim($code), + 'grant_type' => 'authorization_code', + ], + 'oauth' + ); + + $token['expires'] = time() + $token['expires_in']; + + return $token; + } + + /** + * Returns a new access token given the refresh token. + * + * @param string $refreshToken The refresh token + * + * @return array an array including the access token, account ID, expiration, and + * other information + */ + public function refresh($refreshToken) + { + $token = $this->request( + 'oauth2/token', + [ + 'refresh_token' => trim($refreshToken), + 'grant_type' => 'refresh_token', + ], + 'oauth' + ); + + $token['expires'] = time() + $token['expires_in']; + + $this->oauthToken = array_merge($this->oauthToken, $token); + + return $this->oauthToken; + } + + /** + * Notifies the listener that the access token was refreshed. + * + * @param array $token The new token + */ + private function notifyRefresh(array $token) + { + if (isset($this->listener)) { + call_user_func($this->listener, $token); + } + } + + /** + * Revokes the auth token. + * + * @return array + */ + public function authTokenRevoke() + { + return $this->request('auth/token/revoke'); + } + + /** + * Download. + * + * @param array $args argument for the api request + * + * @throws BackWPup_Destination_Dropbox_API_Exception because of a rate limit + * + * @return mixed whatever the api request returns + */ + public function download($args, $startByte = null, $endByte = null) + { + $args['path'] = $this->formatPath($args['path']); + + if ($startByte !== null && $endByte !== null) { + return $this->request('files/download', $args, 'download', false, "{$startByte}-{$endByte}"); + } + + return $this->request('files/download', $args, 'download'); + } + + /** + * Deletes a file. + * + * @param array $args An array of arguments + * + * @return array|null Information on the deleted file + */ + public function filesDelete($args): ?array + { + $args['path'] = $this->formatPath($args['path']); + + try { + return $this->request('files/delete', $args); + } catch (BackWPup_Destination_Dropbox_API_Request_Exception $e) { + $this->handleFilesDeleteError($e->getError()); + + return null; + } + } + + /** + * Gets the metadata of a file. + * + * @param array $args An array of arguments + * + * @return array|null The file's metadata + */ + public function filesGetMetadata($args): ?array + { + $args['path'] = $this->formatPath($args['path']); + + try { + return $this->request('files/get_metadata', $args); + } catch (BackWPup_Destination_Dropbox_API_Request_Exception $e) { + $this->handleFilesGetMetadataError($e->getError()); + + return null; + } + } + + /** + * Gets a temporary link from Dropbox to access the file. + * + * @param array $args An array of arguments + * + * @return array|null Information on the file and link + */ + public function filesGetTemporaryLink($args): ?array + { + $args['path'] = $this->formatPath($args['path']); + + try { + return $this->request('files/get_temporary_link', $args); + } catch (BackWPup_Destination_Dropbox_API_Request_Exception $e) { + $this->handleFilesGetTemporaryLinkError($e->getError()); + + return null; + } + } + + /** + * Lists all the files within a folder. + * + * @param array $args An array of arguments + * + * @return array|null A list of files + */ + public function filesListFolder($args): ?array + { + $args['path'] = $this->formatPath($args['path']); + + try { + return $this->request('files/list_folder', $args); + } catch (BackWPup_Destination_Dropbox_API_Request_Exception $e) { + $this->handleFilesListFolderError($e->getError()); + + return null; + } + } + + /** + * Continue to list more files. + * + * When a folder has a lot of files, the API won't return all at once. + * So this method is to fetch more of them. + * + * @param array $args An array of arguments + * + * @return array|null An array of files + */ + public function filesListFolderContinue($args): ?array + { + try { + return $this->request('files/list_folder/continue', $args); + } catch (BackWPup_Destination_Dropbox_API_Request_Exception $e) { + $this->handleFilesListFolderContinueError($e->getError()); + + return null; + } + } + + /** + * Uploads a file to Dropbox. + * + * The file must be no greater than 150 MB. + * + * @param array $args An array of arguments + * + * @return array|null the uploaded file's information + */ + public function filesUpload($args) + { + $args['path'] = $this->formatPath($args['path']); + + if (isset($args['client_modified']) + && $args['client_modified'] instanceof DateTime + ) { + $args['client_modified'] = $args['client_modified']->format('Y-m-d\TH:m:s\Z'); + } + + try { + return $this->request('files/upload', $args, 'upload'); + } catch (BackWPup_Destination_Dropbox_API_Request_Exception $e) { + $this->handleFilesUploadError($e->getError()); + + return null; + } + } + + /** + * Append more data to an uploading file. + * + * @param array $args An array of arguments + */ + public function filesUploadSessionAppendV2($args) + { + try { + return $this->request( + 'files/upload_session/append_v2', + $args, + 'upload' + ); + } catch (BackWPup_Destination_Dropbox_API_Request_Exception $e) { + $error = $e->getError(); + + // See if we can fix the error first + if ($error['.tag'] === 'incorrect_offset') { + $args['cursor']['offset'] = $error['correct_offset']; + + return $this->request( + 'files/upload_session/append_v2', + $args, + 'upload' + ); + } + + // Otherwise, can't fix + $this->handleFilesUploadSessionLookupError($error); + } + } + + /** + * Finish an upload session. + * + * @param array $args + * + * @return array|null Information on the uploaded file + */ + public function filesUploadSessionFinish($args): ?array + { + $args['commit']['path'] = $this->formatPath($args['commit']['path']); + + try { + return $this->request('files/upload_session/finish', $args, 'upload'); + } catch (BackWPup_Destination_Dropbox_API_Request_Exception $e) { + $error = $e->getError(); + if ($error['.tag'] === 'lookup_failed') { + if ($error['lookup_failed']['.tag'] === 'incorrect_offset') { + $args['cursor']['offset'] = $error['lookup_failed']['correct_offset']; + + return $this->request('files/upload_session/finish', $args, 'upload'); + } + } + $this->handleFilesUploadSessionFinishError($e->getError()); + + return null; + } + } + + /** + * Starts an upload session. + * + * When a file larger than 150 MB needs to be uploaded, then this API + * endpoint is used to start a session to allow the file to be uploaded in + * chunks. + * + * @param array $args + * + * @return array an array containing the session's ID + */ + public function filesUploadSessionStart($args = []) + { + return $this->request('files/upload_session/start', $args, 'upload'); + } + + /** + * Get user's current account info. + * + * @return array + */ + public function usersGetCurrentAccount() + { + return $this->request('users/get_current_account'); + } + + /** + * Get quota info for this user. + * + * @return array + */ + public function usersGetSpaceUsage() + { + return $this->request('users/get_space_usage'); + } + + /** + * Get the user agent. + * + * If no user agent has been provided, defaults to `BackWPup::get_plugin_data('User-Agent')`. + * + * @return string The user agent + */ + public function getUserAgent() + { + return $this->userAgent ?: \BackWPup::get_plugin_data('User-Agent'); + } + + /** + * Set the user agent. + * + * @param string $userAgent + */ + public function setUserAgent($userAgent) + { + $this->userAgent = $userAgent; + } + + /** + * Get the SSL ca-bundle path. + * + * If no ca-bundle has been provided, defaults to `BackWPup::get_plugin_data('cacert')`. + * + * @return string The SSL ca-bundle + */ + public function getCaBundle() + { + return $this->caBundle ?: \BackWPup::get_plugin_data('cacert'); + } + + /** + * Set the path to the SSL ca-bundle. + * + * @param string $caBundle The path to the ca-bundle file + */ + public function setCaBundle($caBundle) + { + $this->caBundle = $caBundle; + } + + /** + * Set the job object. + * + * @param BackWPup_Job $jobObject The job object to set + */ + public function setJobObject(BackWPup_Job $jobObject) + { + $this->jobObject = $jobObject; + } + + /** + * Logs a message to the current job. + * + * @param string $message The message to log + * @param int $level The log level + * + * @return bool|null True on success, null if no job object set + */ + protected function log($message, $level = E_USER_NOTICE) + { + if (!isset($this->jobObject)) { + return null; + } + + return $this->jobObject->log($message, $level); + } + + /** + * Logs debug info about the current request. + * + * @param string $endpoint The current request endpoint + * @param array $args The request args + * + * @return bool|null True on success, null if no job object set or debug is not enabled + */ + protected function logRequest($endpoint, array $args) + { + if (!isset($this->jobObject) || !$this->jobObject->is_debug()) { + return null; + } + + $message = "Call to {$endpoint}"; + + if (isset($args['contents'])) { + $message .= ' with ' . size_format(strlen($args['contents'])) . ' of data,'; + unset($args['contents']); + } + + if (!empty($args)) { + $message .= ' with parameters ' . json_encode($args); + } + + return $this->log($message); + } + + /** + * Request. + * + * @param string $url + * @param array $args + * @param string $endpointFormat + * @param bool $echo + * @param string $bytes + * + * @throws BackWPup_Destination_Dropbox_API_Exception + * + * @return array|mixed|string + */ + public function request($endpoint, $args = [], $endpointFormat = 'rpc', $echo = false, $bytes = null) + { + // Log request + $this->logRequest($endpoint, $args); + + if ($bytes !== null) { + $args['bytes'] = $bytes; + } + + $request = $this->buildRequest($endpoint, $args, $endpointFormat); + $client = $this->createClient(); + + $response = $client->sendRequest($request); + + if ($response->getStatusCode() >= 500) { + $this->handleServerException($response); + } elseif ($response->getStatusCode() >= 400) { + $this->handleRequestException($response); + + // If we're still here, then recurse + return $this->request($endpoint, $args, $endpointFormat, $echo, $bytes); + } + + if ($echo === true) { + echo $response->getBody(); // phpcs:ignore + } + + if ($response->getHeaderLine('Content-Type') === 'application/json') { + return json_decode($response->getBody(), true); + } + + return $response->getBody()->getContents(); + } + + /** + * Gets the full URL to the Dropbox endpoint. + * + * @param string $endpoint The API endpoint + * @param string $format The endpoint Format + * + * @return string The full URL + */ + private function getUrl($endpoint, $format) + { + Assert::oneOf($format, ['oauth', 'rpc', 'upload', 'download']); + + switch ($format) { + case 'oauth': + return self::API_URL . $endpoint; + + case 'rpc': + return self::API_URL . self::API_VERSION_URL . $endpoint; + + default: + return self::API_CONTENT_URL . self::API_VERSION_URL . $endpoint; + } + } + + /** + * Builds the options for the request. + * + * @param string $endpoint The endpoint to call + * @param array $args The arguments for the request + * @param string $format The endpoint format + * + * @return RequestInterface The HTTP request + */ + private function buildRequest($endpoint, array &$args, $format) + { + $url = $this->getUrl($endpoint, $format); + + $request = $this->createRequestFactory() + ->createRequest('POST', $url) + ; + + if ($format !== 'oauth') { + $request = new AuthorizationRequest($request); + $request = $request->withOAuthToken($this->getTokens()['access_token']); + } + + $streamFactory = new StreamFactory(); + + switch ($format) { + case 'oauth': + $request = new AuthorizationRequest(new FormRequest($request)); + $request = $request + ->withBasicAuth(BasicAuthCredentials::fromUsernameAndPassword($this->oauthAppKey, $this->oauthAppSecret)) + ->withFormParams($args, $streamFactory) + ->withHeader('Accept', 'application/json') + ; + break; + + case 'rpc': + $request = new JsonRequest($request); + $request = $request + ->withJsonData($args ?: null, $streamFactory) + ->withHeader('Accept', 'application/json') + ; + break; + + case 'upload': + if (isset($args['contents'])) { + $stream = $streamFactory->createStream($args['contents']); + $request = $request->withBody($stream); + unset($args['contents']); + } + + $request = $request + ->withHeader('Content-Type', 'application/octet-stream') + ->withHeader('Dropbox-API-Arg', json_encode($args, JSON_FORCE_OBJECT)) + ; + break; + + case 'download': + if (isset($args['bytes'])) { + $request = $request + ->withHeader('Range', 'bytes=' . $args['bytes']) + ; + unset($args['bytes']); + } + + $request = $request + ->withHeader('Content-Type', 'text/plain') + ->withHeader('Accept', 'application/octet-stream') + ->withHeader('Dropbox-API-Arg', json_encode($args, JSON_FORCE_OBJECT)) + ; + break; + } + + return $request; + } + + /** + * Handle request exception. + * + * Called for 4xx responses. + * + * @param ResponseInterface $response The returned response + * + * @throws BackWPup_Destination_Dropbox_API_Exception If the error cannot be handled + * @throws BackWPup_Destination_Dropbox_API_Request_Exception For endpoint-specific errors + * to be passed up the chain + */ + private function handleRequestException(ResponseInterface $response) + { + switch ($response->getStatusCode()) { + case 400: + case 401: + case 403: + case 409: + case 429: + $callback = [$this, 'handle' . $response->getStatusCode() . 'Error']; + call_user_func($callback, $response); + break; + + default: + throw new BackWPup_Destination_Dropbox_API_Exception( + sprintf( + __( + '(%1$s) An unknown error has occurred. Response from server: %2$s', + 'backwpup' + ), + $response->getStatusCode(), + $response->getBody()->getContents() + ) + ); + } + } + + /** + * Handle server exception. + * + * Called for 5xx responses. + * + * @param ResponseInterface $response The returned response + * + * @throws BackWPup_Destination_Dropbox_API_Exception + */ + protected function handleServerException(ResponseInterface $response) + { + throw new BackWPup_Destination_Dropbox_API_Exception( + sprintf( + __( + '(%1$d) An unexpected server error was encountered. Response from server: %2$s', + 'backwpup' + ), + $response->getStatusCode(), + $response->getBody()->getContents() + ) + ); + } + + /** + * Handle 400 response error. + * + * @param \Psr\Http\Message\ResponseInterface The returned response + * + * @throws BackWPup_Destination_Dropbox_API_Exception + */ + protected function handle400Error(ResponseInterface $response) + { + throw new BackWPup_Destination_Dropbox_API_Exception( + sprintf( + __( + '(400) Bad input parameter. Response from server: %s', + 'backwpup' + ), + $response->getBody()->getContents() + ) + ); + } + + /** + * Handle 401 response error. + * + * @param \Psr\Http\Message\ResponseInterface The returned response + * + * @throws BackWPup_Destination_Dropbox_API_Exception If token is invalid + */ + protected function handle401Error(ResponseInterface $response) + { + $error = json_decode($response->getBody()->getContents(), true); + if ($error['error']['.tag'] === 'expired_access_token') { + $this->refresh($this->oauthToken['refresh_token']); + } else { + throw new BackWPup_Destination_Dropbox_API_Exception( + sprintf( + __( + '(401) Bad or expired token. Response from server: %s', + 'backwpup' + ), + $error['error']['.tag'] + ) + ); + } + } + + /** + * Handle 403 response error. + * + * @param \Psr\Http\Message\ResponseInterface The returned response + * + * @throws BackWPup_Destination_Dropbox_API_Exception + */ + protected function handle403Error(ResponseInterface $response) + { + $error = json_decode($response->getBody(), true); + + if ($error['error']['.tag'] === 'invalid_account_type') { + // InvalidAccountTypeError + if ($error['error']['invalid_account_type']['.tag'] === 'endpoint') { + throw new BackWPup_Destination_Dropbox_API_Exception( + __( + '(403) You do not have permission to access this endpoint.', + 'backwpup' + ) + ); + } + if ($error['error']['invalid_account_type']['.tag'] === 'feature') { + throw new BackWPup_Destination_Dropbox_API_Exception( + __( + '(403) You do not have permission to access this feature.', + 'backwpup' + ) + ); + } + } + + // Catch all + throw new BackWPup_Destination_Dropbox_API_Exception( + sprintf( + __( + '(403) You do not have permission to access this resource. Response from server: %s', + 'backwpup' + ), + $error['error_summary'] + ) + ); + } + + /** + * Handle 409 response error. + * + * @param \Psr\Http\Message\ResponseInterface The returned response + * + * @throws BackWPup_Destination_Dropbox_API_Request_Exception + */ + protected function handle409Error(ResponseInterface $response) + { + $error = json_decode($response->getBody(), true); + + throw new BackWPup_Destination_Dropbox_API_Request_Exception( + sprintf( + __( + '(409) Endpoint-specific error. Response from server: %s', + 'backwpup' + ), + $error['error_summary'] + ), + $response->getStatusCode(), + null, + $error['error'] + ); + } + + /** + * Handle 429 response error. + * + * This error is encountered when requests are being rate limited. + * + * @param \Psr\Http\Message\ResponseInterface The returned response + * + * @throws BackWPup_Destination_Dropbox_API_Exception If unable to detect time to wait + */ + protected function handle429Error(ResponseInterface $response) + { + if (!$response->hasHeader('Retry-After')) { + throw new BackWPup_Destination_Dropbox_API_Exception( + __( + '(429) Requests are being rate limited. Please try again later.', + 'backwpup' + ) + ); + } + sleep(intval($response->getHeaderLine('Retry-After'))); + } + + /** + * Creates a new HTTP client. + * + * @return ClientInterface + */ + protected function createClient() + { + $options = [ + 'timeout' => 60, + ]; + + if (empty($this->getCaBundle())) { + $options['sslverify'] = false; + } else { + $options += [ + 'sslverify' => true, + 'sslcertificates' => $this->getCaBundle(), + ]; + } + + if (!empty($this->getUserAgent())) { + $options['user-agent'] = $this->getUserAgent(); + } + + return new WpHttpClient(new ResponseFactory(), new StreamFactory(), $options); + } + + /** + * Creates a request factory for creating requests. + * + * @return RequestFactory + */ + protected function createRequestFactory() + { + return new RequestFactory(); + } + + /** + * Formats a path to be valid for Dropbox. + * + * @param string $path + * + * @return string The formatted path + */ + private function formatPath($path) + { + if (!empty($path) && substr($path, 0, 1) !== '/') { + $path = '/' . rtrim($path, '/'); + } elseif ($path === '/') { + $path = ''; + } + + return $path; + } + + // Error Handlers + + private function handleFilesDeleteError($error) + { + switch ($error['.tag']) { + case 'path_lookup': + $this->handleFilesLookupError($error['path_lookup']); + break; + + case 'path_write': + $this->handleFilesWriteError($error['path_write']); + break; + + case 'other': + trigger_error('Could not delete file.', E_USER_WARNING); + break; + } + } + + private function handleFilesGetMetadataError($error) + { + switch ($error['.tag']) { + case 'path': + $this->handleFilesLookupError($error['path']); + break; + + case 'other': + trigger_error('Cannot look up file metadata.', E_USER_WARNING); + break; + } + } + + private function handleFilesGetTemporaryLinkError($error) + { + switch ($error['.tag']) { + case 'path': + $this->handleFilesLookupError($error['path']); + break; + + case 'other': + trigger_error('Cannot get temporary link.', E_USER_WARNING); + break; + } + } + + private function handleFilesListFolderError($error) + { + switch ($error['.tag']) { + case 'path': + $this->handleFilesLookupError($error['path']); + break; + + case 'other': + trigger_error('Cannot list files in folder.', E_USER_WARNING); + break; + } + } + + private function handleFilesListFolderContinueError($error) + { + switch ($error['.tag']) { + case 'path': + $this->handleFilesLookupError($error['path']); + break; + + case 'reset': + trigger_error('This cursor has been invalidated.', E_USER_WARNING); + break; + + case 'other': + trigger_error('Cannot list files in folder.', E_USER_WARNING); + break; + } + } + + private function handleFilesLookupError($error) + { + switch ($error['.tag']) { + case 'malformed_path': + trigger_error('The path was malformed.', E_USER_WARNING); + break; + + case 'not_found': + trigger_error('File could not be found.', E_USER_WARNING); + break; + + case 'not_file': + trigger_error('That is not a file.', E_USER_WARNING); + break; + + case 'not_folder': + trigger_error('That is not a folder.', E_USER_WARNING); + break; + + case 'restricted_content': + trigger_error('This content is restricted.', E_USER_WARNING); + break; + + case 'invalid_path_root': + trigger_error('Path root is invalid.', E_USER_WARNING); + break; + + case 'other': + trigger_error('File could not be found.', E_USER_WARNING); + break; + } + } + + private function handleFilesUploadSessionFinishError($error) + { + switch ($error['.tag']) { + case 'lookup_failed': + $this->handleFilesUploadSessionLookupError( + $error['lookup_failed'] + ); + break; + + case 'path': + $this->handleFilesWriteError($error['path']); + break; + + case 'too_many_shared_folder_targets': + trigger_error('Too many shared folder targets.', E_USER_WARNING); + break; + + case 'other': + trigger_error('The file could not be uploaded.', E_USER_WARNING); + break; + } + } + + private function handleFilesUploadSessionLookupError($error) + { + switch ($error['.tag']) { + case 'not_found': + trigger_error('Session not found.', E_USER_WARNING); + break; + + case 'incorrect_offset': + trigger_error( + 'Incorrect offset given. Correct offset is ' . + intval($error['correct_offset']) . '.', + E_USER_WARNING + ); + break; + + case 'closed': + trigger_error( + 'This session has been closed already.', + E_USER_WARNING + ); + break; + + case 'not_closed': + trigger_error('This session is not closed.', E_USER_WARNING); + break; + + case 'other': + trigger_error( + 'Could not look up the file session.', + E_USER_WARNING + ); + break; + } + } + + private function handleFilesUploadError($error) + { + switch ($error['.tag']) { + case 'path': + $this->handleFilesUploadWriteFailed($error['path']); + break; + + case 'other': + trigger_error('There was an unknown error when uploading the file.', E_USER_WARNING); + break; + } + } + + private function handleFilesUploadWriteFailed($error) + { + $this->handleFilesWriteError($error['reason']); + } + + private function handleFilesWriteError($error) + { + $message = ''; + + // Type of error + switch ($error['.tag']) { + case 'malformed_path': + $message = 'The path was malformed.'; + break; + + case 'conflict': + $message = 'Cannot write to the target path due to conflict.'; + break; + + case 'no_write_permission': + $message = 'You do not have permission to save to this location.'; + break; + + case 'insufficient_space': + $message = 'You do not have enough space in your Dropbox.'; + break; + + case 'disallowed_name': + $message = 'The given name is disallowed by Dropbox.'; + break; + + case 'team_folder': + $message = 'Unable to modify team folders.'; + break; + + case 'other': + $message = 'There was an unknown error when uploading the file.'; + break; + } + + trigger_error($message, E_USER_WARNING); // phpcs:ignore + } } diff --git a/inc/class-destination-dropbox-downloader.php b/inc/class-destination-dropbox-downloader.php index b9625ffd..6f8ebd0a 100644 --- a/inc/class-destination-dropbox-downloader.php +++ b/inc/class-destination-dropbox-downloader.php @@ -1,122 +1,118 @@ data = $data; - - $this->dropbox_api(); - } - - /** - * Clean up things - */ - public function __destruct() { - - fclose( $this->local_file_handler ); - } - - /** - * @inheritdoc - */ - public function download_chunk( $start_byte, $end_byte ) { - - $this->local_file_handler( $start_byte ); - - try { - $data = $this->dropbox_api->download( - array( 'path' => $this->data->source_file_path() ), - $start_byte, - $end_byte - ); - - $bytes = (int) fwrite( $this->local_file_handler, $data ); - if ( $bytes === 0 ) { - throw new \RuntimeException( __( 'Could not write data to file.', 'backwpup' ) ); - } - } catch ( \Exception $e ) { - BackWPup_Admin::message( 'Dropbox: ' . $e->getMessage() ); - } - } - - /** - * @inheritdoc - */ - public function calculate_size() { - - $metadata = $this->dropbox_api->filesGetMetadata( array( 'path' => $this->data->source_file_path() ) ); - - return $metadata['size']; - } - - /** - * Set local file hanlder - * - * @param int $start_byte - */ - private function local_file_handler( $start_byte ) { - - if ( is_resource( $this->local_file_handler ) ) { - return; - } - - // Open file; write mode if $start_byte is 0, else append - $this->local_file_handler = fopen( $this->data->local_file_path(), $start_byte == 0 ? 'wb' : 'ab' ); - - if ( ! is_resource( $this->local_file_handler ) ) { - throw new \RuntimeException( __( 'File could not be opened for writing.', 'backwpup' ) ); - } - } - - /** - * Set the dropbox api instance - * - * @return $this - * @throws \BackWPup_Destination_Dropbox_API_Exception - */ - private function dropbox_api() { - - $this->dropbox_api = new \BackWPup_Destination_Dropbox_API( - \BackWPup_Option::get( $this->data->job_id(), self::OPTION_ROOT ) - ); - - $this->dropbox_api->setOAuthTokens( \BackWPup_Option::get( $this->data->job_id(), self::OPTION_TOKEN ) ); - } +final class BackWPup_Destination_Dropbox_Downloader implements BackWPup_Destination_Downloader_Interface +{ + public const OPTION_ROOT = 'dropboxroot'; + public const OPTION_TOKEN = 'dropboxtoken'; + + /** + * @var \BackWpUp_Destination_Downloader_Data + */ + private $data; + + /** + * @var resource + */ + private $local_file_handler; + + /** + * @var BackWPup_Destination_Dropbox_API + */ + private $dropbox_api; + + /** + * BackWPup_Destination_Dropbox_Downloader constructor. + * + * @param \BackWpUp_Destination_Downloader_Data $data + * + * @throws \BackWPup_Destination_Dropbox_API_Exception + */ + public function __construct(BackWpUp_Destination_Downloader_Data $data) + { + $this->data = $data; + + $this->dropbox_api(); + } + + /** + * Clean up things. + */ + public function __destruct() + { + fclose($this->local_file_handler); + } + + /** + * {@inheritdoc} + */ + public function download_chunk($start_byte, $end_byte) + { + $this->local_file_handler($start_byte); + + try { + $data = $this->dropbox_api->download( + ['path' => $this->data->source_file_path()], + $start_byte, + $end_byte + ); + + $bytes = (int) fwrite($this->local_file_handler, $data); + if ($bytes === 0) { + throw new \RuntimeException(__('Could not write data to file.', 'backwpup')); + } + } catch (\Exception $e) { + BackWPup_Admin::message('Dropbox: ' . $e->getMessage()); + } + } + + /** + * {@inheritdoc} + */ + public function calculate_size() + { + $metadata = $this->dropbox_api->filesGetMetadata(['path' => $this->data->source_file_path()]); + + return $metadata['size']; + } + + /** + * Set local file hanlder. + * + * @param int $start_byte + */ + private function local_file_handler($start_byte) + { + if (is_resource($this->local_file_handler)) { + return; + } + + // Open file; write mode if $start_byte is 0, else append + $this->local_file_handler = fopen($this->data->local_file_path(), $start_byte == 0 ? 'wb' : 'ab'); + + if (!is_resource($this->local_file_handler)) { + throw new \RuntimeException(__('File could not be opened for writing.', 'backwpup')); + } + } + + /** + * Set the dropbox api instance. + * + * @throws \BackWPup_Destination_Dropbox_API_Exception + */ + private function dropbox_api(): void + { + $this->dropbox_api = new \BackWPup_Destination_Dropbox_API( + \BackWPup_Option::get($this->data->job_id(), self::OPTION_ROOT) + ); + + $this->dropbox_api->setOAuthTokens(\BackWPup_Option::get($this->data->job_id(), self::OPTION_TOKEN)); + } } diff --git a/inc/class-destination-dropbox.php b/inc/class-destination-dropbox.php index a3a005c3..1a901c76 100644 --- a/inc/class-destination-dropbox.php +++ b/inc/class-destination-dropbox.php @@ -5,185 +5,183 @@ * * Documentation: https://www.dropbox.com/developers/documentation/http/overview */ -class BackWPup_Destination_Dropbox extends BackWPup_Destinations { - - /** - * Dropbox - * - * Instance of Dropbox API - * - * @var BackWPup_Destination_Dropbox_Api - */ - protected $dropbox; - - /** - * Default Options - * - * @return array The default options for dropbox - */ - public function option_defaults() { - - return array( - 'dropboxtoken' => array(), - 'dropboxroot' => 'sandbox', - 'dropboxmaxbackups' => 15, - 'dropboxsyncnodelete' => true, - 'dropboxdir' => '/' . trailingslashit( sanitize_file_name( get_bloginfo( 'name' ) ) ), - ); - } - - /** - * Edit Tab - * - * @throws BackWPup_Destination_Dropbox_API_Exception If the destionation instance cannot be created. - * - * @param int $jobid The job id. - * - * @return void - */ - public function edit_tab( $jobid ) { - - if ( ! empty( $_GET['deleteauth'] ) ) { // phpcs:ignore - // Disable token on dropbox. - try { - $dropbox = $this->get_dropbox ($jobid ); - $dropbox->authTokenRevoke(); - } catch ( Exception $e ) { - echo '

' - . sprintf( - /* translators: the $1 is the error message */ - esc_html__( 'Dropbox API: %s', 'backwpup' ), - esc_html( $e->getMessage() ) - ) - . '

'; - } - BackWPup_Option::update( $jobid, 'dropboxtoken', array() ); - BackWPup_Option::update( $jobid, 'dropboxroot', 'sandbox' ); - } - - $dropbox = new BackWPup_Destination_Dropbox_API( 'dropbox' ); - $dropbox_auth_url = $dropbox->oAuthAuthorize(); - $dropbox = new BackWPup_Destination_Dropbox_API( 'sandbox' ); - $sandbox_auth_url = $dropbox->oAuthAuthorize(); - - $dropboxtoken = BackWPup_Option::get( $jobid, 'dropboxtoken' ); - ?> - -

+class BackWPup_Destination_Dropbox extends BackWPup_Destinations +{ + /** + * Dropbox. + * + * Instance of Dropbox API + * + * @var BackWPup_Destination_Dropbox_API|null + */ + protected $dropbox; + + /** + * Default Options. + * + * @return array The default options for dropbox + */ + public function option_defaults(): array + { + return [ + 'dropboxtoken' => [], + 'dropboxroot' => 'sandbox', + 'dropboxmaxbackups' => 15, + 'dropboxsyncnodelete' => true, + 'dropboxdir' => '/' . trailingslashit(sanitize_file_name(get_bloginfo('name'))), + ]; + } + + /** + * Edit Tab. + * + * @param int $jobid the job id + * + * @throws BackWPup_Destination_Dropbox_API_Exception if the destionation instance cannot be created + */ + public function edit_tab(int $jobid): void + { + if (!empty($_GET['deleteauth'])) { // phpcs:ignore + // Disable token on dropbox. + try { + $dropbox = $this->get_dropbox($jobid); + $dropbox->authTokenRevoke(); + } catch (Exception $e) { + echo '

' + . sprintf( + // translators: the $1 is the error message + esc_html__('Dropbox API: %s', 'backwpup'), + esc_html($e->getMessage()) + ) + . '

'; + } + BackWPup_Option::update($jobid, 'dropboxtoken', []); + BackWPup_Option::update($jobid, 'dropboxroot', 'sandbox'); + } + + $dropbox = new BackWPup_Destination_Dropbox_API('dropbox'); + $dropbox_auth_url = $dropbox->oAuthAuthorize(); + $dropbox = new BackWPup_Destination_Dropbox_API('sandbox'); + $sandbox_auth_url = $dropbox->oAuthAuthorize(); + + $dropboxtoken = BackWPup_Option::get($jobid, 'dropboxtoken'); ?> + +

- - + - + + 'App Access to Dropbox', + 'backwpup' + ); ?> - + + 'Full Access to Dropbox', + 'backwpup' + ); ?>
- + +
 
+ href="http://db.tt/8irM1vQ0"> - +
 
" title=""> + 'Delete Dropbox Authentication', + 'backwpup' + ); ?>">
  + $sandbox_auth_url + ); ?>" target="_blank">

+ 'A dedicated folder named BackWPup will be created inside of the Apps folder in your Dropbox. BackWPup will get read and write access to that folder only. You can specify a subfolder as your backup destination for this job in the destination field below.', + 'backwpup' + ); ?>

  + $dropbox_auth_url + ); ?>" target="_blank">

+ 'BackWPup will have full read and write access to your entire Dropbox. You can specify your backup destination wherever you want, just be aware that ANY files or folders inside of your Dropbox can be overridden or deleted by BackWPup.', + 'backwpup' + ); ?>

-

+

+ 'Destination Folder', + 'backwpup' + ); ?> - + @@ -191,324 +189,308 @@ public function edit_tab( $jobid ) {
+ BackWPup_Option::get($jobid, 'dropboxdir') + ); ?>" class="regular-text" />

+ 'Specify a subfolder where your backup archives will be stored. If you use the App option from above, this folder will be created inside of Apps/BackWPup. Otherwise it will be created at the root of your Dropbox. Already exisiting folders with the same name will not be overriden.', + 'backwpup' + ); ?>

+ if (BackWPup_Option::get($jobid, 'backuptype') === 'archive') { + ?>

Warning: Files belonging to this job are now tracked. Old backup archives which are untracked will not be automatically deleted.', - 'backwpup' - ) ?>

- + 'Warning: Files belonging to this job are now tracked. Old backup archives which are untracked will not be automatically deleted.', + 'backwpup' + ); ?>

+
oAuthToken( $_POST['sandbox_code'] ); - BackWPup_Option::update( $jobid, 'dropboxtoken', $dropboxtoken ); - BackWPup_Option::update( $jobid, 'dropboxroot', 'sandbox' ); - } catch ( Exception $e ) { - BackWPup_Admin::message( 'DROPBOX: ' . $e->getMessage(), true ); - } - } - - if ( ! empty( $_POST['dropbbox_code'] ) ) { - try { - $dropbox = new BackWPup_Destination_Dropbox_API( 'dropbox' ); - $dropboxtoken = $dropbox->oAuthToken( $_POST['dropbbox_code'] ); - BackWPup_Option::update( $jobid, 'dropboxtoken', $dropboxtoken ); - BackWPup_Option::update( $jobid, 'dropboxroot', 'dropbox' ); - } catch ( Exception $e ) { - BackWPup_Admin::message( 'DROPBOX: ' . $e->getMessage(), true ); - } - } - - BackWPup_Option::update( $jobid, 'dropboxsyncnodelete', ! empty( $_POST['dropboxsyncnodelete'] ) ); - BackWPup_Option::update( - $jobid, - 'dropboxmaxbackups', - ! empty( $_POST['dropboxmaxbackups'] ) ? absint( $_POST['dropboxmaxbackups'] ) : 0 - ); - - $_POST['dropboxdir'] = trailingslashit( - str_replace( '//', '/', str_replace( '\\', '/', trim( sanitize_text_field( $_POST['dropboxdir'] ) ) ) ) - ); - if ( $_POST['dropboxdir'] === '/' ) { - $_POST['dropboxdir'] = ''; - } - BackWPup_Option::update( $jobid, 'dropboxdir', $_POST['dropboxdir'] ); - } - - /** - * Delete File - * - * @param string $jobdest The destionation for this job. - * @param string $backupfile The file to delete. - */ - public function file_delete( $jobdest, $backupfile ) { - - $files = get_site_transient( 'backwpup_' . strtolower( $jobdest ) ); - list( $jobid, $dest ) = explode( '_', $jobdest ); - - try { - $dropbox = $this->get_dropbox( $jobid ); - $dropbox->filesDelete( array( 'path' => $backupfile ) ); - - //update file list - foreach ( $files as $key => $file ) { - if ( is_array( $file ) && $file['file'] == $backupfile ) { - unset( $files[ $key ] ); - } - } - unset( $dropbox ); - } catch ( Exception $e ) { - BackWPup_Admin::message( 'DROPBOX: ' . $e->getMessage(), true ); - } - - set_site_transient( 'backwpup_' . strtolower( $jobdest ), $files, YEAR_IN_SECONDS ); - } - - /** - * @inheritdoc - */ - public function file_get_list( $jobdest ) { - - $list = (array) get_site_transient( 'backwpup_' . strtolower( $jobdest ) ); - $list = array_filter( $list ); - - return $list; - } - - /** - * File Update List - * - * Update the list of files in the transient. - * - * @param BackWPup_Job|int $job Either the job object or job ID - * @param bool $delete Whether to delete old backups. - */ - public function file_update_list( $job, $delete = false ) { - - if ( $job instanceof BackWPup_Job ) { - $job_object = $job; - $jobid = $job->job['jobid']; - } else { - $job_object = null; - $jobid = $job; - } - - $backupfilelist = array(); - $filecounter = 0; - $files = array(); - $dropbox = $this->get_dropbox( $jobid ); - $filesList = $dropbox->listFolder( BackWPup_Option::get( $jobid, 'dropboxdir' ) ); - foreach ( $filesList as $data ) { - if ( $data['.tag'] == 'file' && $this->is_backup_owned_by_job( $data['name'], $jobid ) == true ) { - $file = $data['name']; - if ( $this->is_backup_archive( $file ) ) { - $backupfilelist[ strtotime( $data['server_modified'] ) ] = $file; - } - $files[ $filecounter ]['folder'] = dirname( $data['path_display'] ); - $files[ $filecounter ]['file'] = $data['path_display']; - $files[ $filecounter ]['filename'] = $data['name']; - $files[ $filecounter ]['downloadurl'] = network_admin_url( - 'admin.php?page=backwpupbackups&action=downloaddropbox&file=' . $data['path_display'] . '&local_file=' . $data['name'] . '&jobid=' . $jobid - ); - $files[ $filecounter ]['filesize'] = $data['size']; - $files[ $filecounter ]['time'] = strtotime( $data['server_modified'] ) + ( get_option( - 'gmt_offset' - ) * 3600 ); - $filecounter++; - } - } - if ( $delete && $job_object && BackWPup_Option::get( $jobid, 'dropboxmaxbackups' ) > 0 ) { //Delete old backups - if ( count( $backupfilelist ) > $job_object->job['dropboxmaxbackups'] ) { - ksort( $backupfilelist ); - $numdeltefiles = 0; - while ( $file = array_shift( $backupfilelist ) ) { - if ( count( $backupfilelist ) < $job_object->job['dropboxmaxbackups'] ) { - break; - } - $response = $dropbox->filesDelete( - array( 'path' => $job_object->job['dropboxdir'] . $file ) - ); //delete files on Cloud - foreach ( $files as $key => $filedata ) { - if ( $filedata['file'] == '/' . $job_object->job['dropboxdir'] . $file ) { - unset( $files[ $key ] ); - } - } - $numdeltefiles++; - } - if ( $numdeltefiles > 0 ) { - $job_object->log( - sprintf( - _n( - 'One file deleted from Dropbox', - '%d files deleted on Dropbox', - $numdeltefiles, - 'backwpup' - ), - $numdeltefiles - ), - E_USER_NOTICE - ); - } - } - } - set_site_transient( 'backwpup_' . $jobid . '_dropbox', $files, YEAR_IN_SECONDS ); - } - - /** - * @param $job_object - * - * @return bool - */ - public function job_run_archive( BackWPup_Job $job_object ) { - - $job_object->substeps_todo = 2 + $job_object->backup_filesize; - if ( $job_object->steps_data[ $job_object->step_working ]['SAVE_STEP_TRY'] != $job_object->steps_data[ $job_object->step_working ]['STEP_TRY'] ) { - $job_object->log( - sprintf( - __( '%d. Try to send backup file to Dropbox …', 'backwpup' ), - $job_object->steps_data[ $job_object->step_working ]['STEP_TRY'] - ) - ); - } - - try { - $dropbox = $this->get_dropbox( $job_object ); - - //get account info - if ( $job_object->steps_data[ $job_object->step_working ]['SAVE_STEP_TRY'] != $job_object->steps_data[ $job_object->step_working ]['STEP_TRY'] ) { - $info = $dropbox->usersGetCurrentAccount(); - if ( ! empty( $info['account_id'] ) ) { - if ( $job_object->is_debug() ) { - $user = $info['name']['display_name'] . ' (' . $info['email'] . ')'; - } else { - $user = $info['name']['display_name']; - } - $job_object->log( sprintf( __( 'Authenticated with Dropbox of user: %s', 'backwpup' ), $user ) ); - - //Quota - if ( $job_object->is_debug() ) { - $quota = $dropbox->usersGetSpaceUsage(); - $dropboxfreespase = $quota['allocation']['allocated'] - $quota['used']; - $job_object->log( - sprintf( - __( '%s available on your Dropbox', 'backwpup' ), - size_format( $dropboxfreespase, 2 ) - ) - ); - } - } else { - $job_object->log( __( 'Not Authenticated with Dropbox!', 'backwpup' ), E_USER_ERROR ); - - return false; - } - $job_object->log( __( 'Uploading to Dropbox …', 'backwpup' ) ); - } - - // put the file - if ( $job_object->substeps_done < $job_object->backup_filesize ) { //only if upload not complete - $response = $dropbox->upload( - $job_object->backup_folder . $job_object->backup_file, - $job_object->job['dropboxdir'] . $job_object->backup_file - ); - if ( $response['size'] == $job_object->backup_filesize ) { - if ( ! empty( $job_object->job['jobid'] ) ) { - BackWPup_Option::update( - $job_object->job['jobid'], - 'lastbackupdownloadurl', - network_admin_url( - 'admin.php' - ) . '?page=backwpupbackups&action=downloaddropbox&file=' . ltrim( - $response['path_display'], - '/' - ) . '&jobid=' . $job_object->job['jobid'] - ); - } - $job_object->substeps_done = 1 + $job_object->backup_filesize; - $job_object->log( - sprintf( __( 'Backup transferred to %s', 'backwpup' ), $response['path_display'] ), - E_USER_NOTICE - ); - } else { - if ( $response['size'] != $job_object->backup_filesize ) { - $job_object->log( - __( 'Uploaded file size and local file size don\'t match.', 'backwpup' ), - E_USER_ERROR - ); - } else { - $job_object->log( - sprintf( - __( 'Error transfering backup to %s.', 'backwpup' ) . ' ' . $response['error'], - __( 'Dropbox', 'backwpup' ) - ), - E_USER_ERROR - ); - } - - return false; - } - } - - $this->file_update_list( $job_object, true ); - } catch ( Exception $e ) { - $job_object->log( - sprintf( __( 'Dropbox API: %s', 'backwpup' ), $e->getMessage() ), - $e->getFile(), - $e->getLine(), - E_USER_ERROR - ); - - return false; - } - $job_object->substeps_done++; - - return true; - } - - /** - * @param $job_settings - * - * @return bool - */ - public function can_run( array $job_settings ) { - - if ( empty( $job_settings['dropboxtoken'] ) ) { - return false; - } - - return true; - } - - /** - * Get Dropbox - * - * Gets the Dropbox API instance. - * - * @parent BackWPup_Job|int $job Either the job object or job ID - * - * @return BackWPup_Destination_Dropbox_Api - */ - protected function get_dropbox( $job ) { - - if ( ! $this->dropbox ) { - if ( $job instanceof BackWPup_Job ) { - $this->dropbox = new BackWPup_Destination_Dropbox_API( $job->job['dropboxroot'], $job ); - $jobid = $job->job['jobid']; - $token = $job->job['dropboxtoken']; - } else { - $this->dropbox = new BackWPup_Destination_Dropbox_API( BackWPup_Option::get( $job, 'dropboxroot' ) ); - $jobid = $job; - $token = BackWPup_Option::get( $job, 'dropboxtoken' ); - } - - $this->dropbox->setOAuthTokens( $token, function ( $token ) use ( $jobid ) { - BackWPup_Option::update( $jobid, 'dropboxtoken', $token ); - } ); - } - - return $this->dropbox; - } + } + + /** + * Save settings. + */ + public function edit_form_post_save(int $jobid): void + { + // Bet auth. + if (!empty($_POST['sandbox_code'])) { + try { + $dropbox = new BackWPup_Destination_Dropbox_API('sandbox'); + $dropboxtoken = $dropbox->oAuthToken($_POST['sandbox_code']); + BackWPup_Option::update($jobid, 'dropboxtoken', $dropboxtoken); + BackWPup_Option::update($jobid, 'dropboxroot', 'sandbox'); + } catch (Exception $e) { + BackWPup_Admin::message('DROPBOX: ' . $e->getMessage(), true); + } + } + + if (!empty($_POST['dropbbox_code'])) { + try { + $dropbox = new BackWPup_Destination_Dropbox_API('dropbox'); + $dropboxtoken = $dropbox->oAuthToken($_POST['dropbbox_code']); + BackWPup_Option::update($jobid, 'dropboxtoken', $dropboxtoken); + BackWPup_Option::update($jobid, 'dropboxroot', 'dropbox'); + } catch (Exception $e) { + BackWPup_Admin::message('DROPBOX: ' . $e->getMessage(), true); + } + } + + BackWPup_Option::update($jobid, 'dropboxsyncnodelete', !empty($_POST['dropboxsyncnodelete'])); + BackWPup_Option::update( + $jobid, + 'dropboxmaxbackups', + !empty($_POST['dropboxmaxbackups']) ? absint($_POST['dropboxmaxbackups']) : 0 + ); + + $_POST['dropboxdir'] = trailingslashit( + str_replace('//', '/', str_replace('\\', '/', trim(sanitize_text_field($_POST['dropboxdir'])))) + ); + if ($_POST['dropboxdir'] === '/') { + $_POST['dropboxdir'] = ''; + } + BackWPup_Option::update($jobid, 'dropboxdir', $_POST['dropboxdir']); + } + + /** + * Delete File. + * + * @param string $jobdest the destionation for this job + * @param string $backupfile the file to delete + */ + public function file_delete(string $jobdest, string $backupfile): void + { + $files = get_site_transient('backwpup_' . strtolower($jobdest)); + [$jobid, $dest] = explode('_', $jobdest); + + try { + $dropbox = $this->get_dropbox($jobid); + $dropbox->filesDelete(['path' => $backupfile]); + + //update file list + foreach ($files as $key => $file) { + if (is_array($file) && $file['file'] == $backupfile) { + unset($files[$key]); + } + } + unset($dropbox); + } catch (Exception $e) { + BackWPup_Admin::message('DROPBOX: ' . $e->getMessage(), true); + } + + set_site_transient('backwpup_' . strtolower($jobdest), $files, YEAR_IN_SECONDS); + } + + /** + * {@inheritdoc} + */ + public function file_get_list(string $jobdest): array + { + $list = (array) get_site_transient('backwpup_' . strtolower($jobdest)); + + return array_filter($list); + } + + /** + * File Update List. + * + * Update the list of files in the transient. + * + * @param BackWPup_Job|int $job Either the job object or job ID + * @param bool $delete whether to delete old backups + */ + public function file_update_list($job, bool $delete = false): void + { + if ($job instanceof BackWPup_Job) { + $job_object = $job; + $jobid = $job->job['jobid']; + } else { + $job_object = null; + $jobid = $job; + } + + $backupfilelist = []; + $filecounter = 0; + $files = []; + $dropbox = $this->get_dropbox($jobid); + $filesList = $dropbox->listFolder(BackWPup_Option::get($jobid, 'dropboxdir')); + + foreach ($filesList as $data) { + if ($data['.tag'] == 'file' && $this->is_backup_owned_by_job($data['name'], $jobid) == true) { + $file = $data['name']; + if ($this->is_backup_archive($file)) { + $backupfilelist[strtotime($data['server_modified'])] = $file; + } + $files[$filecounter]['folder'] = dirname($data['path_display']); + $files[$filecounter]['file'] = $data['path_display']; + $files[$filecounter]['filename'] = $data['name']; + $files[$filecounter]['downloadurl'] = network_admin_url( + 'admin.php?page=backwpupbackups&action=downloaddropbox&file=' . $data['path_display'] . '&local_file=' . $data['name'] . '&jobid=' . $jobid + ); + $files[$filecounter]['filesize'] = $data['size']; + $files[$filecounter]['time'] = strtotime($data['server_modified']) + (get_option( + 'gmt_offset' + ) * 3600); + ++$filecounter; + } + } + if ($delete && $job_object && BackWPup_Option::get($jobid, 'dropboxmaxbackups') > 0) { //Delete old backups + if (count($backupfilelist) > $job_object->job['dropboxmaxbackups']) { + ksort($backupfilelist); + $numdeltefiles = 0; + + while ($file = array_shift($backupfilelist)) { + if (count($backupfilelist) < $job_object->job['dropboxmaxbackups']) { + break; + } + $response = $dropbox->filesDelete( + ['path' => $job_object->job['dropboxdir'] . $file] + ); //delete files on Cloud + + foreach ($files as $key => $filedata) { + if ($filedata['file'] == '/' . $job_object->job['dropboxdir'] . $file) { + unset($files[$key]); + } + } + ++$numdeltefiles; + } + if ($numdeltefiles > 0) { + $job_object->log( + sprintf( + _n( + 'One file deleted from Dropbox', + '%d files deleted on Dropbox', + $numdeltefiles, + 'backwpup' + ), + $numdeltefiles + ), + E_USER_NOTICE + ); + } + } + } + set_site_transient('backwpup_' . $jobid . '_dropbox', $files, YEAR_IN_SECONDS); + } + + public function job_run_archive(BackWPup_Job $job_object): bool + { + $job_object->substeps_todo = 2 + $job_object->backup_filesize; + if ($job_object->steps_data[$job_object->step_working]['SAVE_STEP_TRY'] != $job_object->steps_data[$job_object->step_working]['STEP_TRY']) { + $job_object->log( + sprintf( + __('%d. Try to send backup file to Dropbox …', 'backwpup'), + $job_object->steps_data[$job_object->step_working]['STEP_TRY'] + ) + ); + } + + try { + $dropbox = $this->get_dropbox($job_object); + + //get account info + if ($job_object->steps_data[$job_object->step_working]['SAVE_STEP_TRY'] != $job_object->steps_data[$job_object->step_working]['STEP_TRY']) { + $info = $dropbox->usersGetCurrentAccount(); + if (!empty($info['account_id'])) { + if ($job_object->is_debug()) { + $user = $info['name']['display_name'] . ' (' . $info['email'] . ')'; + } else { + $user = $info['name']['display_name']; + } + $job_object->log(sprintf(__('Authenticated with Dropbox of user: %s', 'backwpup'), $user)); + + //Quota + if ($job_object->is_debug()) { + $quota = $dropbox->usersGetSpaceUsage(); + $dropboxfreespase = $quota['allocation']['allocated'] - $quota['used']; + $job_object->log( + sprintf( + __('%s available on your Dropbox', 'backwpup'), + size_format($dropboxfreespase, 2) + ) + ); + } + } else { + $job_object->log(__('Not Authenticated with Dropbox!', 'backwpup'), E_USER_ERROR); + + return false; + } + $job_object->log(__('Uploading to Dropbox …', 'backwpup')); + } + + // put the file + if ($job_object->substeps_done < $job_object->backup_filesize) { //only if upload not complete + $response = $dropbox->upload( + $job_object->backup_folder . $job_object->backup_file, + $job_object->job['dropboxdir'] . $job_object->backup_file + ); + if ($response['size'] == $job_object->backup_filesize) { + if (!empty($job_object->job['jobid'])) { + BackWPup_Option::update( + $job_object->job['jobid'], + 'lastbackupdownloadurl', + network_admin_url( + 'admin.php' + ) . '?page=backwpupbackups&action=downloaddropbox&file=' . ltrim( + $response['path_display'], + '/' + ) . '&jobid=' . $job_object->job['jobid'] + ); + } + $job_object->substeps_done = 1 + $job_object->backup_filesize; + $job_object->log( + sprintf(__('Backup transferred to %s', 'backwpup'), $response['path_display']), + E_USER_NOTICE + ); + } else { + if ($response['size'] != $job_object->backup_filesize) { + $job_object->log( + __('Uploaded file size and local file size don\'t match.', 'backwpup'), + E_USER_ERROR + ); + } else { + $job_object->log( + sprintf( + __('Error transfering backup to %s.', 'backwpup') . ' ' . $response['error'], + __('Dropbox', 'backwpup') + ), + E_USER_ERROR + ); + } + + return false; + } + } + + $this->file_update_list($job_object, true); + } catch (Exception $e) { + $job_object->log( + sprintf(__('Dropbox API: %s', 'backwpup'), $e->getMessage()), + $e->getFile(), + $e->getLine(), + E_USER_ERROR + ); + + return false; + } + ++$job_object->substeps_done; + + return true; + } + + public function can_run(array $job_settings): bool + { + return !(empty($job_settings['dropboxtoken'])); + } + + /** + * Get Dropbox. + * + * Gets the Dropbox API instance. + * + * @parent BackWPup_Job|int $job Either the job object or job ID + */ + protected function get_dropbox($job): BackWPup_Destination_Dropbox_API + { + if (!$this->dropbox) { + if ($job instanceof BackWPup_Job) { + $this->dropbox = new BackWPup_Destination_Dropbox_API($job->job['dropboxroot'], $job); + $jobid = $job->job['jobid']; + $token = $job->job['dropboxtoken']; + } else { + $this->dropbox = new BackWPup_Destination_Dropbox_API(BackWPup_Option::get($job, 'dropboxroot')); + $jobid = $job; + $token = BackWPup_Option::get($job, 'dropboxtoken'); + } + + $this->dropbox->setOAuthTokens($token, function ($token) use ($jobid): void { + BackWPup_Option::update($jobid, 'dropboxtoken', $token); + }); + } + + return $this->dropbox; + } } diff --git a/inc/class-destination-email.php b/inc/class-destination-email.php index addac9a6..983db8a6 100644 --- a/inc/class-destination-email.php +++ b/inc/class-destination-email.php @@ -3,186 +3,177 @@ // http://swiftmailer.org/ // https://github.com/swiftmailer/swiftmailer -use \Inpsyde\BackWPupShared\File\MimeTypeExtractor; - -class BackWPup_Destination_Email extends BackWPup_Destinations { - - /** - * @return array - */ - public function option_defaults() { - - $default = array(); - $default['emailaddress'] = sanitize_email( get_bloginfo( 'admin_email' ) ); - $default['emailefilesize'] = 20; - $default['emailsndemail'] = sanitize_email( get_bloginfo( 'admin_email' ) ); - $default['emailsndemailname'] = 'BackWPup ' . get_bloginfo( 'name' ); - $default['emailmethod'] = ''; - $default['emailsendmail'] = ini_get( 'sendmail_path' ); - $default['emailhost'] = isset( $_SERVER['SERVER_NAME'] ) ? $_SERVER['SERVER_NAME'] : ''; - $default['emailhostport'] = 25; - $default['emailsecure'] = ''; - $default['emailuser'] = ''; - $default['emailpass'] = ''; - - return $default; - } - - - /** - * @param $jobid - */ - public function edit_tab( $jobid ) { - - ?> -

+use Inpsyde\BackWPupShared\File\MimeTypeExtractor; + +class BackWPup_Destination_Email extends BackWPup_Destinations +{ + public function option_defaults(): array + { + $default = []; + $default['emailaddress'] = sanitize_email(get_bloginfo('admin_email')); + $default['emailefilesize'] = 20; + $default['emailsndemail'] = sanitize_email(get_bloginfo('admin_email')); + $default['emailsndemailname'] = 'BackWPup ' . get_bloginfo('name'); + $default['emailmethod'] = ''; + $default['emailsendmail'] = ini_get('sendmail_path'); + $default['emailhost'] = $_SERVER['SERVER_NAME'] ?? ''; + $default['emailhostport'] = 25; + $default['emailsecure'] = ''; + $default['emailuser'] = ''; + $default['emailpass'] = ''; + + return $default; + } + + public function edit_tab(int $jobid): void + { + ?> +

-
+ for="emailaddress">
+ + class="button secondary">
-

+

+ for="idemailefilesize"> + for="emailsndemail"> - - - > - > + - > - + > + - > + > - > - + > + - > - + > +
+ value="" + class="small-text"/>


- :
- + 'Maximum file size to be included in an email. 0 = unlimited', + 'backwpup' + ); ?>
+ :
+

+
+
+
  -
+ for="emailsecure">
+ public function edit_inline_js(): void + { + ?> get_email_array( $_POST['emailaddress'] ) ) : '' ); - BackWPup_Option::update( $jobid, 'emailefilesize', ! empty( $_POST['emailefilesize'] ) ? absint( $_POST['emailefilesize'] ) : 0 ); - BackWPup_Option::update( $jobid, 'emailsndemail', sanitize_email( $_POST['emailsndemail'] ) ); - BackWPup_Option::update( $jobid, 'emailmethod', ( $_POST['emailmethod'] === '' || $_POST['emailmethod'] === 'mail' || $_POST['emailmethod'] === 'sendmail' || $_POST['emailmethod'] === 'smtp' ) ? $_POST['emailmethod'] : '' ); - BackWPup_Option::update( $jobid, 'emailsendmail', sanitize_text_field( $_POST['emailsendmail'] ) ); - BackWPup_Option::update( $jobid, 'emailsndemailname', sanitize_text_field( $_POST['emailsndemailname'] ) ); - BackWPup_Option::update( $jobid, 'emailhost', sanitize_text_field( $_POST['emailhost'] ) ); - BackWPup_Option::update( $jobid, 'emailhostport', absint( $_POST['emailhostport'] ) ); - BackWPup_Option::update( $jobid, 'emailsecure', ( $_POST['emailsecure'] === 'ssl' || $_POST['emailsecure'] === 'tls' ) ? $_POST['emailsecure'] : '' ); - BackWPup_Option::update( $jobid, 'emailuser', sanitize_text_field( $_POST['emailuser'] ) ); - BackWPup_Option::update( $jobid, 'emailpass', BackWPup_Encryption::encrypt( $_POST['emailpass'] ) ); - } - - /** - * @param $job_object - * - * @return bool - */ - public function job_run_archive( BackWPup_Job $job_object ) { - - $job_object->substeps_todo = 1; - $job_object->log( sprintf( __( '%d. Try to send backup with email …', 'backwpup' ), $job_object->steps_data[ $job_object->step_working ]['STEP_TRY'] ), E_USER_NOTICE ); - - //check file Size - if ( ! empty( $job_object->job['emailefilesize'] ) ) { - if ( $job_object->backup_filesize > $job_object->job['emailefilesize'] * 1024 * 1024 ) { - $job_object->log( __( 'Backup archive too big to be sent by email!', 'backwpup' ), E_USER_ERROR ); - $job_object->substeps_done = 1; - - return true; - } - } - - $job_object->log( sprintf( __( 'Sending email to %s…', 'backwpup' ), $job_object->job['emailaddress'] ), E_USER_NOTICE ); - - //get mail settings - $emailmethod = 'mail'; - $emailsendmail = ''; - $emailhost = ''; - $emailhostport = ''; - $emailsecure = ''; - $emailuser = ''; - $emailpass = ''; - - if (empty($job_object->job['emailmethod'])) { + } + + public function edit_form_post_save(int $jobid): void + { + BackWPup_Option::update($jobid, 'emailaddress', isset($_POST['emailaddress']) ? implode(', ', $this->get_email_array($_POST['emailaddress'])) : ''); + BackWPup_Option::update($jobid, 'emailefilesize', !empty($_POST['emailefilesize']) ? absint($_POST['emailefilesize']) : 0); + BackWPup_Option::update($jobid, 'emailsndemail', sanitize_email($_POST['emailsndemail'])); + BackWPup_Option::update($jobid, 'emailmethod', ($_POST['emailmethod'] === '' || $_POST['emailmethod'] === 'mail' || $_POST['emailmethod'] === 'sendmail' || $_POST['emailmethod'] === 'smtp') ? $_POST['emailmethod'] : ''); + BackWPup_Option::update($jobid, 'emailsendmail', sanitize_text_field($_POST['emailsendmail'])); + BackWPup_Option::update($jobid, 'emailsndemailname', sanitize_text_field($_POST['emailsndemailname'])); + BackWPup_Option::update($jobid, 'emailhost', sanitize_text_field($_POST['emailhost'])); + BackWPup_Option::update($jobid, 'emailhostport', absint($_POST['emailhostport'])); + BackWPup_Option::update($jobid, 'emailsecure', ($_POST['emailsecure'] === 'ssl' || $_POST['emailsecure'] === 'tls') ? $_POST['emailsecure'] : ''); + BackWPup_Option::update($jobid, 'emailuser', sanitize_text_field($_POST['emailuser'])); + BackWPup_Option::update($jobid, 'emailpass', BackWPup_Encryption::encrypt($_POST['emailpass'])); + } + + public function job_run_archive(BackWPup_Job $job_object): bool + { + $job_object->substeps_todo = 1; + $job_object->log(sprintf(__('%d. Try to send backup with email …', 'backwpup'), $job_object->steps_data[$job_object->step_working]['STEP_TRY']), E_USER_NOTICE); + + //check file Size + if (!empty($job_object->job['emailefilesize'])) { + if ($job_object->backup_filesize > $job_object->job['emailefilesize'] * 1024 * 1024) { + $job_object->log(__('Backup archive too big to be sent by email!', 'backwpup'), E_USER_ERROR); + $job_object->substeps_done = 1; + + return true; + } + } + + $job_object->log(sprintf(__('Sending email to %s…', 'backwpup'), $job_object->job['emailaddress']), E_USER_NOTICE); + + //get mail settings + $emailmethod = 'mail'; + $emailsendmail = ''; + $emailhost = ''; + $emailhostport = ''; + $emailsecure = ''; + $emailuser = ''; + $emailpass = ''; + + if (empty($job_object->job['emailmethod'])) { //do so if i'm the wp_mail to get the settings $phpmailer = $this->getPhpMailer(); @@ -298,224 +281,215 @@ public function job_run_archive( BackWPup_Job $job_object ) { $emailpass = BackWPup_Encryption::decrypt($job_object->job['emailpass']); } - //Generate mail with Swift Mailer - if ( ! class_exists( 'Swift', false ) ) { - require BackWPup::get_plugin_data( 'plugindir' ) . '/vendor/SwiftMailer/swift_required.php'; - } - - if ( function_exists( 'mb_internal_encoding' ) && ( (int) ini_get( 'mbstring.func_overload' ) ) & 2 ) { - $mbEncoding = mb_internal_encoding(); - mb_internal_encoding( 'ASCII' ); - } - - try { - //Set Temp dir for mailing - Swift_Preferences::getInstance()->setTempDir( untrailingslashit( BackWPup::get_plugin_data( 'TEMP' ) ) )->setCacheType( 'disk' ); - // Create the Transport - if ( $emailmethod == 'smtp' ) { - $transport = Swift_SmtpTransport::newInstance( $emailhost, $emailhostport ); - if ( $emailuser ) { - $transport->setUsername( $emailuser ); - $transport->setPassword( $emailpass ); - } - if ( $emailsecure == 'ssl' ) { - $transport->setEncryption( 'ssl' ); - } - if ( $emailsecure == 'tls' ) { - $transport->setEncryption( 'tls' ); - } - } elseif ( $emailmethod == 'sendmail' ) { - $transport = Swift_SendmailTransport::newInstance( $emailsendmail ); - } else { - $job_object->need_free_memory( $job_object->backup_filesize * 8 ); - $transport = Swift_MailTransport::newInstance(); - } - // Create the Mailer using your created Transport - $emailer = Swift_Mailer::newInstance( $transport ); - - // Create a message - $message = Swift_Message::newInstance( sprintf( __( 'BackWPup archive from %1$s: %2$s', 'backwpup' ), date_i18n( 'd-M-Y H:i', $job_object->start_time, true ), esc_attr( $job_object->job['name'] ) ) ); - $message->setFrom( array( $job_object->job['emailsndemail'] => $job_object->job['emailsndemailname'] ) ); - $message->setTo( $this->get_email_array( $job_object->job['emailaddress'] ) ); - $message->setBody( sprintf( __( 'Backup archive: %s', 'backwpup' ), $job_object->backup_file ), 'text/plain', strtolower( get_bloginfo( 'charset' ) ) ); - $message->attach( Swift_Attachment::fromPath( $job_object->backup_folder . $job_object->backup_file, MimeTypeExtractor::fromFilePath( $job_object->backup_folder . $job_object->backup_file ) ) ); - // Send the message - $result = $emailer->send( $message ); - } catch ( Exception $e ) { - $job_object->log( 'Swift Mailer: ' . $e->getMessage(), E_USER_ERROR ); - } - - if ( isset( $mbEncoding ) ) { - mb_internal_encoding( $mbEncoding ); - } - - if ( ! isset( $result ) || ! $result ) { - $job_object->log( __( 'Error while sending email!', 'backwpup' ), E_USER_ERROR ); - - return false; - } else { - $job_object->substeps_done = 1; - $job_object->log( __( 'Email sent.', 'backwpup' ), E_USER_NOTICE ); - - return true; - } - } - - /** - * @param $job_settings - * - * @return bool - */ - public function can_run( array $job_settings ) { - - if ( empty( $job_settings['emailaddress'] ) ) { - return false; - } - - if ( $job_settings['backuptype'] != 'archive' ) { - return false; - } - - return true; - } - - - /** - * sends test mail - */ - public function edit_ajax() { - - check_ajax_referer( 'backwpup_ajax_nonce' ); - - //get mail settings - $emailmethod = 'mail'; - $emailsendmail = ''; - $emailhost = ''; - $emailhostport = ''; - $emailsecure = ''; - $emailuser = ''; - $emailpass = ''; - - if ( empty( $_POST['emailmethod'] ) ) { - //do so if i'm the wp_mail to get the settings + //Generate mail with Swift Mailer + if (!class_exists(\Swift::class, false)) { + require BackWPup::get_plugin_data('plugindir') . '/vendor/SwiftMailer/swift_required.php'; + } + + $mbEncoding = null; + if (function_exists('mb_internal_encoding') && ((int) ini_get('mbstring.func_overload')) & 2) { + $mbEncoding = mb_internal_encoding(); + mb_internal_encoding('ASCII'); + } + + $result = null; + + try { + //Set Temp dir for mailing + Swift_Preferences::getInstance()->setTempDir(untrailingslashit(BackWPup::get_plugin_data('TEMP')))->setCacheType('disk'); + // Create the Transport + if ($emailmethod == 'smtp') { + $transport = Swift_SmtpTransport::newInstance($emailhost, $emailhostport); + if ($emailuser) { + $transport->setUsername($emailuser); + $transport->setPassword($emailpass); + } + if ($emailsecure == 'ssl') { + $transport->setEncryption('ssl'); + } + if ($emailsecure == 'tls') { + $transport->setEncryption('tls'); + } + } elseif ($emailmethod == 'sendmail') { + $transport = Swift_SendmailTransport::newInstance($emailsendmail); + } else { + $job_object->need_free_memory($job_object->backup_filesize * 8); + $transport = Swift_MailTransport::newInstance(); + } + // Create the Mailer using your created Transport + $emailer = Swift_Mailer::newInstance($transport); + + // Create a message + $message = Swift_Message::newInstance(sprintf(__('BackWPup archive from %1$s: %2$s', 'backwpup'), date_i18n('d-M-Y H:i', $job_object->start_time, true), esc_attr($job_object->job['name']))); + $message->setFrom([$job_object->job['emailsndemail'] => $job_object->job['emailsndemailname']]); + $message->setTo($this->get_email_array($job_object->job['emailaddress'])); + $message->setBody(sprintf(__('Backup archive: %s', 'backwpup'), $job_object->backup_file), 'text/plain', strtolower(get_bloginfo('charset'))); + $message->attach(Swift_Attachment::fromPath($job_object->backup_folder . $job_object->backup_file, MimeTypeExtractor::fromFilePath($job_object->backup_folder . $job_object->backup_file))); + // Send the message + $result = $emailer->send($message); + } catch (Exception $e) { + $job_object->log('Swift Mailer: ' . $e->getMessage(), E_USER_ERROR); + } + + if (isset($mbEncoding)) { + mb_internal_encoding($mbEncoding); + } + + if (!isset($result) || !$result) { + $job_object->log(__('Error while sending email!', 'backwpup'), E_USER_ERROR); + + return false; + } + $job_object->substeps_done = 1; + $job_object->log(__('Email sent.', 'backwpup'), E_USER_NOTICE); + + return true; + } + + public function can_run(array $job_settings): bool + { + if (empty($job_settings['emailaddress'])) { + return false; + } + + return !($job_settings['backuptype'] != 'archive'); + } + + /** + * sends test mail. + */ + public function edit_ajax(): void + { + check_ajax_referer('backwpup_ajax_nonce'); + + //get mail settings + $emailmethod = 'mail'; + $emailsendmail = ''; + $emailhost = ''; + $emailhostport = ''; + $emailsecure = ''; + $emailuser = ''; + $emailpass = ''; + + if (empty($_POST['emailmethod'])) { + //do so if i'm the wp_mail to get the settings $phpmailer = $this->getPhpMailer(); - //only if PHPMailer really used - if ( is_object( $phpmailer ) ) { - do_action_ref_array( 'phpmailer_init', array( &$phpmailer ) ); - //get settings from PHPMailer - $emailmethod = $phpmailer->Mailer; - $emailsendmail = $phpmailer->Sendmail; - $emailhost = $phpmailer->Host; - $emailhostport = $phpmailer->Port; - $emailsecure = $phpmailer->SMTPSecure; - $emailuser = $phpmailer->Username; - $emailpass = $phpmailer->Password; - } - } else { - $emailmethod = sanitize_text_field( $_POST['emailmethod'] ); - $emailsendmail = sanitize_email( $_POST['emailsendmail'] ); - $emailhost = sanitize_text_field( $_POST['emailhost'] ); - $emailhostport = absint( $_POST['emailhostport'] ); - $emailsecure = sanitize_text_field( $_POST['emailsecure'] ); - $emailuser = sanitize_text_field( $_POST['emailuser'] ); - $emailpass = BackWPup_Encryption::decrypt( $_POST['emailpass'] ); - } - - //Generate mail with Swift Mailer - if ( ! class_exists( 'Swift', false ) ) { - require BackWPup::get_plugin_data( 'plugindir' ) . '/vendor/SwiftMailer/swift_required.php'; - } - - if ( function_exists( 'mb_internal_encoding' ) && ( (int) ini_get( 'mbstring.func_overload' ) ) & 2 ) { - $mbEncoding = mb_internal_encoding(); - mb_internal_encoding( 'ASCII' ); - } - - try { - // Create the Transport - if ( $emailmethod == 'smtp' ) { - $transport = Swift_SmtpTransport::newInstance( $emailhost, $emailhostport ); - if ( $emailuser ) { - $transport->setUsername( $emailuser ); - $transport->setPassword( $emailpass ); - } - if ( $emailsecure == 'ssl' ) { - $transport->setEncryption( 'ssl' ); - } - if ( $emailsecure == 'tls' ) { - $transport->setEncryption( 'tls' ); - } - } elseif ( $emailmethod == 'sendmail' ) { - $transport = Swift_SendmailTransport::newInstance( $emailsendmail ); - } else { - $transport = Swift_MailTransport::newInstance(); - } - // Create the Mailer using your created Transport - $emailer = Swift_Mailer::newInstance( $transport ); - - // Create a message - $message = Swift_Message::newInstance( __( 'BackWPup archive sending TEST Message', 'backwpup' ) ); - $message->setFrom( array( $_POST['emailsndemail'] => sanitize_email( $_POST['emailsndemailname'] ) ) ); - $message->setTo( $this->get_email_array( $_POST['emailaddress'] ) ); - $message->setBody( __( 'If this message reaches your inbox, sending backup archives via email should work for you.', 'backwpup' ) ); - // Send the message - $result = $emailer->send( $message ); - } catch ( Exception $e ) { - echo 'Swift Mailer: ' . $e->getMessage() . ''; - } - - if ( isset( $mbEncoding ) ) { - mb_internal_encoding( $mbEncoding ); - } - - if ( ! isset( $result ) || ! $result ) { - echo '' . esc_html__( 'Error while sending email!', 'backwpup' ) . ''; - } else { - echo '' . esc_html__( 'Email sent.', 'backwpup' ) . ''; - } - die(); - } - - /** - * Get an array of emails from comma-separated string. - * - * @param string $emailString - * - * @return array - */ - private function get_email_array( $emailString ) { - - $emails = explode( ',', sanitize_text_field( $emailString ) ); - - foreach ( $emails as $key => $email ) { - $emails[ $key ] = sanitize_email( trim( $email ) ); - if ( ! is_email( $emails[ $key ] ) ) { - unset( $emails[ $key ] ); - } - } - - return $emails; - } + //only if PHPMailer really used + if (is_object($phpmailer)) { + do_action_ref_array('phpmailer_init', [&$phpmailer]); + //get settings from PHPMailer + $emailmethod = $phpmailer->Mailer; + $emailsendmail = $phpmailer->Sendmail; + $emailhost = $phpmailer->Host; + $emailhostport = $phpmailer->Port; + $emailsecure = $phpmailer->SMTPSecure; + $emailuser = $phpmailer->Username; + $emailpass = $phpmailer->Password; + } + } else { + $emailmethod = sanitize_text_field($_POST['emailmethod']); + $emailsendmail = sanitize_email($_POST['emailsendmail']); + $emailhost = sanitize_text_field($_POST['emailhost']); + $emailhostport = absint($_POST['emailhostport']); + $emailsecure = sanitize_text_field($_POST['emailsecure']); + $emailuser = sanitize_text_field($_POST['emailuser']); + $emailpass = BackWPup_Encryption::decrypt($_POST['emailpass']); + } + + //Generate mail with Swift Mailer + if (!class_exists(\Swift::class, false)) { + require BackWPup::get_plugin_data('plugindir') . '/vendor/SwiftMailer/swift_required.php'; + } + + $mbEncoding = null; + if (function_exists('mb_internal_encoding') && ((int) ini_get('mbstring.func_overload')) & 2) { + $mbEncoding = mb_internal_encoding(); + mb_internal_encoding('ASCII'); + } + + $result = null; + + try { + // Create the Transport + if ($emailmethod == 'smtp') { + $transport = Swift_SmtpTransport::newInstance($emailhost, $emailhostport); + if ($emailuser) { + $transport->setUsername($emailuser); + $transport->setPassword($emailpass); + } + if ($emailsecure == 'ssl') { + $transport->setEncryption('ssl'); + } + if ($emailsecure == 'tls') { + $transport->setEncryption('tls'); + } + } elseif ($emailmethod == 'sendmail') { + $transport = Swift_SendmailTransport::newInstance($emailsendmail); + } else { + $transport = Swift_MailTransport::newInstance(); + } + // Create the Mailer using your created Transport + $emailer = Swift_Mailer::newInstance($transport); + + // Create a message + $message = Swift_Message::newInstance(__('BackWPup archive sending TEST Message', 'backwpup')); + $message->setFrom([$_POST['emailsndemail'] => sanitize_email($_POST['emailsndemailname'])]); + $message->setTo($this->get_email_array($_POST['emailaddress'])); + $message->setBody(__('If this message reaches your inbox, sending backup archives via email should work for you.', 'backwpup')); + // Send the message + $result = $emailer->send($message); + } catch (Exception $e) { + echo 'Swift Mailer: ' . $e->getMessage() . ''; + } + + if (isset($mbEncoding)) { + mb_internal_encoding($mbEncoding); + } + + if (!isset($result) || !$result) { + echo '' . esc_html__('Error while sending email!', 'backwpup') . ''; + } else { + echo '' . esc_html__('Email sent.', 'backwpup') . ''; + } + + exit(); + } + + /** + * Get an array of emails from comma-separated string. + */ + private function get_email_array(string $emailString): array + { + $emails = explode(',', sanitize_text_field($emailString)); + + foreach ($emails as $key => $email) { + $emails[$key] = sanitize_email(trim($email)); + if (!is_email($emails[$key])) { + unset($emails[$key]); + } + } + + return $emails; + } /** * @return PHPMailer|\PHPMailer\PHPMailer\PHPMailer */ private function getPhpMailer() { - global $phpmailer; + global $phpmailer, $wp_version; if (!is_object($phpmailer) || !$phpmailer instanceof PHPMailer) { - - global $wp_version; if (version_compare($wp_version, '5.5', '>=')) { - require_once ABSPATH . WPINC . '/PHPMailer/PHPMailer.php'; + require_once ABSPATH . WPINC . '/PHPMailer/Exception.php'; return new PHPMailer\PHPMailer\PHPMailer(); } require_once ABSPATH . WPINC . '/class-phpmailer.php'; + require_once ABSPATH . WPINC . '/class-smtp.php'; return new PHPMailer(true); diff --git a/inc/class-destination-folder-downloader.php b/inc/class-destination-folder-downloader.php index 06162f98..9fd5443d 100644 --- a/inc/class-destination-folder-downloader.php +++ b/inc/class-destination-folder-downloader.php @@ -1,146 +1,139 @@ data = $data; - - $this->source_file_handler(); - $this->local_file_handler(); - } - - /** - * Clean up things - */ - public function __destruct() { - - fclose( $this->local_file_handler ); - fclose( $this->source_file_handler ); - } - - /** - * @inheritdoc - */ - public function download_chunk( $start_byte, $end_byte ) { - - if ( ftell( $this->source_file_handler ) !== $start_byte ) { - fseek( $this->source_file_handler, $start_byte ); - } - - $data = fread( $this->source_file_handler, $end_byte - $start_byte + 1 ); - if ( ! $data ) { - throw new Exception( __( 'Could not read data from source file.', 'backwpup' ) ); - } - - $bytes = (int) fwrite( $this->local_file_handler, $data ); - if ( $bytes === 0 ) { - throw new Exception( __( 'Could not write data into target file.', 'backwpup' ) ); - } - } - - /** - * @inheritdoc - */ - public function calculate_size() { - - return filesize( $this->source_backup_file() ); - } - - /** - * Retrieve the file handler for the source file - * - * @return void - */ - private function source_file_handler() { - - if ( is_resource( $this->source_file_handler ) ) { - return; - } - - $file = $this->source_backup_file(); - - $this->source_file_handler = @fopen( $file, 'rb' ); - if ( ! is_resource( $this->source_file_handler ) ) { - throw new \RuntimeException( __( 'File could not be opened for reading.', 'backwpup' ) ); - } - } - - /** - * @return string - */ - private function backup_dir() { - - $backup_dir = esc_attr( BackWPup_Option::get( $this->data->job_id(), self::OPTION_BACKUP_DIR ) ); - $backup_dir = trailingslashit( BackWPup_File::get_absolute_path( $backup_dir ) ); - - return (string) $backup_dir; - } - - /** - * @return string - */ - private function source_backup_file() { - - return (string) realpath( - BackWPup_Sanitize_Path::sanitize_path( - $this->backup_dir() . basename( $this->data->source_file_path() ) - ) - ); - } - - /** - * Retrieve the file handler for the local file - * - * @return void - */ - private function local_file_handler() { - - if ( is_resource( $this->local_file_handler ) ) { - return; - } - - try { - $this->local_file_handler = @fopen( $this->data->local_file_path(), 'wb' ); - } catch ( \RuntimeException $exc ) { - throw new \RuntimeException( __( 'File could not be opened for writing.', 'backwpup' ) ); - } catch ( \LogicException $exc ) { - throw new \RuntimeException( sprintf( - /* translators: $1 is the path of the local file where the backup will be stored */ - __( '%s is a directory not a file.', 'backwpup' ), - $this->data->local_file_path() - ) ); - } - } +final class BackWPup_Destination_Folder_Downloader implements BackWPup_Destination_Downloader_Interface +{ + public const OPTION_BACKUP_DIR = 'backupdir'; + + /** + * @var \BackWpUp_Destination_Downloader_Data + */ + private $data; + + /** + * @var resource + */ + private $source_file_handler; + + /** + * @var resource + */ + private $local_file_handler; + + /** + * BackWPup_Destination_Folder_Downloader constructor. + */ + public function __construct(BackWpUp_Destination_Downloader_Data $data) + { + $this->data = $data; + + $this->source_file_handler(); + $this->local_file_handler(); + } + + /** + * Clean up things. + */ + public function __destruct() + { + fclose($this->local_file_handler); + fclose($this->source_file_handler); + } + + /** + * {@inheritdoc} + */ + public function download_chunk($start_byte, $end_byte) + { + if (ftell($this->source_file_handler) !== $start_byte) { + fseek($this->source_file_handler, $start_byte); + } + + $data = fread($this->source_file_handler, $end_byte - $start_byte + 1); + if (!$data) { + throw new Exception(__('Could not read data from source file.', 'backwpup')); + } + + $bytes = (int) fwrite($this->local_file_handler, $data); + if ($bytes === 0) { + throw new Exception(__('Could not write data into target file.', 'backwpup')); + } + } + + /** + * {@inheritdoc} + */ + public function calculate_size() + { + return filesize($this->source_backup_file()); + } + + /** + * Retrieve the file handler for the source file. + */ + private function source_file_handler() + { + if (is_resource($this->source_file_handler)) { + return; + } + + $file = $this->source_backup_file(); + + $this->source_file_handler = @fopen($file, 'rb'); + if (!is_resource($this->source_file_handler)) { + throw new \RuntimeException(__('File could not be opened for reading.', 'backwpup')); + } + } + + /** + * @return string + */ + private function backup_dir() + { + $backup_dir = esc_attr(BackWPup_Option::get($this->data->job_id(), self::OPTION_BACKUP_DIR)); + $backup_dir = trailingslashit(BackWPup_File::get_absolute_path($backup_dir)); + + return (string) $backup_dir; + } + + /** + * @return string + */ + private function source_backup_file() + { + return (string) realpath( + BackWPup_Sanitize_Path::sanitize_path( + $this->backup_dir() . basename($this->data->source_file_path()) + ) + ); + } + + /** + * Retrieve the file handler for the local file. + */ + private function local_file_handler() + { + if (is_resource($this->local_file_handler)) { + return; + } + + try { + $this->local_file_handler = @fopen($this->data->local_file_path(), 'wb'); + } catch (\RuntimeException $exc) { + throw new \RuntimeException(__('File could not be opened for writing.', 'backwpup')); + } catch (\LogicException $exc) { + throw new \RuntimeException(sprintf( + // translators: $1 is the path of the local file where the backup will be stored + __('%s is a directory not a file.', 'backwpup'), + $this->data->local_file_path() + )); + } + } } diff --git a/inc/class-destination-folder.php b/inc/class-destination-folder.php index 43c101b5..fcd93d93 100644 --- a/inc/class-destination-folder.php +++ b/inc/class-destination-folder.php @@ -1,52 +1,51 @@ 15, 'backupdir' => $backups_dir, 'backupsyncnodelete' => true]; + } - /** - * @return array - */ - public function option_defaults() { - - $upload_dir = wp_upload_dir( null, false, true ); - $backups_dir = trailingslashit( str_replace( '\\', - '/', - $upload_dir['basedir'] ) ) . 'backwpup-' . BackWPup::get_plugin_data( 'hash' ) . '-backups/'; - $content_path = trailingslashit( str_replace( '\\', '/', WP_CONTENT_DIR ) ); - $backups_dir = str_replace( $content_path, '', $backups_dir ); - - return array( 'maxbackups' => 15, 'backupdir' => $backups_dir, 'backupsyncnodelete' => true ); - } - - /** - * @inheritdoc - */ - public function edit_tab( $jobid ) { - - ?> -

+ /** + * {@inheritdoc} + */ + public function edit_tab($jobid): void + { + ?> +

+ for="idbackupdir"> - +
+ if (BackWPup_Option::get($jobid, 'backuptype') === 'archive') { + ?>

Warning: Files belonging to this job are now tracked. Old backup archives which are untracked will not be automatically deleted.', - 'backwpup' - ) - ?> + _e( + 'Warning: Files belonging to this job are now tracked. Old backup archives which are untracked will not be automatically deleted.', + 'backwpup' + ); ?>

- + - +
get_backwpup_directory( $backup_folder ); + } - foreach ( $dir as $file ) { - if ( - $file->isDot() - || $file->isDir() - || $file->isLink() - || in_array( $file->getFilename(), $not_allowed_files, true ) - || ! $this->is_backup_archive( $file->getFilename() ) - ) { - continue; - } - - if ( $file->isReadable() ) { - //file list for backups - $files[ $filecounter ]['folder'] = $backup_folder; - $files[ $filecounter ]['file'] = str_replace( '\\', '/', $file->getPathname() ); - $files[ $filecounter ]['filename'] = $file->getFilename(); - $files[ $filecounter ]['downloadurl'] = add_query_arg( - array( - 'page' => 'backwpupbackups', - 'action' => 'downloadfolder', - 'file' => $file->getFilename(), - 'local_file' => $file->getFilename(), - 'jobid' => $jobid, - ), - network_admin_url( 'admin.php' ) - ); - $files[ $filecounter ]['filesize'] = $file->getSize(); - $files[ $filecounter ]['time'] = $file->getMTime() + ( get_option( 'gmt_offset' ) * 3600 ); - $filecounter ++; - } - } - } - - $files = array_filter( $files ); - - return $files; - } - - /** - * @inheritdoc - */ - public function job_run_archive( BackWPup_Job $job_object ) { - - $job_object->substeps_todo = 1; - if ( ! empty( $job_object->job['jobid'] ) ) { - BackWPup_Option::update( - $job_object->job['jobid'], - 'lastbackupdownloadurl', - add_query_arg( - array( - 'page' => 'backwpupbackups', - 'action' => 'downloadfolder', - 'file' => basename( $job_object->backup_file ), - 'jobid' => $job_object->job['jobid'], - ), - network_admin_url( 'admin.php' ) - ) - ); - } - - // Delete old Backupfiles. - $backupfilelist = array(); - $files = array(); - - if ( is_writable( $job_object->backup_folder ) ) { //make file list - try { - $dir = new BackWPup_Directory( $job_object->backup_folder ); - - foreach ( $dir as $file ) { - if ( $file->isDot() || $file->isDir() || $file->isLink() || ! $file->isWritable() ) { - continue; - } + /** + * {@inheritdoc} + */ + public function edit_form_post_save(int $jobid): void + { + $to_replace = ['//', '\\']; + $backup_dir = trim(sanitize_text_field($_POST['backupdir'])); + $_POST['backupdir'] = trailingslashit(str_replace($to_replace, '/', $backup_dir)); + $max_backups = isset($_POST['maxbackups']) ? absint($_POST['maxbackups']) : 0; + + BackWPup_Option::update($jobid, 'backupdir', $_POST['backupdir']); + BackWPup_Option::update($jobid, 'maxbackups', $max_backups); + BackWPup_Option::update($jobid, 'backupsyncnodelete', !empty($_POST['backupsyncnodelete'])); + } - $is_backup_archive = $this->is_backup_archive( $file->getFilename() ); - $is_owned_by_job = $this->is_backup_owned_by_job( $file->getFilename(), $job_object->job['jobid'] ); - if ( $is_backup_archive && $is_owned_by_job ) { - $backupfilelist[ $file->getMTime() ] = clone $file; - } - } - } catch ( UnexpectedValueException $e ) { - $job_object->log( - sprintf( - esc_html__( 'Could not open path: %s', 'backwpup' ), - $e->getMessage() - ), - E_USER_WARNING - ); - } - } + /** + * {@inheritdoc} + */ + public function file_delete(string $jobdest, string $backupfile): void + { + [$jobid, $dest] = explode('_', $jobdest, 2); - if ( $job_object->job['maxbackups'] > 0 ) { - if ( count( $backupfilelist ) > $job_object->job['maxbackups'] ) { - ksort( $backupfilelist ); - $numdeltefiles = 0; + if (empty($jobid)) { + return; + } - while ( $file = array_shift( $backupfilelist ) ) { - if ( count( $backupfilelist ) < $job_object->job['maxbackups'] ) { - break; - } - unlink( $file->getPathname() ); - foreach ( $files as $key => $filedata ) { - if ( $filedata['file'] === $file->getPathname() ) { - unset( $files[ $key ] ); - } - } - $numdeltefiles ++; - } + $backup_dir = esc_attr(BackWPup_Option::get((int) $jobid, 'backupdir')); + $backup_dir = BackWPup_File::get_absolute_path($backup_dir); - if ( $numdeltefiles > 0 ) { - $job_object->log( sprintf( - _n( 'One backup file deleted', '%d backup files deleted', $numdeltefiles, 'backwpup' ), - $numdeltefiles - ), - E_USER_NOTICE ); - } - } - } + $backupfile = realpath(trailingslashit($backup_dir) . basename($backupfile)); - $job_object->substeps_done ++; + if ($backupfile && is_writeable($backupfile) && !is_dir($backupfile) && !is_link($backupfile)) { + unlink($backupfile); + } + } - return true; - } + /** + * {@inheritdoc} + */ + public function file_get_list(string $jobdest): array + { + [$jobid, $dest] = explode('_', $jobdest, 2); + + $filecounter = 0; + $files = []; + $backup_folder = BackWPup_Option::get($jobid, 'backupdir'); + $backup_folder = BackWPup_File::get_absolute_path($backup_folder); + $not_allowed_files = [ + 'index.php', + '.htaccess', + '.donotbackup', + 'Web.config', + ]; + + if (is_dir($backup_folder)) { + $dir = $this->get_backwpup_directory($backup_folder); + + foreach ($dir as $file) { + if ( + $file->isDot() + || $file->isDir() + || $file->isLink() + || in_array($file->getFilename(), $not_allowed_files, true) + || !$this->is_backup_archive($file->getFilename()) + ) { + continue; + } + + if ($file->isReadable()) { + //file list for backups + $files[$filecounter]['folder'] = $backup_folder; + $files[$filecounter]['file'] = str_replace('\\', '/', $file->getPathname()); + $files[$filecounter]['filename'] = $file->getFilename(); + $files[$filecounter]['downloadurl'] = add_query_arg( + [ + 'page' => 'backwpupbackups', + 'action' => 'downloadfolder', + 'file' => $file->getFilename(), + 'local_file' => $file->getFilename(), + 'jobid' => $jobid, + ], + network_admin_url('admin.php') + ); + $files[$filecounter]['filesize'] = $file->getSize(); + $files[$filecounter]['time'] = $file->getMTime() + (get_option('gmt_offset') * 3600); + ++$filecounter; + } + } + } + + return array_filter($files); + } - /** - * @inheritdoc - */ - public function can_run( array $job_settings ) { + /** + * {@inheritdoc} + */ + public function job_run_archive(BackWPup_Job $job_object): bool + { + $job_object->substeps_todo = 1; + if (!empty($job_object->job['jobid'])) { + BackWPup_Option::update( + $job_object->job['jobid'], + 'lastbackupdownloadurl', + add_query_arg( + [ + 'page' => 'backwpupbackups', + 'action' => 'downloadfolder', + 'file' => basename($job_object->backup_file), + 'jobid' => $job_object->job['jobid'], + ], + network_admin_url('admin.php') + ) + ); + } + + // Delete old Backupfiles. + $backupfilelist = []; + $files = []; + + if (is_writable($job_object->backup_folder)) { //make file list + try { + $dir = new BackWPup_Directory($job_object->backup_folder); + + foreach ($dir as $file) { + if ($file->isDot() || $file->isDir() || $file->isLink() || !$file->isWritable()) { + continue; + } + + $is_backup_archive = $this->is_backup_archive($file->getFilename()); + $is_owned_by_job = $this->is_backup_owned_by_job($file->getFilename(), $job_object->job['jobid']); + if ($is_backup_archive && $is_owned_by_job) { + $backupfilelist[$file->getMTime()] = clone $file; + } + } + } catch (UnexpectedValueException $e) { + $job_object->log( + sprintf( + esc_html__('Could not open path: %s', 'backwpup'), + $e->getMessage() + ), + E_USER_WARNING + ); + } + } + + if ($job_object->job['maxbackups'] > 0) { + if (count($backupfilelist) > $job_object->job['maxbackups']) { + ksort($backupfilelist); + $numdeltefiles = 0; + + while ($file = array_shift($backupfilelist)) { + if (count($backupfilelist) < $job_object->job['maxbackups']) { + break; + } + unlink($file->getPathname()); + + foreach ($files as $key => $filedata) { + if ($filedata['file'] === $file->getPathname()) { + unset($files[$key]); + } + } + ++$numdeltefiles; + } + + if ($numdeltefiles > 0) { + $job_object->log( + sprintf( + _n('One backup file deleted', '%d backup files deleted', $numdeltefiles, 'backwpup'), + $numdeltefiles + ), + E_USER_NOTICE + ); + } + } + } + + ++$job_object->substeps_done; + + return true; + } - if ( empty( $job_settings['backupdir'] ) || $job_settings['backupdir'] == '/' ) { - return false; - } + /** + * {@inheritdoc} + */ + public function can_run(array $job_settings): bool + { + return !(empty($job_settings['backupdir']) || $job_settings['backupdir'] == '/'); + } - return true; - } /** * Returns new instance of BackWPup_Directory. * - * @param string $dir The directory to iterate. - * - * @return BackWPup_Directory + * @param string $dir the directory to iterate */ - protected function get_backwpup_directory( $dir ) { - return new BackWPup_Directory( $dir ); - } - + protected function get_backwpup_directory(string $dir): BackWPup_Directory + { + return new BackWPup_Directory($dir); + } } diff --git a/inc/class-destination-ftp-connect.php b/inc/class-destination-ftp-connect.php index fc56e9e6..bc996271 100644 --- a/inc/class-destination-ftp-connect.php +++ b/inc/class-destination-ftp-connect.php @@ -1,226 +1,223 @@ host = $host; - $this->user = $user; - $this->pass = $pass; - $this->port = $port ?: 21; - $this->timeout = $timeout ?: 90; - $this->use_ssl = function_exists( 'ftp_ssl_connect' ) && $use_ssl; - $this->passive = (bool) $passive; - $this->resource = null; - } - - /** - * @inheritdoc - */ - public function connect() { - - // Don't execute the connection twice. - if ( $this->resource ) { - return $this; - } - - if ( ! function_exists( 'ftp_connect' ) ) { - throw new \BackWPup_Destination_Connect_Exception( - 'Function ftp_connect does not exists. No way to connect to the server.' - ); - } - - // Default connection type. - $resource = ftp_connect( $this->host, $this->port, $this->timeout ); - - // Can be connected over ssl? - if ( $this->use_ssl ) { - $resource = ftp_ssl_connect( $this->host, $this->port, $this->timeout ); - } - - if ( ! $resource ) { - throw new \BackWPup_Destination_Connect_Exception( - 'Something went wrong during FTP connection. Seems not possible to connect to the service.' - ); - } - - $this->resource = $resource; - - if ( ! $this->login() ) { - throw new BackWPup_Destination_Connect_Exception( - 'Something went wrong during FTP connection. Seems not possible to connect to the service.' - ); - } - - ftp_pasv( $this->resource, $this->passive ); - - return $this; - } - - /** - * @inheritdoc - */ - public function resource() { - - return $this->resource; - } - - /** - * Get the FTP URL - * - * @param string $path The path to the FTP file - * - * @return string The URL to the FTP server. - */ - public function getURL( $path = null ) { - - if ( $path !== null && substr( $path, 0, 1 ) != '/' ) { - $path = '/' . $path; - } - - return ( $this->use_ssl ? 'ftps://' : 'ftp://' ) . - rawurlencode( $this->user ) . ':' . rawurlencode( $this->pass ) . - '@' . rawurlencode( $this->host ) . ':' . intval( $this->port ) . $path; - } - - /** - * Login - * - * Perform a login to the service. - * - * @throws BackWPup_Destination_Connect_Exception In case isn't possible to login. - * - * @since 3.5.0 - * - * @return bool True if was able to login, false otherwise. - */ - private function login() { - - $response = false; - - if ( ftp_login( $this->resource, $this->user, $this->pass ) ) { - $response = true; - } - - if ( ! $response ) { - ftp_raw( $this->resource, 'USER ' . $this->user ); - $response = ftp_raw( $this->resource, 'PASS ' . $this->pass ); - } - - // Since the ftp_raw returns an array, we check for false value. - if ( is_array( $response ) && substr( trim( $response[0] ), 0, 3 ) > 400 ) { - $response = false; - } - - return $response; - } +class BackWPup_Destination_Ftp_Connect implements BackWPup_Destination_Connect_Interface +{ + /** + * Host. + * + * @since 3.5.0 + * + * @var string The host to connect + */ + private $host; + + /** + * User. + * + * @since 3.5.0 + * + * @var string The user name + */ + private $user; + + /** + * Pass. + * + * @since 3.5.0 + * + * @var string The user password + */ + private $pass; + + /** + * Port. + * + * @since 3.5.0 + * + * @var int The port where the resource is listen for + */ + private $port; + + /** + * Connection timeout. + * + * @since 3.5.0 + * @see ftp_connect() + * + * @var int The connectin timeout + */ + private $timeout; + + /** + * Use ssl. + * + * @since 3.5.0 + * + * @var bool To use ssl or not + */ + private $use_ssl; + + /** + * Passive. + * + * @since 3.5.0 + * + * @var bool True to set passive mode, false otherwise + */ + private $passive; + + /** + * The Resource or Stream to the service. + * + * @since 3.5.0 + * + * @var mixed Depending on the type of the connection + */ + private $resource; + + /** + * BackWPup_Destination_Ftp_Connect constructor. + * + * @since 3.5.0 + * + * @param string $host the host to connect + * @param string $user the user name for host + * @param string $pass the password for host + * @param int $port the port in which the service is listen for + * @param int $timeout the connection timeout + * @param bool $use_ssl to connect over ssl + * @param bool $passive connetion should be passive or not + */ + public function __construct($host, $user, $pass, $port, $timeout, $use_ssl, $passive) + { + if (!is_string($host) || '' === $host) { + throw new \InvalidArgumentException('Invalid HOST value. The host must be a valid string.'); + } + if (!is_string($user) || '' === $user) { + throw new \InvalidArgumentException('Invalid USER value. The user must be a valid username string.'); + } + if (!is_string($pass) || '' === $pass) { + throw new \InvalidArgumentException('Invalid PASSWORD value. The user must be a valid password string.'); + } + + $this->host = $host; + $this->user = $user; + $this->pass = $pass; + $this->port = $port ?: 21; + $this->timeout = $timeout ?: 90; + $this->use_ssl = function_exists('ftp_ssl_connect') && $use_ssl; + $this->passive = (bool) $passive; + $this->resource = null; + } + + /** + * {@inheritdoc} + */ + public function connect() + { + // Don't execute the connection twice. + if ($this->resource) { + return $this; + } + + if (!function_exists('ftp_connect')) { + throw new \BackWPup_Destination_Connect_Exception( + 'Function ftp_connect does not exists. No way to connect to the server.' + ); + } + + // Default connection type. + $resource = ftp_connect($this->host, $this->port, $this->timeout); + + // Can be connected over ssl? + if ($this->use_ssl) { + $resource = ftp_ssl_connect($this->host, $this->port, $this->timeout); + } + + if (!$resource) { + throw new \BackWPup_Destination_Connect_Exception( + 'Something went wrong during FTP connection. Seems not possible to connect to the service.' + ); + } + + $this->resource = $resource; + + if (!$this->login()) { + throw new BackWPup_Destination_Connect_Exception( + 'Something went wrong during FTP connection. Seems not possible to connect to the service.' + ); + } + + ftp_pasv($this->resource, $this->passive); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function resource() + { + return $this->resource; + } + + /** + * Get the FTP URL. + * + * @param string $path The path to the FTP file + * + * @return string the URL to the FTP server + */ + public function getURL($path = null) + { + if ($path !== null && substr($path, 0, 1) != '/') { + $path = '/' . $path; + } + + return ($this->use_ssl ? 'ftps://' : 'ftp://') . + rawurlencode($this->user) . ':' . rawurlencode($this->pass) . + '@' . rawurlencode($this->host) . ':' . intval($this->port) . $path; + } + + /** + * Login. + * + * Perform a login to the service. + * + * @throws BackWPup_Destination_Connect_Exception in case isn't possible to login + * + * @since 3.5.0 + * + * @return bool true if was able to login, false otherwise + */ + private function login() + { + $response = false; + + if (@ftp_login($this->resource, $this->user, $this->pass)) { + $response = true; + } + + if (!$response) { + ftp_raw($this->resource, 'USER ' . $this->user); + $response = ftp_raw($this->resource, 'PASS ' . $this->pass); + } + + // Since the ftp_raw returns an array, we check for false value. + if (is_array($response) && substr(trim($response[0]), 0, 3) > 400) { + $response = false; + } + + return $response; + } } diff --git a/inc/class-destination-ftp-downloader.php b/inc/class-destination-ftp-downloader.php index df1b0ccc..a766cd94 100644 --- a/inc/class-destination-ftp-downloader.php +++ b/inc/class-destination-ftp-downloader.php @@ -1,149 +1,146 @@ data = $data; - - $this->ftp_resource(); - } - - /** - * Clean up things - */ - public function __destruct() { - - fclose( $this->source_file_handler ); - fclose( $this->local_file_handler ); - } - - /** - * @inheritdoc - */ - public function download_chunk( $start_byte, $end_byte ) { - - $this->source_file_handler( $start_byte ); - $this->local_file_handler( $start_byte ); - - $bytes = (int) stream_copy_to_stream( - $this->source_file_handler, - $this->local_file_handler, - $end_byte - $start_byte + 1, - 0 - ); - - if ( $bytes === 0 ) { - throw new \RuntimeException( __( 'Could not write data to file.', 'backwpup' ) ); - } - } - - /** - * @inheritdoc - */ - public function calculate_size() { - - $resource = $this->ftp_resource - ->connect() - ->resource(); - - $size = ftp_size( $resource, $this->data->source_file_path() ); - ftp_close( $resource ); - - return $size; - } - - /** - * Set the source file handler - * - * @param int $start_byte - */ - private function source_file_handler( $start_byte ) { - - if ( is_resource( $this->source_file_handler ) ) { - return; - } - - $ctx = stream_context_create( array( 'ftp' => array( 'resume_pos' => $start_byte ) ) ); - $url = $this->ftp_resource->getURL( $this->data->source_file_path(), false, $ctx ); - - $this->source_file_handler = fopen( $url, 'r' ); - - if ( ! is_resource( $this->source_file_handler ) ) { - throw new \RuntimeException( __( 'Cannot open FTP file for download.', 'backwpup' ) ); - } - } - - /** - * Set the local file handler - * - * @param int $start_byte - */ - private function local_file_handler( $start_byte ) { - - if ( is_resource( $this->local_file_handler ) ) { - return; - } - - $this->local_file_handler = fopen( $this->data->local_file_path(), $start_byte === 0 ? 'wb' : 'ab' ); - - if ( ! is_resource( $this->local_file_handler ) ) { - throw new \RuntimeException( __( 'File could not be opened for writing.', 'backwpup' ) ); - } - } - - /** - * Set the Ftp resource - * - * @return void - */ - private function ftp_resource() { - - $opts = (object) BackWPup_Option::get_job( $this->data->job_id() ); - - $this->ftp_resource = new BackWPup_Destination_Ftp_Connect( - $opts->ftphost, - $opts->ftpuser, - BackWPup_Encryption::decrypt( $opts->ftppass ), - $opts->ftphostport, - $opts->ftptimeout, - $opts->ftpssl, - $opts->ftppasv - ); - } +final class BackWPup_Destination_Ftp_Downloader implements BackWPup_Destination_Downloader_Interface +{ + /** + * @var \BackWpUp_Destination_Downloader_Data + */ + private $data; + + /** + * @var resource + */ + private $source_file_handler; + + /** + * @var resource + */ + private $local_file_handler; + + /** + * @var BackWPup_Destination_Ftp_Connect + */ + private $ftp_resource; + + /** + * BackWPup_Destination_Ftp_Downloader constructor. + */ + public function __construct(BackWpUp_Destination_Downloader_Data $data) + { + $this->data = $data; + + $this->ftp_resource(); + } + + /** + * Clean up things. + */ + public function __destruct() + { + fclose($this->source_file_handler); + fclose($this->local_file_handler); + } + + /** + * {@inheritdoc} + */ + public function download_chunk($start_byte, $end_byte) + { + $this->source_file_handler($start_byte); + $this->local_file_handler($start_byte); + + $bytes = (int) stream_copy_to_stream( + $this->source_file_handler, + $this->local_file_handler, + $end_byte - $start_byte + 1, + 0 + ); + + if ($bytes === 0) { + throw new \RuntimeException(__('Could not write data to file.', 'backwpup')); + } + } + + /** + * {@inheritdoc} + */ + public function calculate_size() + { + $resource = $this->ftp_resource + ->connect() + ->resource() + ; + + $size = ftp_size($resource, $this->data->source_file_path()); + ftp_close($resource); + + return $size; + } + + /** + * Set the source file handler. + * + * @param int $start_byte + */ + private function source_file_handler($start_byte) + { + if (is_resource($this->source_file_handler)) { + return; + } + + $ctx = stream_context_create([\ftp::class => ['resume_pos' => $start_byte]]); + $url = $this->ftp_resource->getURL($this->data->source_file_path(), false, $ctx); + + $this->source_file_handler = fopen($url, 'r'); + + if (!is_resource($this->source_file_handler)) { + throw new \RuntimeException(__('Cannot open FTP file for download.', 'backwpup')); + } + } + + /** + * Set the local file handler. + * + * @param int $start_byte + */ + private function local_file_handler($start_byte) + { + if (is_resource($this->local_file_handler)) { + return; + } + + $this->local_file_handler = fopen($this->data->local_file_path(), $start_byte === 0 ? 'wb' : 'ab'); + + if (!is_resource($this->local_file_handler)) { + throw new \RuntimeException(__('File could not be opened for writing.', 'backwpup')); + } + } + + /** + * Set the Ftp resource. + */ + private function ftp_resource() + { + $opts = (object) BackWPup_Option::get_job($this->data->job_id()); + + $this->ftp_resource = new BackWPup_Destination_Ftp_Connect( + $opts->ftphost, + $opts->ftpuser, + BackWPup_Encryption::decrypt($opts->ftppass), + $opts->ftphostport, + $opts->ftptimeout, + $opts->ftpssl, + $opts->ftppasv + ); + } } diff --git a/inc/class-destination-ftp.php b/inc/class-destination-ftp.php index 2d9c699d..724edc23 100644 --- a/inc/class-destination-ftp.php +++ b/inc/class-destination-ftp.php @@ -1,770 +1,732 @@ '', - 'ftphostport' => 21, - 'ftptimeout' => 90, - 'ftpuser' => '', - 'ftppass' => '', - 'ftpdir' => trailingslashit( sanitize_title_with_dashes( get_bloginfo( 'name' ) ) ), - 'ftpmaxbackups' => 15, - 'ftppasv' => true, - 'ftpssl' => false, - ); - } - - /** - * Edit Tab - * - * @param int $jobid The job ID. - * - * @return void - */ - public function edit_tab( $jobid ) { - - ?> - -

+class BackWPup_Destination_Ftp extends BackWPup_Destinations +{ + private const FILTER_USEPASVADDRESS = 'backwpup_ftp_use_passive_address'; + + /** + * FTP Connection Resource. + * + * @var resource|null + */ + private $ftp_conn_id; + + public function option_defaults(): array + { + return [ + 'ftphost' => '', + 'ftphostport' => 21, + 'ftptimeout' => 90, + 'ftpuser' => '', + 'ftppass' => '', + 'ftpdir' => trailingslashit(sanitize_title_with_dashes(get_bloginfo('name'))), + 'ftpmaxbackups' => 15, + 'ftppasv' => true, + 'ftpssl' => false, + ]; + } + + public function edit_tab(int $jobid): void + { + ?> + +

- + - + - +
   -
" class="password regular-text" autocomplete="off" />
-

+

+ for="idftpdir"> - +
+ if (BackWPup_Option::get($jobid, 'backuptype') === 'archive') { + ?>

Warning: Files belonging to this job are now tracked. Old backup archives which are untracked will not be automatically deleted.', - 'backwpup' - ) ?>

- + 'Warning: Files belonging to this job are now tracked. Old backup archives which are untracked will not be automatically deleted.', + 'backwpup' + ); ?>

+
-

+

+ for="idftptimeout"> - + - +
+ value="" + class="small-text" />
+ name="ftpssl" />
ftphost, - $job_options->ftpuser, - BackWPup_Encryption::decrypt( $job_options->ftppass ), - $job_options->ftphostport, - $job_options->ftptimeout, - $job_options->ftpssl, - $job_options->ftppasv - ); - - $resource = $service - ->connect() - ->resource(); - - // Delete file. - ftp_delete( $resource, $backupfile ); - - // Update file list of existing files. - foreach ( $files as $key => $file ) { - if ( is_array( $file ) && $file['file'] == $backupfile ) { - unset( $files[ $key ] ); - } - } - - set_site_transient( 'backwpup_' . strtolower( $jobdest ), $files, YEAR_IN_SECONDS ); - } - - /** - * @inheritdoc - */ - public function file_get_list( $jobdest ) { - - $list = (array) get_site_transient( 'backwpup_' . strtolower( $jobdest ) ); - $list = array_filter( $list ); - - return $list; - } - - /** - * File Update List - * - * Update the list of files in the transient. - * - * @param BackWPup_Job|int $job Either the job object or job ID - * @param bool $delete Whether to delete old backups. - */ - public function file_update_list( $job, $delete = false ) { - - if ( $job instanceof BackWPup_Job ) { - $job_object = $job; - $jobid = $job->job['jobid']; - } else { - $job_object = null; - $jobid = $job; - } - - if ( ! $this->ftp_conn_id ) { - $ftp_ssl = BackWPup_Option::get( $jobid, 'ftpssl' ); - if ( ! empty( $ftp_ssl ) - && function_exists( 'ftp_ssl_connect' ) ) { - $ftp_conn_id = ftp_ssl_connect( - BackWPup_Option::get( $jobid, 'ftphost' ), - BackWPup_Option::get( $jobid, 'ftphostport' ), - BackWPup_Option::get( $jobid, 'ftptimeout' ) - ); - } else { //make normal FTP connection if SSL not work - $ftp_conn_id = ftp_connect( - BackWPup_Option::get( $jobid, 'ftphost' ), - BackWPup_Option::get( $jobid, 'ftphostport' ), - BackWPup_Option::get( $jobid, 'ftptimeout' ) - ); - } - - //FTP Login - if ( $loginok = @ftp_login( - $ftp_conn_id, - BackWPup_Option::get( $jobid, 'ftpuser' ), - BackWPup_Encryption::decrypt( BackWPup_Option::get( $jobid, 'ftppass' ) ) - ) ) { - } else { //if PHP ftp login don't work use raw login - $return = ftp_raw( $ftp_conn_id, 'USER ' . BackWPup_Option::get( $jobid, 'ftpuser' ) ); - if ( substr( trim( $return[0] ), 0, 3 ) <= 400 ) { - $return = ftp_raw( - $ftp_conn_id, - 'PASS ' . BackWPup_Encryption::decrypt( BackWPup_Option::get( $jobid, 'ftppass' ) ) - ); - if ( substr( trim( $return[0] ), 0, 3 ) <= 400 ) { - $loginok = true; - } - } - } - - if ( ! $loginok ) { - throw new Exception( __( 'Could not log in to FTP server.', 'backwpup' ) ); - } - - //set actual ftp dir to ftp dir - $ftp_dir = BackWPup_Option::get( $jobid, 'ftpdir' ); - if ( empty( $ftp_dir ) ) { - $ftp_dir = trailingslashit( ftp_pwd( $ftp_conn_id ) ); - } - // prepend actual ftp dir if relative dir - if ( substr( $ftp_dir, 0, 1 ) != '/' ) { - $ftp_dir = trailingslashit( ftp_pwd( $ftp_conn_id ) ) . $ftp_dir; - } - ftp_chdir( $ftp_conn_id, $ftp_dir ); - - if ( BackWPup_Option::get( $jobid, 'ftppasv' ) ) { - ftp_pasv( $ftp_conn_id, true ); - } else { - ftp_pasv( $ftp_conn_id, false ); - } - - } else { - $ftp_conn_id = $this->ftp_conn_id; - $ftp_dir = $job_object->job['ftpdir']; - } - - $backupfilelist = array(); - $filecounter = 0; - $files = array(); - if ( $filelist = ftp_nlist( $ftp_conn_id, '.' ) ) { - foreach ( $filelist as $file ) { - if ( basename( $file ) != '.' && basename( $file ) != '..' ) { - if ( $this->is_backup_archive( $file ) - && $this->is_backup_owned_by_job( - $file, $jobid - ) == true ) { - $time = ftp_mdtm( $ftp_conn_id, $file ); - if ( $time != - 1 ) { - $backupfilelist[ $time ] = basename( $file ); - } else { - $backupfilelist[] = basename( $file ); - } - } - $files[ $filecounter ]['folder'] = 'ftp://' . BackWPup_Option::get( $jobid, 'ftphost' ) . ':' . BackWPup_Option::get( $jobid, 'ftphostport' ) . $ftp_dir; - $files[ $filecounter ]['file'] = trailingslashit( $ftp_dir ) . basename( $file ); - $files[ $filecounter ]['filename'] = basename( $file ); - $files[ $filecounter ]['downloadurl'] = network_admin_url( - 'admin.php?page=backwpupbackups&action=downloadftp&file=' . trailingslashit( $ftp_dir ) . basename( $file ) . '&local_file=' . basename( $file ) . '&jobid=' . $jobid - ); - $files[ $filecounter ]['filesize'] = ftp_size( $ftp_conn_id, $file ); - $files[ $filecounter ]['time'] = ftp_mdtm( $ftp_conn_id, $file ); - $filecounter ++; - } - } - } - - if ( $delete && $job_object && ! empty( $job_object->job['ftpmaxbackups'] ) && $job_object->job['ftpmaxbackups'] > 0 ) { //Delete old backups - if ( count( $backupfilelist ) > $job_object->job['ftpmaxbackups'] ) { - ksort( $backupfilelist ); - $numdeltefiles = 0; - while ( $file = array_shift( $backupfilelist ) ) { - if ( count( $backupfilelist ) < $job_object->job['ftpmaxbackups'] ) { - break; - } - if ( ftp_delete( $ftp_conn_id, $file ) ) { //delete files on ftp - foreach ( $files as $key => $filedata ) { - if ( $filedata['file'] == trailingslashit( $job_object->job['ftpdir'] ) . $file ) { - unset( $files[ $key ] ); - } - } - $numdeltefiles ++; - } else { - $job_object->log( - sprintf( - __( 'Cannot delete "%s" on FTP server!', 'backwpup' ), - $job_object->job['ftpdir'] . $file - ), - E_USER_ERROR - ); - } - } - if ( $numdeltefiles > 0 ) { - $job_object->log( - sprintf( - _n( - 'One file deleted on FTP server', - '%d files deleted on FTP server', - $numdeltefiles, - 'backwpup' - ), - $numdeltefiles - ), - E_USER_NOTICE - ); - } - } - } - set_site_transient( 'backwpup_' . $jobid . '_ftp', $files, YEAR_IN_SECONDS ); - } - - /** - * Run Job - * - * @param BackWPup_Job $job_object The instance of the job to use. - * - * @return bool True on success false on error - */ - public function job_run_archive( BackWPup_Job $job_object ) { - - $job_object->substeps_todo = 2 + $job_object->backup_filesize; - if ( $job_object->steps_data[ $job_object->step_working ]['SAVE_STEP_TRY'] != $job_object->steps_data[ $job_object->step_working ]['STEP_TRY'] ) { - $job_object->log( - sprintf( - __( '%d. Try to send backup file to an FTP server …', 'backwpup' ), - $job_object->steps_data[ $job_object->step_working ]['STEP_TRY'] - ), - E_USER_NOTICE - ); - } - - if ( ! empty( $job_object->job['ftpssl'] ) ) { //make SSL FTP connection - if ( function_exists( 'ftp_ssl_connect' ) ) { - $ftp_conn_id = ftp_ssl_connect( - $job_object->job['ftphost'], - $job_object->job['ftphostport'], - $job_object->job['ftptimeout'] - ); - if ( $ftp_conn_id ) { - $job_object->log( - sprintf( - __( 'Connected via explicit SSL-FTP to server: %s', 'backwpup' ), - $job_object->job['ftphost'] . ':' . $job_object->job['ftphostport'] - ), - E_USER_NOTICE - ); - } else { - $job_object->log( - sprintf( - __( 'Cannot connect via explicit SSL-FTP to server: %s', 'backwpup' ), - $job_object->job['ftphost'] . ':' . $job_object->job['ftphostport'] - ), - E_USER_ERROR - ); - - return false; - } - } else { - $job_object->log( - __( 'PHP function to connect with explicit SSL-FTP to server does not exist!', 'backwpup' ), - E_USER_ERROR - ); - - return true; - } - } else { //make normal FTP connection if SSL not work - $ftp_conn_id = ftp_connect( - $job_object->job['ftphost'], - $job_object->job['ftphostport'], - $job_object->job['ftptimeout'] - ); - if ( $ftp_conn_id ) { - $job_object->log( - sprintf( - __( 'Connected to FTP server: %s', 'backwpup' ), - $job_object->job['ftphost'] . ':' . $job_object->job['ftphostport'] - ), - E_USER_NOTICE - ); - } else { - $job_object->log( - sprintf( - __( 'Cannot connect to FTP server: %s', 'backwpup' ), - $job_object->job['ftphost'] . ':' . $job_object->job['ftphostport'] - ), - E_USER_ERROR - ); - - return false; - } - } - - //FTP Login - $job_object->log( - sprintf( __( 'FTP client command: %s', 'backwpup' ), 'USER ' . $job_object->job['ftpuser'] ), - E_USER_NOTICE - ); - if ( $loginok = @ftp_login( - $ftp_conn_id, - $job_object->job['ftpuser'], - BackWPup_Encryption::decrypt( $job_object->job['ftppass'] ) - ) ) { - $job_object->log( - sprintf( - __( 'FTP server response: %s', 'backwpup' ), - 'User ' . $job_object->job['ftpuser'] . ' logged in.' - ), - E_USER_NOTICE - ); - } else { //if PHP ftp login don't work use raw login - $return = ftp_raw( $ftp_conn_id, 'USER ' . $job_object->job['ftpuser'] ); - $job_object->log( sprintf( __( 'FTP server reply: %s', 'backwpup' ), $return[0] ), E_USER_NOTICE ); - if ( substr( trim( $return[0] ), 0, 3 ) <= 400 ) { - $job_object->log( - sprintf( __( 'FTP client command: %s', 'backwpup' ), 'PASS *******' ), - E_USER_NOTICE - ); - $return = ftp_raw( - $ftp_conn_id, - 'PASS ' . BackWPup_Encryption::decrypt( $job_object->job['ftppass'] ) - ); - if ( substr( trim( $return[0] ), 0, 3 ) <= 400 ) { - $job_object->log( sprintf( __( 'FTP server reply: %s', 'backwpup' ), $return[0] ), E_USER_NOTICE ); - $loginok = true; - } else { - $job_object->log( sprintf( __( 'FTP server reply: %s', 'backwpup' ), $return[0] ), E_USER_ERROR ); - } - } - } - - if ( ! $loginok ) { - return false; - } - - $this->ftp_conn_id = $ftp_conn_id; - - //SYSTYPE - $job_object->log( sprintf( __( 'FTP client command: %s', 'backwpup' ), 'SYST' ), E_USER_NOTICE ); - $systype = ftp_systype( $ftp_conn_id ); - if ( $systype ) { - $job_object->log( sprintf( __( 'FTP server reply: %s', 'backwpup' ), $systype ), E_USER_NOTICE ); - } else { - $job_object->log( - sprintf( __( 'FTP server reply: %s', 'backwpup' ), __( 'Error getting SYSTYPE', 'backwpup' ) ), - E_USER_ERROR - ); - } - - //set actual ftp dir to ftp dir - if ( empty( $job_object->job['ftpdir'] ) ) { - $job_object->job['ftpdir'] = trailingslashit( ftp_pwd( $ftp_conn_id ) ); - } - // prepend actual ftp dir if relative dir - if ( substr( $job_object->job['ftpdir'], 0, 1 ) != '/' ) { - $job_object->job['ftpdir'] = trailingslashit( ftp_pwd( $ftp_conn_id ) ) . $job_object->job['ftpdir']; - } - - //test ftp dir and create it if not exists - if ( $job_object->job['ftpdir'] != '/' ) { - @ftp_chdir( $ftp_conn_id, '/' ); //go to root - $ftpdirs = explode( '/', trim( $job_object->job['ftpdir'], '/' ) ); - - foreach ( $ftpdirs as $ftpdir ) { - if ( empty( $ftpdir ) ) { - continue; - } - - if ( ! @ftp_chdir( $ftp_conn_id, $ftpdir ) ) { - if ( ! $this->create_dir( $ftp_conn_id, $ftpdir, $job_object ) ) { - return false; - } - - ftp_chdir( $ftp_conn_id, $ftpdir ); - } - } - } - - // Get the current working directory - $current_ftp_dir = trailingslashit( ftp_pwd( $ftp_conn_id ) ); - if ( $job_object->substeps_done == 0 ) { - $job_object->log( - sprintf( __( 'FTP current folder is: %s', 'backwpup' ), $current_ftp_dir ), - E_USER_NOTICE - ); - } - - //get file size to resume upload - @clearstatcache(); - $job_object->substeps_done = @ftp_size( $ftp_conn_id, $job_object->job['ftpdir'] . $job_object->backup_file ); - if ( $job_object->substeps_done == - 1 ) { - $job_object->substeps_done = 0; - } - - //PASV - $job_object->log( sprintf( __( 'FTP client command: %s', 'backwpup' ), 'PASV' ), E_USER_NOTICE ); - if ( $job_object->job['ftppasv'] ) { - if ( ftp_pasv( $ftp_conn_id, true ) ) { - $job_object->log( - sprintf( __( 'FTP server reply: %s', 'backwpup' ), __( 'Entering passive mode', 'backwpup' ) ), - E_USER_NOTICE - ); - } else { - $job_object->log( - sprintf( __( 'FTP server reply: %s', 'backwpup' ), __( 'Cannot enter passive mode', 'backwpup' ) ), - E_USER_WARNING - ); - } - } else { - if ( ftp_pasv( $ftp_conn_id, false ) ) { - $job_object->log( - sprintf( __( 'FTP server reply: %s', 'backwpup' ), __( 'Entering normal mode', 'backwpup' ) ), - E_USER_NOTICE - ); - } else { - $job_object->log( - sprintf( __( 'FTP server reply: %s', 'backwpup' ), __( 'Cannot enter normal mode', 'backwpup' ) ), - E_USER_WARNING - ); - } - } - - if ( $job_object->substeps_done < $job_object->backup_filesize ) { - $job_object->log( __( 'Starting upload to FTP  …', 'backwpup' ), E_USER_NOTICE ); - if ( $fp = fopen( $job_object->backup_folder . $job_object->backup_file, 'rb' ) ) { - //go to actual file pos - fseek( $fp, $job_object->substeps_done ); - $ret = ftp_nb_fput( - $ftp_conn_id, - $current_ftp_dir . $job_object->backup_file, - $fp, - FTP_BINARY, - $job_object->substeps_done - ); - while ( $ret == FTP_MOREDATA ) { - $job_object->substeps_done = ftell( $fp ); - $job_object->update_working_data(); - $job_object->do_restart_time(); - $ret = ftp_nb_continue( $ftp_conn_id ); - } - if ( $ret != FTP_FINISHED ) { - $job_object->log( __( 'Cannot transfer backup to FTP server!', 'backwpup' ), E_USER_ERROR ); - - return false; - } else { - $job_object->substeps_done = $job_object->backup_filesize + 1; - $job_object->log( - sprintf( - __( 'Backup transferred to FTP server: %s', 'backwpup' ), - $current_ftp_dir . $job_object->backup_file - ), - E_USER_NOTICE - ); - if ( ! empty( $job_object->job['jobid'] ) ) { - BackWPup_Option::update( - $job_object->job['jobid'], - 'lastbackupdownloadurl', - "ftp://" . $job_object->job['ftpuser'] . ":" . BackWPup_Encryption::decrypt( - $job_object->job['ftppass'] - ) . "@" . $job_object->job['ftphost'] . ':' . $job_object->job['ftphostport'] . $current_ftp_dir . $job_object->backup_file - ); - } - } - fclose( $fp ); - } else { - $job_object->log( __( 'Can not open source file for transfer.', 'backwpup' ), E_USER_ERROR ); - - return false; - } - } - - $this->file_update_list( $job_object, true ); - $job_object->substeps_done ++; - - ftp_close( $ftp_conn_id ); - - return true; - } - - /** - * Can Run - * - * @param array $job_settings The settings of the job. - * - * @return bool True if the job can run, false otherwise - */ - public function can_run( array $job_settings ) { - - if ( empty( $job_settings['ftphost'] ) ) { - return false; - } - - if ( empty( $job_settings['ftpuser'] ) ) { - return false; - } - - if ( empty( $job_settings['ftppass'] ) ) { - return false; - } - - return true; - } - - /** - * Create Dir - * - * Try to create the directory and if not possible, try to change the permissions of the parent, - * then try to create it again. - * - * @param resource $stream The ftp stream pointer. - * @param string $dir The directory to create. - * @param BackWPup_Job $job_object The job instance. - * - * @return bool True on success false on failure - */ - private function create_dir( $stream, $dir, BackWPup_Job $job_object ) { - - // Try to create the directory. - $response = (bool) ftp_mkdir( $stream, $dir ); - - if ( ! $response ) { - // Trying to set the parent directory permissions. - $response = (bool) ftp_chmod( $stream, 0775, './' ); - - if ( ! $response ) { - $job_object->log( - sprintf( - esc_html__( - 'FTP Folder "%s" cannot be created! Parent directory may be not writable.', - 'backwpup' - ), - $dir - ), - E_USER_ERROR - ); - - return $response; - } - - // Try to create the directory for the second time. - $response = (bool) ftp_mkdir( $stream, $dir ); - - if ( ! $response ) { - $job_object->log( - sprintf( - esc_html__( 'FTP Folder "%s" cannot be created!', 'backwpup' ), - $dir - ), - E_USER_ERROR - ); - - return $response; - } - } - - $job_object->log( - sprintf( - esc_html__( 'FTP Folder "%s" created!', 'backwpup' ), - $dir - ), - E_USER_NOTICE - ); - - return $response; - } + } + + public function edit_form_post_save(int $id): void + { + $_POST['ftphost'] = str_replace(['http://', 'ftp://'], '', sanitize_text_field($_POST['ftphost'])); + BackWPup_Option::update($id, 'ftphost', $_POST['ftphost'] ?? ''); + + BackWPup_Option::update( + $id, + 'ftphostport', + !empty($_POST['ftphostport']) ? absint($_POST['ftphostport']) : 21 + ); + BackWPup_Option::update( + $id, + 'ftptimeout', + !empty($_POST['ftptimeout']) ? absint($_POST['ftptimeout']) : 90 + ); + BackWPup_Option::update($id, 'ftpuser', sanitize_text_field($_POST['ftpuser'])); + BackWPup_Option::update($id, 'ftppass', BackWPup_Encryption::encrypt($_POST['ftppass'])); + + if (!empty($_POST['ftpdir'])) { + $_POST['ftpdir'] = trailingslashit( + str_replace('//', '/', str_replace('\\', '/', trim(sanitize_text_field($_POST['ftpdir'])))) + ); + } + BackWPup_Option::update($id, 'ftpdir', $_POST['ftpdir']); + + BackWPup_Option::update( + $id, + 'ftpmaxbackups', + !empty($_POST['ftpmaxbackups']) ? absint($_POST['ftpmaxbackups']) : 0 + ); + + if (function_exists('ftp_ssl_connect')) { + BackWPup_Option::update($id, 'ftpssl', !empty($_POST['ftpssl'])); + } else { + BackWPup_Option::update($id, 'ftpssl', false); + } + + BackWPup_Option::update($id, 'ftppasv', !empty($_POST['ftppasv'])); + } + + public function file_delete(string $jobdest, string $backupfile): void + { + $files = get_site_transient('backwpup_' . strtolower($jobdest)); + [$jobid, $dest] = explode('_', $jobdest); + + $job_options = (object) BackWPup_Option::get_job($jobid); + $service = new BackWPup_Destination_Ftp_Connect( + $job_options->ftphost, + $job_options->ftpuser, + BackWPup_Encryption::decrypt($job_options->ftppass), + $job_options->ftphostport, + $job_options->ftptimeout, + $job_options->ftpssl, + $job_options->ftppasv + ); + + try { + $resource = $service + ->connect() + ->resource() + ; + + // Delete file. + ftp_delete($resource, $backupfile); + + // Update file list of existing files. + foreach ($files as $key => $file) { + if (is_array($file) && $file['file'] == $backupfile) { + unset($files[$key]); + } + } + } catch (\RuntimeException $e) { + BackWPup_Admin::message('FTP: ' . $e->getMessage(), true); + } + + set_site_transient('backwpup_' . strtolower($jobdest), $files, YEAR_IN_SECONDS); + } + + /** + * {@inheritdoc} + */ + public function file_get_list(string $jobdest): array + { + $list = (array) get_site_transient('backwpup_' . strtolower($jobdest)); + + return array_filter($list); + } + + /** + * File Update List. + * + * Update the list of files in the transient. + * + * @param BackWPup_Job|int $job Either the job object or job ID + * @param bool $delete whether to delete old backups + */ + public function file_update_list($job, bool $delete = false): void + { + if ($job instanceof BackWPup_Job) { + $job_object = $job; + $jobid = $job->job['jobid']; + } else { + $job_object = null; + $jobid = $job; + } + + if (!$this->ftp_conn_id) { + $ftp_ssl = BackWPup_Option::get($jobid, 'ftpssl'); + if (!empty($ftp_ssl) + && function_exists('ftp_ssl_connect')) { + $ftp_conn_id = ftp_ssl_connect( + BackWPup_Option::get($jobid, 'ftphost'), + BackWPup_Option::get($jobid, 'ftphostport'), + BackWPup_Option::get($jobid, 'ftptimeout') + ); + } else { //make normal FTP connection if SSL not work + $ftp_conn_id = ftp_connect( + BackWPup_Option::get($jobid, 'ftphost'), + BackWPup_Option::get($jobid, 'ftphostport'), + BackWPup_Option::get($jobid, 'ftptimeout') + ); + } + + //FTP Login + if ($loginok = @ftp_login( + $ftp_conn_id, + BackWPup_Option::get($jobid, 'ftpuser'), + BackWPup_Encryption::decrypt(BackWPup_Option::get($jobid, 'ftppass')) + )) { + } else { //if PHP ftp login don't work use raw login + $return = ftp_raw($ftp_conn_id, 'USER ' . BackWPup_Option::get($jobid, 'ftpuser')); + if (substr(trim($return[0]), 0, 3) <= 400) { + $return = ftp_raw( + $ftp_conn_id, + 'PASS ' . BackWPup_Encryption::decrypt(BackWPup_Option::get($jobid, 'ftppass')) + ); + if (substr(trim($return[0]), 0, 3) <= 400) { + $loginok = true; + } + } + } + + if (!$loginok) { + throw new Exception(__('Could not log in to FTP server.', 'backwpup')); + } + + //set actual ftp dir to ftp dir + $ftp_dir = BackWPup_Option::get($jobid, 'ftpdir'); + if (empty($ftp_dir)) { + $ftp_dir = trailingslashit(ftp_pwd($ftp_conn_id)); + } + // prepend actual ftp dir if relative dir + if (substr($ftp_dir, 0, 1) != '/') { + $ftp_dir = trailingslashit(ftp_pwd($ftp_conn_id)) . $ftp_dir; + } + ftp_chdir($ftp_conn_id, $ftp_dir); + + if (BackWPup_Option::get($jobid, 'ftppasv')) { + ftp_set_option($ftp_conn_id, FTP_USEPASVADDRESS, apply_filters(self::FILTER_USEPASVADDRESS, true)); + ftp_pasv($ftp_conn_id, true); + } else { + ftp_pasv($ftp_conn_id, false); + } + } else { + $ftp_conn_id = $this->ftp_conn_id; + $ftp_dir = $job_object->job['ftpdir']; + } + + $backupfilelist = []; + $filecounter = 0; + $files = []; + if ($filelist = ftp_nlist($ftp_conn_id, '.')) { + foreach ($filelist as $file) { + if (basename($file) != '.' && basename($file) != '..') { + if ($this->is_backup_archive($file) + && $this->is_backup_owned_by_job( + $file, + $jobid + ) == true) { + $time = ftp_mdtm($ftp_conn_id, $file); + if ($time != -1) { + $backupfilelist[$time] = basename($file); + } else { + $backupfilelist[] = basename($file); + } + } + $files[$filecounter]['folder'] = 'ftp://' . BackWPup_Option::get($jobid, 'ftphost') . ':' . BackWPup_Option::get($jobid, 'ftphostport') . $ftp_dir; + $files[$filecounter]['file'] = trailingslashit($ftp_dir) . basename($file); + $files[$filecounter]['filename'] = basename($file); + $files[$filecounter]['downloadurl'] = network_admin_url( + 'admin.php?page=backwpupbackups&action=downloadftp&file=' . trailingslashit($ftp_dir) . basename($file) . '&local_file=' . basename($file) . '&jobid=' . $jobid + ); + $files[$filecounter]['filesize'] = ftp_size($ftp_conn_id, $file); + $files[$filecounter]['time'] = ftp_mdtm($ftp_conn_id, $file); + ++$filecounter; + } + } + } + + if ($delete && $job_object && !empty($job_object->job['ftpmaxbackups']) && $job_object->job['ftpmaxbackups'] > 0) { //Delete old backups + if (count($backupfilelist) > $job_object->job['ftpmaxbackups']) { + ksort($backupfilelist); + $numdeltefiles = 0; + + while ($file = array_shift($backupfilelist)) { + if (count($backupfilelist) < $job_object->job['ftpmaxbackups']) { + break; + } + if (ftp_delete($ftp_conn_id, $file)) { //delete files on ftp + foreach ($files as $key => $filedata) { + if ($filedata['file'] == trailingslashit($job_object->job['ftpdir']) . $file) { + unset($files[$key]); + } + } + ++$numdeltefiles; + } else { + $job_object->log( + sprintf( + __('Cannot delete "%s" on FTP server!', 'backwpup'), + $job_object->job['ftpdir'] . $file + ), + E_USER_ERROR + ); + } + } + if ($numdeltefiles > 0) { + $job_object->log( + sprintf( + _n( + 'One file deleted on FTP server', + '%d files deleted on FTP server', + $numdeltefiles, + 'backwpup' + ), + $numdeltefiles + ), + E_USER_NOTICE + ); + } + } + } + set_site_transient('backwpup_' . $jobid . '_ftp', $files, YEAR_IN_SECONDS); + } + + public function job_run_archive(BackWPup_Job $job_object): bool + { + $job_object->substeps_todo = 2 + $job_object->backup_filesize; + if ($job_object->steps_data[$job_object->step_working]['SAVE_STEP_TRY'] != $job_object->steps_data[$job_object->step_working]['STEP_TRY']) { + $job_object->log( + sprintf( + __('%d. Try to send backup file to an FTP server …', 'backwpup'), + $job_object->steps_data[$job_object->step_working]['STEP_TRY'] + ), + E_USER_NOTICE + ); + } + + if (!empty($job_object->job['ftpssl'])) { //make SSL FTP connection + if (function_exists('ftp_ssl_connect')) { + $ftp_conn_id = ftp_ssl_connect( + $job_object->job['ftphost'], + $job_object->job['ftphostport'], + $job_object->job['ftptimeout'] + ); + if ($ftp_conn_id) { + $job_object->log( + sprintf( + __('Connected via explicit SSL-FTP to server: %s', 'backwpup'), + $job_object->job['ftphost'] . ':' . $job_object->job['ftphostport'] + ), + E_USER_NOTICE + ); + } else { + $job_object->log( + sprintf( + __('Cannot connect via explicit SSL-FTP to server: %s', 'backwpup'), + $job_object->job['ftphost'] . ':' . $job_object->job['ftphostport'] + ), + E_USER_ERROR + ); + + return false; + } + } else { + $job_object->log( + __('PHP function to connect with explicit SSL-FTP to server does not exist!', 'backwpup'), + E_USER_ERROR + ); + + return true; + } + } else { //make normal FTP connection if SSL not work + $ftp_conn_id = ftp_connect( + $job_object->job['ftphost'], + $job_object->job['ftphostport'], + $job_object->job['ftptimeout'] + ); + if ($ftp_conn_id) { + $job_object->log( + sprintf( + __('Connected to FTP server: %s', 'backwpup'), + $job_object->job['ftphost'] . ':' . $job_object->job['ftphostport'] + ), + E_USER_NOTICE + ); + } else { + $job_object->log( + sprintf( + __('Cannot connect to FTP server: %s', 'backwpup'), + $job_object->job['ftphost'] . ':' . $job_object->job['ftphostport'] + ), + E_USER_ERROR + ); + + return false; + } + } + + //FTP Login + $job_object->log( + sprintf(__('FTP client command: %s', 'backwpup'), 'USER ' . $job_object->job['ftpuser']), + E_USER_NOTICE + ); + if ($loginok = @ftp_login( + $ftp_conn_id, + $job_object->job['ftpuser'], + BackWPup_Encryption::decrypt($job_object->job['ftppass']) + )) { + $job_object->log( + sprintf( + __('FTP server response: %s', 'backwpup'), + 'User ' . $job_object->job['ftpuser'] . ' logged in.' + ), + E_USER_NOTICE + ); + } else { //if PHP ftp login don't work use raw login + $return = ftp_raw($ftp_conn_id, 'USER ' . $job_object->job['ftpuser']); + $job_object->log(sprintf(__('FTP server reply: %s', 'backwpup'), $return[0]), E_USER_NOTICE); + if (substr(trim($return[0]), 0, 3) <= 400) { + $job_object->log( + sprintf(__('FTP client command: %s', 'backwpup'), 'PASS *******'), + E_USER_NOTICE + ); + $return = ftp_raw( + $ftp_conn_id, + 'PASS ' . BackWPup_Encryption::decrypt($job_object->job['ftppass']) + ); + if (substr(trim($return[0]), 0, 3) <= 400) { + $job_object->log(sprintf(__('FTP server reply: %s', 'backwpup'), $return[0]), E_USER_NOTICE); + $loginok = true; + } else { + $job_object->log(sprintf(__('FTP server reply: %s', 'backwpup'), $return[0]), E_USER_ERROR); + } + } + } + + if (!$loginok) { + return false; + } + + $this->ftp_conn_id = $ftp_conn_id; + + //SYSTYPE + $job_object->log(sprintf(__('FTP client command: %s', 'backwpup'), 'SYST'), E_USER_NOTICE); + $systype = ftp_systype($ftp_conn_id); + if ($systype) { + $job_object->log(sprintf(__('FTP server reply: %s', 'backwpup'), $systype), E_USER_NOTICE); + } else { + $job_object->log( + sprintf(__('FTP server reply: %s', 'backwpup'), __('Error getting SYSTYPE', 'backwpup')), + E_USER_ERROR + ); + } + + //set actual ftp dir to ftp dir + if (empty($job_object->job['ftpdir'])) { + $job_object->job['ftpdir'] = trailingslashit(ftp_pwd($ftp_conn_id)); + } + // prepend actual ftp dir if relative dir + if (substr($job_object->job['ftpdir'], 0, 1) != '/') { + $job_object->job['ftpdir'] = trailingslashit(ftp_pwd($ftp_conn_id)) . $job_object->job['ftpdir']; + } + + //test ftp dir and create it if not exists + if ($job_object->job['ftpdir'] != '/') { + @ftp_chdir($ftp_conn_id, '/'); //go to root + $ftpdirs = explode('/', trim($job_object->job['ftpdir'], '/')); + + foreach ($ftpdirs as $ftpdir) { + if (empty($ftpdir)) { + continue; + } + + if (!@ftp_chdir($ftp_conn_id, $ftpdir)) { + if (!$this->create_dir($ftp_conn_id, $ftpdir, $job_object)) { + return false; + } + + ftp_chdir($ftp_conn_id, $ftpdir); + } + } + } + + // Get the current working directory + $current_ftp_dir = trailingslashit(ftp_pwd($ftp_conn_id)); + if ($job_object->substeps_done == 0) { + $job_object->log( + sprintf(__('FTP current folder is: %s', 'backwpup'), $current_ftp_dir), + E_USER_NOTICE + ); + } + + //get file size to resume upload + @clearstatcache(); + $job_object->substeps_done = @ftp_size($ftp_conn_id, $job_object->job['ftpdir'] . $job_object->backup_file); + if ($job_object->substeps_done == -1) { + $job_object->substeps_done = 0; + } + + //PASV + $job_object->log(sprintf(__('FTP client command: %s', 'backwpup'), 'PASV'), E_USER_NOTICE); + if ($job_object->job['ftppasv']) { + ftp_set_option($ftp_conn_id, FTP_USEPASVADDRESS, apply_filters(self::FILTER_USEPASVADDRESS, true)); + if (ftp_pasv($ftp_conn_id, true)) { + $job_object->log( + sprintf(__('FTP server reply: %s', 'backwpup'), __('Entering passive mode', 'backwpup')), + E_USER_NOTICE + ); + } else { + $job_object->log( + sprintf(__('FTP server reply: %s', 'backwpup'), __('Cannot enter passive mode', 'backwpup')), + E_USER_WARNING + ); + } + } else { + if (ftp_pasv($ftp_conn_id, false)) { + $job_object->log( + sprintf(__('FTP server reply: %s', 'backwpup'), __('Entering normal mode', 'backwpup')), + E_USER_NOTICE + ); + } else { + $job_object->log( + sprintf(__('FTP server reply: %s', 'backwpup'), __('Cannot enter normal mode', 'backwpup')), + E_USER_WARNING + ); + } + } + + if ($job_object->substeps_done < $job_object->backup_filesize) { + $job_object->log(__('Starting upload to FTP  …', 'backwpup'), E_USER_NOTICE); + if ($fp = fopen($job_object->backup_folder . $job_object->backup_file, 'rb')) { + //go to actual file pos + fseek($fp, $job_object->substeps_done); + $ret = ftp_nb_fput( + $ftp_conn_id, + $current_ftp_dir . $job_object->backup_file, + $fp, + FTP_BINARY, + $job_object->substeps_done + ); + + while ($ret == FTP_MOREDATA) { + $job_object->substeps_done = ftell($fp); + $job_object->update_working_data(); + $job_object->do_restart_time(); + $ret = ftp_nb_continue($ftp_conn_id); + } + if ($ret != FTP_FINISHED) { + $job_object->log(__('Cannot transfer backup to FTP server!', 'backwpup'), E_USER_ERROR); + + return false; + } + $job_object->substeps_done = $job_object->backup_filesize + 1; + $job_object->log( + sprintf( + __('Backup transferred to FTP server: %s', 'backwpup'), + $current_ftp_dir . $job_object->backup_file + ), + E_USER_NOTICE + ); + if (!empty($job_object->job['jobid'])) { + BackWPup_Option::update( + $job_object->job['jobid'], + 'lastbackupdownloadurl', + 'ftp://' . $job_object->job['ftpuser'] . ':' . BackWPup_Encryption::decrypt( + $job_object->job['ftppass'] + ) . '@' . $job_object->job['ftphost'] . ':' . $job_object->job['ftphostport'] . $current_ftp_dir . $job_object->backup_file + ); + } + + fclose($fp); + } else { + $job_object->log(__('Can not open source file for transfer.', 'backwpup'), E_USER_ERROR); + + return false; + } + } + + $this->file_update_list($job_object, true); + ++$job_object->substeps_done; + + ftp_close($ftp_conn_id); + + return true; + } + + public function can_run(array $job_settings): bool + { + if (empty($job_settings['ftphost'])) { + return false; + } + + if (empty($job_settings['ftpuser'])) { + return false; + } + + return !(empty($job_settings['ftppass'])); + } + + /** + * Create a directory. + * + * Try to create the directory and if not possible, try to change the permissions of the parent, + * then try to create it again. + * + * @param resource $stream the ftp stream pointer + */ + private function create_dir($stream, string $dir, BackWPup_Job $job_object): bool + { + // Try to create the directory. + $response = (bool) ftp_mkdir($stream, $dir); + + if (!$response) { + // Trying to set the parent directory permissions. + $response = (bool) ftp_chmod($stream, 0775, './'); + + if (!$response) { + $job_object->log( + sprintf( + esc_html__( + 'FTP Folder "%s" cannot be created! Parent directory may be not writable.', + 'backwpup' + ), + $dir + ), + E_USER_ERROR + ); + + return $response; + } + + // Try to create the directory for the second time. + $response = (bool) ftp_mkdir($stream, $dir); + + if (!$response) { + $job_object->log( + sprintf( + esc_html__('FTP Folder "%s" cannot be created!', 'backwpup'), + $dir + ), + E_USER_ERROR + ); + + return $response; + } + } + + $job_object->log( + sprintf( + esc_html__('FTP Folder "%s" created!', 'backwpup'), + $dir + ), + E_USER_NOTICE + ); + + return $response; + } } diff --git a/inc/class-destination-msazure-downloader.php b/inc/class-destination-msazure-downloader.php index a9c9a9b6..98d26eb0 100644 --- a/inc/class-destination-msazure-downloader.php +++ b/inc/class-destination-msazure-downloader.php @@ -1,10 +1,9 @@ data = $data; } /** - * @inheritDoc + * {@inheritDoc} */ public function download_chunk($start_byte, $end_byte) { @@ -54,7 +50,7 @@ public function download_chunk($start_byte, $end_byte) $this->setLocalFileHandler($start_byte); - $bytes = (int)fwrite($this->local_file_handler, stream_get_contents($blob->getContentStream())); + $bytes = (int) fwrite($this->local_file_handler, stream_get_contents($blob->getContentStream())); if ($bytes === 0) { throw new RuntimeException( sprintf(__('Could not write data to file %s.', 'backwpup'), $this->data->source_file_path()) @@ -63,13 +59,13 @@ public function download_chunk($start_byte, $end_byte) } /** - * @inheritDoc + * {@inheritDoc} */ public function calculate_size() { $client = $this->getBlobClient(); - $blob = $client->getBlob( + $blobProperties = $client->getBlobProperties( BackWPup_Option::get( $this->data->job_id(), MsAzureDestinationConfiguration::MSAZURE_CONTAINER @@ -77,12 +73,14 @@ public function calculate_size() $this->data->source_file_path() ); - return $blob->getProperties()->getContentLength(); + return $blobProperties->getProperties()->getContentLength(); } /** * Sets local_file_handler property by opening the current chunk of the resource. + * * @param int $start_byte + * * @throws RuntimeException */ private function setLocalFileHandler($start_byte) @@ -103,6 +101,7 @@ private function setLocalFileHandler($start_byte) /** * Retrieves the service used to access the blob. + * * @return BlobRestProxy */ private function getBlobClient() diff --git a/inc/class-destination-msazure.php b/inc/class-destination-msazure.php index cb06cd6d..9cce6e52 100755 --- a/inc/class-destination-msazure.php +++ b/inc/class-destination-msazure.php @@ -3,124 +3,142 @@ use Inpsyde\BackWPup\MsAzureDestinationConfiguration; use MicrosoftAzure\Storage\Blob\BlobRestProxy; use MicrosoftAzure\Storage\Blob\Models\Blob; +use MicrosoftAzure\Storage\Blob\Models\BlockList; +use MicrosoftAzure\Storage\Blob\Models\Container; use MicrosoftAzure\Storage\Blob\Models\CreateContainerOptions; +use MicrosoftAzure\Storage\Blob\Models\ListBlobsOptions; use MicrosoftAzure\Storage\Blob\Models\PublicAccessType; /** - * Documentation: http://www.windowsazure.com/en-us/develop/php/how-to-guides/blob-service/ + * Documentation: http://www.windowsazure.com/en-us/develop/php/how-to-guides/blob-service/. */ -class BackWPup_Destination_MSAzure extends BackWPup_Destinations { - - const MSAZUREDIR = 'msazuredir'; - const MSAZUREMAXBACKUPS = 'msazuremaxbackups'; - const MSAZURESYNCNODELETE = 'msazuresyncnodelete'; - const NEWMSAZURECONTAINER = 'newmsazurecontainer'; +class BackWPup_Destination_MSAzure extends BackWPup_Destinations +{ + public const MSAZUREDIR = 'msazuredir'; + public const MSAZUREMAXBACKUPS = 'msazuremaxbackups'; + public const MSAZURESYNCNODELETE = 'msazuresyncnodelete'; + public const NEWMSAZURECONTAINER = 'newmsazurecontainer'; + + public function option_defaults(): array + { + return [MsAzureDestinationConfiguration::MSAZURE_ACCNAME => '', MsAzureDestinationConfiguration::MSAZURE_KEY => '', MsAzureDestinationConfiguration::MSAZURE_CONTAINER => '', self::MSAZUREDIR => trailingslashit(sanitize_file_name(get_bloginfo('name'))), self::MSAZUREMAXBACKUPS => 15, self::MSAZURESYNCNODELETE => true]; + } - /** - * @return array - */ - public function option_defaults() { - - return array( MsAzureDestinationConfiguration::MSAZURE_ACCNAME => '', MsAzureDestinationConfiguration::MSAZURE_KEY => '', MsAzureDestinationConfiguration::MSAZURE_CONTAINER => '', self::MSAZUREDIR => trailingslashit( sanitize_file_name( get_bloginfo( 'name' ) ) ), self::MSAZUREMAXBACKUPS => 15, self::MSAZURESYNCNODELETE => TRUE ); - } - - /** - * @param $jobid - */ - public function edit_tab( $jobid ) { - ?> -

+ public function edit_tab(int $jobid): void + { + ?> +

- + - +
+ value="" class="regular-text" autocomplete="off" />
+ value="" class="regular-text" autocomplete="off" />
-

+

- + - +
- - edit_ajax( array( - MsAzureDestinationConfiguration::MSAZURE_ACCNAME => BackWPup_Option::get( $jobid, MsAzureDestinationConfiguration::MSAZURE_ACCNAME ), - MsAzureDestinationConfiguration::MSAZURE_KEY => BackWPup_Encryption::decrypt( BackWPup_Option::get( $jobid, MsAzureDestinationConfiguration::MSAZURE_KEY ) ), - 'msazureselected' => BackWPup_Option::get( $jobid, MsAzureDestinationConfiguration::MSAZURE_CONTAINER ) - ) ); ?> + + edit_ajax([ + MsAzureDestinationConfiguration::MSAZURE_ACCNAME => BackWPup_Option::get($jobid, MsAzureDestinationConfiguration::MSAZURE_ACCNAME), + MsAzureDestinationConfiguration::MSAZURE_KEY => BackWPup_Encryption::decrypt(BackWPup_Option::get($jobid, MsAzureDestinationConfiguration::MSAZURE_KEY)), + 'msazureselected' => BackWPup_Option::get($jobid, MsAzureDestinationConfiguration::MSAZURE_CONTAINER), + ]); + } ?>
-

+

- + - +
- +
+ if (BackWPup_Option::get($jobid, 'backuptype') === 'archive') { + ?> -

Warning: Files belonging to this job are now tracked. Old backup archives which are untracked will not be automatically deleted.', 'backwpup' ) ?>

- +

Warning: Files belonging to this job are now tracked. Old backup archives which are untracked will not be automatically deleted.', 'backwpup'); ?>

+
msazureConfiguration(); } catch (\UnexpectedValueException $exception) { BackWPup_Admin::message(__('Microsoft Azure Configuration: ', 'backwpup') . $exception->getMessage(), true); + return; } + if ($msazureConfiguration->isNew()) { + try { + $this->createContainer($msazureConfiguration); + + BackWPup_Admin::message( + sprintf( + __('MS Azure container "%s" created.', 'backwpup'), + esc_html(sanitize_text_field($msazureConfiguration->msazurecontainer())) + ) + ); + } catch (Exception $e) { + BackWPup_Admin::message(sprintf(__('MS Azure container create: %s', 'backwpup'), $e->getMessage()), true); + + return; + } + } + BackWPup_Option::update( $jobid, MsAzureDestinationConfiguration::MSAZURE_ACCNAME, @@ -152,52 +170,17 @@ public function edit_form_post_save( $jobid ) { self::MSAZURESYNCNODELETE, filter_input(INPUT_POST, self::MSAZURESYNCNODELETE, FILTER_SANITIZE_STRING) ?: '' ); + } - $newmsazurecontainer = filter_input( - INPUT_POST, - self::NEWMSAZURECONTAINER, - FILTER_SANITIZE_STRING - ); - - if ($newmsazurecontainer) { - try { - $this->createContainer( - $newmsazurecontainer, - $msazureConfiguration - ); - - BackWPup_Admin::message( - sprintf( - __('MS Azure container "%s" created.', 'backwpup'), - esc_html(sanitize_text_field($newmsazurecontainer)) - ) - ); - } catch ( Exception $e ) { - BackWPup_Admin::message( sprintf( __( 'MS Azure container create: %s', 'backwpup' ), $e->getMessage() ), TRUE ); - return; - } - - BackWPup_Option::update( - $jobid, - MsAzureDestinationConfiguration::MSAZURE_CONTAINER, - sanitize_text_field($newmsazurecontainer) - ); - } - } - - /** - * @param $jobdest - * @param $backupfile - */ - public function file_delete( $jobdest, $backupfile ) { - - $files = get_site_transient( 'backwpup_'. strtolower( $jobdest ) ); - list( $jobid, $dest ) = explode( '_', $jobdest ); + public function file_delete(string $jobdest, string $backupfile): void + { + $files = get_site_transient('backwpup_' . strtolower($jobdest)); + [$jobid, $dest] = explode('_', $jobdest); if (BackWPup_Option::get($jobid, MsAzureDestinationConfiguration::MSAZURE_ACCNAME) && BackWPup_Option::get($jobid, MsAzureDestinationConfiguration::MSAZURE_KEY) && BackWPup_Option::get($jobid, MsAzureDestinationConfiguration::MSAZURE_CONTAINER)) { - try { + try { $blobClient = $this->createBlobClient( BackWPup_Option::get($jobid, MsAzureDestinationConfiguration::MSAZURE_ACCNAME), BackWPup_Encryption::decrypt( @@ -215,87 +198,83 @@ public function file_delete( $jobdest, $backupfile ) { ); //update file list - foreach ( $files as $key => $file ) { - if ( is_array( $file ) && $file[ 'file' ] == $backupfile ) - unset( $files[ $key ] ); - } - } - catch ( Exception $e ) { - BackWPup_Admin::message( 'MS AZURE: ' . $e->getMessage(), TRUE ); - } - } - - set_site_transient( 'backwpup_' . strtolower( $jobdest ), $files, YEAR_IN_SECONDS ); - } - - /** - * @inheritdoc - */ - public function file_get_list( $jobdest ) { + foreach ($files as $key => $file) { + if (is_array($file) && $file['file'] == $backupfile) { + unset($files[$key]); + } + } + } catch (Exception $e) { + BackWPup_Admin::message('MS AZURE: ' . $e->getMessage(), true); + } + } - $list = (array) get_site_transient( 'backwpup_' . strtolower( $jobdest ) ); - $list = array_filter( $list ); + set_site_transient('backwpup_' . strtolower($jobdest), $files, YEAR_IN_SECONDS); + } - return $list; - } + /** + * {@inheritdoc} + */ + public function file_get_list(string $jobdest): array + { + $list = (array) get_site_transient('backwpup_' . strtolower($jobdest)); - /** - * @param $job_object - * @return bool - */ - public function job_run_archive( BackWPup_Job $job_object ) { + return array_filter($list); + } - $job_object->substeps_todo = $job_object->backup_filesize + 2; + public function job_run_archive(BackWPup_Job $job_object): bool + { + $job_object->substeps_todo = $job_object->backup_filesize + 2; - if ( $job_object->steps_data[ $job_object->step_working ]['SAVE_STEP_TRY'] != $job_object->steps_data[ $job_object->step_working ][ 'STEP_TRY' ] ) - $job_object->log( sprintf( __( '%d. Try sending backup to a Microsoft Azure (Blob) …', 'backwpup' ), $job_object->steps_data[ $job_object->step_working ][ 'STEP_TRY' ] ), E_USER_NOTICE ); + if ($job_object->steps_data[$job_object->step_working]['SAVE_STEP_TRY'] != $job_object->steps_data[$job_object->step_working]['STEP_TRY']) { + $job_object->log(sprintf(__('%d. Try sending backup to a Microsoft Azure (Blob) …', 'backwpup'), $job_object->steps_data[$job_object->step_working]['STEP_TRY']), E_USER_NOTICE); + } - try { + try { $blobRestProxy = $this->createBlobClient( $job_object->job[MsAzureDestinationConfiguration::MSAZURE_ACCNAME], BackWPup_Encryption::decrypt($job_object->job[MsAzureDestinationConfiguration::MSAZURE_KEY]) ); - if ( $job_object->steps_data[ $job_object->step_working ]['SAVE_STEP_TRY'] != $job_object->steps_data[ $job_object->step_working ][ 'STEP_TRY' ] ) { - - //test vor existing container + if ($job_object->steps_data[$job_object->step_working]['SAVE_STEP_TRY'] != $job_object->steps_data[$job_object->step_working]['STEP_TRY']) { + //test vor existing container $containers = $this->getContainers($blobRestProxy); - $job_object->steps_data[ $job_object->step_working ][ 'container_url' ] = ''; - foreach( $containers as $container ) { - if ( $container->getName() == $job_object->job[ MsAzureDestinationConfiguration::MSAZURE_CONTAINER ] ) { - $job_object->steps_data[ $job_object->step_working ][ 'container_url' ] = $container->getUrl(); - break; - } - } + $job_object->steps_data[$job_object->step_working]['container_url'] = ''; - if ( ! $job_object->steps_data[ $job_object->step_working ][ 'container_url' ] ) { - $job_object->log( sprintf( __( 'MS Azure container "%s" does not exist!', 'backwpup'), $job_object->job[ MsAzureDestinationConfiguration::MSAZURE_CONTAINER ] ), E_USER_ERROR ); + foreach ($containers as $container) { + if ($container->getName() == $job_object->job[MsAzureDestinationConfiguration::MSAZURE_CONTAINER]) { + $job_object->steps_data[$job_object->step_working]['container_url'] = $container->getUrl(); + break; + } + } - return TRUE; - } else { - $job_object->log( sprintf( __( 'Connected to MS Azure container "%s".', 'backwpup'), $job_object->job[ MsAzureDestinationConfiguration::MSAZURE_CONTAINER ] ), E_USER_NOTICE ); - } + if (!$job_object->steps_data[$job_object->step_working]['container_url']) { + $job_object->log(sprintf(__('MS Azure container "%s" does not exist!', 'backwpup'), $job_object->job[MsAzureDestinationConfiguration::MSAZURE_CONTAINER]), E_USER_ERROR); - $job_object->log( __( 'Starting upload to MS Azure …', 'backwpup' ), E_USER_NOTICE ); - } + return true; + } + $job_object->log(sprintf(__('Connected to MS Azure container "%s".', 'backwpup'), $job_object->job[MsAzureDestinationConfiguration::MSAZURE_CONTAINER]), E_USER_NOTICE); - //Prepare Upload - if ( $file_handel = fopen( $job_object->backup_folder . $job_object->backup_file, 'rb' ) ) { - fseek( $file_handel, $job_object->substeps_done ); + $job_object->log(__('Starting upload to MS Azure …', 'backwpup'), E_USER_NOTICE); + } - if ( empty( $job_object->steps_data[ $job_object->step_working ][ 'BlockList' ] ) ) { - $job_object->steps_data[ $job_object->step_working ][ 'BlockList' ] = array(); - } + //Prepare Upload + $file_handel = null; + if ($file_handel = fopen($job_object->backup_folder . $job_object->backup_file, 'rb')) { + fseek($file_handel, $job_object->substeps_done); + + if (empty($job_object->steps_data[$job_object->step_working]['BlockList'])) { + $job_object->steps_data[$job_object->step_working]['BlockList'] = []; + } - while ( ! feof( $file_handel ) ) { - $data = fread( $file_handel, 1048576 * 4 ); //4MB - if ( strlen( $data ) == 0 ) { - continue; - } - $chunk_upload_start = microtime( TRUE ); - $block_count = count( $job_object->steps_data[ $job_object->step_working ][ 'BlockList' ] ) + 1; - $block_id = base64_encode(str_pad($block_count, 6, "0", STR_PAD_LEFT)); + while (!feof($file_handel)) { + $data = fread($file_handel, 1048576 * 4); //4MB + if (strlen($data) == 0) { + continue; + } + $chunk_upload_start = microtime(true); + $block_count = count($job_object->steps_data[$job_object->step_working]['BlockList']) + 1; + $block_id = base64_encode(str_pad($block_count, 6, '0', STR_PAD_LEFT)); $blobRestProxy->createBlobBlock( $job_object->job[MsAzureDestinationConfiguration::MSAZURE_CONTAINER], @@ -304,53 +283,52 @@ public function job_run_archive( BackWPup_Job $job_object ) { $data ); - $job_object->steps_data[ $job_object->step_working ][ 'BlockList' ][] = $block_id; - $chunk_upload_time = microtime( TRUE ) - $chunk_upload_start; - $job_object->substeps_done = $job_object->substeps_done + strlen( $data ); - $time_remaining = $job_object->do_restart_time(); - if ( $time_remaining < $chunk_upload_time ) { - $job_object->do_restart_time( TRUE ); - } - $job_object->update_working_data(); - } - fclose( $file_handel ); - } else { - $job_object->log( __( 'Can not open source file for transfer.', 'backwpup' ), E_USER_ERROR ); - return FALSE; - } + $job_object->steps_data[$job_object->step_working]['BlockList'][] = $block_id; + $chunk_upload_time = microtime(true) - $chunk_upload_start; + $job_object->substeps_done = $job_object->substeps_done + strlen($data); + $time_remaining = $job_object->do_restart_time(); + if ($time_remaining < $chunk_upload_time) { + $job_object->do_restart_time(true); + } + $job_object->update_working_data(); + } + fclose($file_handel); + } else { + $job_object->log(__('Can not open source file for transfer.', 'backwpup'), E_USER_ERROR); + + return false; + } $blocklist = $this->createBlockList(); - foreach( $job_object->steps_data[ $job_object->step_working ][ 'BlockList' ] as $block_id ) { - $blocklist->addUncommittedEntry( $block_id ); - } - unset( $job_object->steps_data[ $job_object->step_working ][ 'BlockList' ] ); - - //Commit Blocks - $blobRestProxy->commitBlobBlocks( $job_object->job[ MsAzureDestinationConfiguration::MSAZURE_CONTAINER ], $job_object->job[self::MSAZUREDIR] . $job_object->backup_file, $blocklist->getEntries() ); - - $job_object->substeps_done ++; - $job_object->log( sprintf( __( 'Backup transferred to %s', 'backwpup' ), $job_object->steps_data[ $job_object->step_working ][ 'container_url' ] . '/' . $job_object->job[self::MSAZUREDIR] . $job_object->backup_file ), E_USER_NOTICE ); - if ( !empty( $job_object->job[ 'jobid' ] ) ) { - BackWPup_Option::update( $job_object->job[ 'jobid' ] , 'lastbackupdownloadurl', network_admin_url( 'admin.php' ) . '?page=backwpupbackups&action=downloadmsazure&file=' . $job_object->job[self::MSAZUREDIR] . $job_object->backup_file . '&jobid=' . $job_object->job[ 'jobid' ] ); - } - } - catch ( Exception $e ) { - $job_object->log( E_USER_ERROR, sprintf( __( 'Microsoft Azure API: %s', 'backwpup' ), $e->getMessage() ), $e->getFile(), $e->getLine() ); - $job_object->substeps_done = 0; - unset( $job_object->steps_data[ $job_object->step_working ][ 'BlockList' ] ); - if ( isset( $file_handel ) && is_resource( $file_handel ) ) - fclose( $file_handel ); + foreach ($job_object->steps_data[$job_object->step_working]['BlockList'] as $block_id) { + $blocklist->addUncommittedEntry($block_id); + } + unset($job_object->steps_data[$job_object->step_working]['BlockList']); - return FALSE; - } + //Commit Blocks + $blobRestProxy->commitBlobBlocks($job_object->job[MsAzureDestinationConfiguration::MSAZURE_CONTAINER], $job_object->job[self::MSAZUREDIR] . $job_object->backup_file, $blocklist->getEntries()); + ++$job_object->substeps_done; + $job_object->log(sprintf(__('Backup transferred to %s', 'backwpup'), $job_object->steps_data[$job_object->step_working]['container_url'] . '/' . $job_object->job[self::MSAZUREDIR] . $job_object->backup_file), E_USER_NOTICE); + if (!empty($job_object->job['jobid'])) { + BackWPup_Option::update($job_object->job['jobid'], 'lastbackupdownloadurl', network_admin_url('admin.php') . '?page=backwpupbackups&action=downloadmsazure&file=' . $job_object->job[self::MSAZUREDIR] . $job_object->backup_file . '&jobid=' . $job_object->job['jobid']); + } + } catch (Exception $e) { + $job_object->log(E_USER_ERROR, sprintf(__('Microsoft Azure API: %s', 'backwpup'), $e->getMessage()), $e->getFile(), $e->getLine()); + $job_object->substeps_done = 0; + unset($job_object->steps_data[$job_object->step_working]['BlockList']); + if (isset($file_handel) && is_resource($file_handel)) { + fclose($file_handel); + } - try { + return false; + } - $backupfilelist = array(); - $filecounter = 0; - $files = array(); + try { + $backupfilelist = []; + $filecounter = 0; + $files = []; $blob_options = $this->createListBlobsOptions(); $blob_options->setPrefix($job_object->job[self::MSAZUREDIR]); @@ -361,73 +339,73 @@ public function job_run_archive( BackWPup_Job $job_object ) { $blob_options ); - if ( is_array( $blobs ) ) { - foreach ( $blobs as $blob ) { - $file = basename( $blob->getName() ); - if ( $this->is_backup_archive( $file ) && $this->is_backup_owned_by_job( $file, $job_object->job['jobid'] ) == true ) - $backupfilelist[ $blob->getProperties()->getLastModified()->getTimestamp() ] = $file; - $files[ $filecounter ][ 'folder' ] = $job_object->steps_data[ $job_object->step_working ][ 'container_url' ] . "/" . dirname( $blob->getName() ) . "/"; - $files[ $filecounter ][ 'file' ] = $blob->getName(); - $files[ $filecounter ][ 'filename' ] = basename( $blob->getName() ); - $files[ $filecounter ][ 'downloadurl' ] = network_admin_url( 'admin.php' ) . '?page=backwpupbackups&action=downloadmsazure&file=' . $blob->getName() . '&jobid=' . $job_object->job[ 'jobid' ]; - $files[ $filecounter ][ 'filesize' ] = $blob->getProperties()->getContentLength(); - $files[ $filecounter ][ 'time' ] = $blob->getProperties()->getLastModified()->getTimestamp() + ( get_option( 'gmt_offset' ) * 3600 ); - $filecounter ++; - } - } - // Delete old backups - if ( ! empty ($job_object->job[self::MSAZUREMAXBACKUPS] ) && $job_object->job[self::MSAZUREMAXBACKUPS] > 0 ) { - if ( count( $backupfilelist ) > $job_object->job[self::MSAZUREMAXBACKUPS] ) { - ksort( $backupfilelist ); - $numdeltefiles = 0; - while ( $file = array_shift( $backupfilelist ) ) { - if ( count( $backupfilelist ) < $job_object->job[self::MSAZUREMAXBACKUPS] ) - break; - $blobRestProxy->deleteBlob( $job_object->job[ MsAzureDestinationConfiguration::MSAZURE_CONTAINER ], $job_object->job[self::MSAZUREDIR] . $file ); - foreach ( $files as $key => $filedata ) { - if ( $filedata[ 'file' ] == $job_object->job[self::MSAZUREDIR] . $file ) - unset( $files[ $key ] ); - } - $numdeltefiles ++; - } - if ( $numdeltefiles > 0 ) - $job_object->log( sprintf( _n( 'One file deleted on Microsoft Azure container.', '%d files deleted on Microsoft Azure container.', $numdeltefiles, 'backwpup' ), $numdeltefiles ), E_USER_NOTICE ); - - } - } - set_site_transient( 'backwpup_' . $job_object->job[ 'jobid' ] . '_msazure', $files, YEAR_IN_SECONDS ); - } - catch ( Exception $e ) { - $job_object->log( E_USER_ERROR, sprintf( __( 'Microsoft Azure API: %s', 'backwpup' ), $e->getMessage() ), $e->getFile(), $e->getLine() ); - - return FALSE; - } - - $job_object->substeps_done = $job_object->backup_filesize + 2; + if (is_array($blobs)) { + foreach ($blobs as $blob) { + $file = basename($blob->getName()); + if ($this->is_backup_archive($file) && $this->is_backup_owned_by_job($file, $job_object->job['jobid']) == true) { + $backupfilelist[$blob->getProperties()->getLastModified()->getTimestamp()] = $file; + } + $files[$filecounter]['folder'] = $job_object->steps_data[$job_object->step_working]['container_url'] . '/' . dirname($blob->getName()) . '/'; + $files[$filecounter]['file'] = $blob->getName(); + $files[$filecounter]['filename'] = basename($blob->getName()); + $files[$filecounter]['downloadurl'] = network_admin_url('admin.php') . '?page=backwpupbackups&action=downloadmsazure&file=' . $blob->getName() . '&jobid=' . $job_object->job['jobid']; + $files[$filecounter]['filesize'] = $blob->getProperties()->getContentLength(); + $files[$filecounter]['time'] = $blob->getProperties()->getLastModified()->getTimestamp() + (get_option('gmt_offset') * 3600); + ++$filecounter; + } + } + // Delete old backups + if (!empty($job_object->job[self::MSAZUREMAXBACKUPS]) && $job_object->job[self::MSAZUREMAXBACKUPS] > 0) { + if (count($backupfilelist) > $job_object->job[self::MSAZUREMAXBACKUPS]) { + ksort($backupfilelist); + $numdeltefiles = 0; + + while ($file = array_shift($backupfilelist)) { + if (count($backupfilelist) < $job_object->job[self::MSAZUREMAXBACKUPS]) { + break; + } + $blobRestProxy->deleteBlob($job_object->job[MsAzureDestinationConfiguration::MSAZURE_CONTAINER], $job_object->job[self::MSAZUREDIR] . $file); + + foreach ($files as $key => $filedata) { + if ($filedata['file'] == $job_object->job[self::MSAZUREDIR] . $file) { + unset($files[$key]); + } + } + ++$numdeltefiles; + } + if ($numdeltefiles > 0) { + $job_object->log(sprintf(_n('One file deleted on Microsoft Azure container.', '%d files deleted on Microsoft Azure container.', $numdeltefiles, 'backwpup'), $numdeltefiles), E_USER_NOTICE); + } + } + } + set_site_transient('backwpup_' . $job_object->job['jobid'] . '_msazure', $files, YEAR_IN_SECONDS); + } catch (Exception $e) { + $job_object->log(E_USER_ERROR, sprintf(__('Microsoft Azure API: %s', 'backwpup'), $e->getMessage()), $e->getFile(), $e->getLine()); - return TRUE; - } + return false; + } - /** - * @param $job_settings array - * @return bool - */ - public function can_run( array $job_settings ) { + $job_object->substeps_done = $job_object->backup_filesize + 2; - if ( empty( $job_settings[ MsAzureDestinationConfiguration::MSAZURE_ACCNAME ] ) ) - return FALSE; + return true; + } - if ( empty( $job_settings[ MsAzureDestinationConfiguration::MSAZURE_KEY ]) ) - return FALSE; + public function can_run(array $job_settings): bool + { + if (empty($job_settings[MsAzureDestinationConfiguration::MSAZURE_ACCNAME])) { + return false; + } - if ( empty( $job_settings[ MsAzureDestinationConfiguration::MSAZURE_CONTAINER ] ) ) - return FALSE; + if (empty($job_settings[MsAzureDestinationConfiguration::MSAZURE_KEY])) { + return false; + } - return TRUE; - } + return !(empty($job_settings[MsAzureDestinationConfiguration::MSAZURE_CONTAINER])); + } - public function edit_inline_js() { - ?> + public function edit_inline_js(): void + { + ?> '; + } + echo ''; + + $containers = null; - if ( ! empty( $args[ MsAzureDestinationConfiguration::MSAZURE_ACCNAME ] ) && ! empty( $args[ MsAzureDestinationConfiguration::MSAZURE_KEY ] ) ) { - try { + if (!empty($args[MsAzureDestinationConfiguration::MSAZURE_ACCNAME]) && !empty($args[MsAzureDestinationConfiguration::MSAZURE_KEY])) { + try { $blobClient = $this->createBlobClient( $args[MsAzureDestinationConfiguration::MSAZURE_ACCNAME], BackWPup_Encryption::decrypt($args[MsAzureDestinationConfiguration::MSAZURE_KEY]) ); $containers = $blobClient->listContainers()->getContainers(); - } - catch ( Exception $e ) { - $error = $e->getMessage(); - } - } - - if ( empty( $args[ MsAzureDestinationConfiguration::MSAZURE_ACCNAME ] ) ) - _e( 'Missing account name!', 'backwpup' ); - elseif ( empty( $args[ MsAzureDestinationConfiguration::MSAZURE_KEY ] ) ) - _e( 'Missing access key!', 'backwpup' ); - elseif ( ! empty( $error ) ) - echo esc_html( $error ); - elseif ( empty( $containers ) ) - _e( 'No container found!', 'backwpup' ); - echo ''; - - if ( !empty( $containers ) ) { - echo ''; - } - if ( $ajax ) - die(); - else - return; - } + } catch (Exception $e) { + $error = $e->getMessage(); + } + } + + if (empty($args[MsAzureDestinationConfiguration::MSAZURE_ACCNAME])) { + _e('Missing account name!', 'backwpup'); + } elseif (empty($args[MsAzureDestinationConfiguration::MSAZURE_KEY])) { + _e('Missing access key!', 'backwpup'); + } elseif (!empty($error)) { + echo esc_html($error); + } elseif (empty($containers)) { + _e('No container found!', 'backwpup'); + } + echo ''; + + if (!empty($containers)) { + echo ''; + } + if ($ajax) { + exit(); + } + } /** * Creates the service used to access the blob. - * @param string $accountName - * @param string $accountKey - * @return BlobRestProxy */ - public function createBlobClient($accountName, $accountKey) + public function createBlobClient(string $accountName, string $accountKey): BlobRestProxy { $connectionString = 'DefaultEndpointsProtocol=https;AccountName=' . $accountName . ';AccountKey=' . $accountKey; @@ -543,10 +517,7 @@ public function createBlobClient($accountName, $accountKey) return BlobRestProxy::createBlobService($connectionString); } - /** - * @return MsAzureDestinationConfiguration - */ - protected function msazureConfiguration() + protected function msazureConfiguration(): MsAzureDestinationConfiguration { $msazureaccname = filter_input(INPUT_POST, MsAzureDestinationConfiguration::MSAZURE_ACCNAME, FILTER_SANITIZE_STRING); $msazurekey = filter_input(INPUT_POST, MsAzureDestinationConfiguration::MSAZURE_KEY, FILTER_SANITIZE_STRING); @@ -556,6 +527,20 @@ protected function msazureConfiguration() FILTER_SANITIZE_STRING ); + if (!$msazurecontainer) { + $newmsazurecontainer = filter_input( + INPUT_POST, + self::NEWMSAZURECONTAINER, + FILTER_SANITIZE_STRING + ); + + return MsAzureDestinationConfiguration::withNewContainer( + $msazureaccname, + $msazurekey, + $newmsazurecontainer + ); + } + return new MsAzureDestinationConfiguration( $msazureaccname, $msazurekey, @@ -563,14 +548,8 @@ protected function msazureConfiguration() ); } - /** - * @param string $name - * @param MsAzureDestinationConfiguration $configuration - */ - protected function createContainer( - $name, - $configuration - ) { + protected function createContainer(MsAzureDestinationConfiguration $configuration): void + { $blobClient = $this->createBlobClient( $configuration->msazureaccname(), $configuration->msazurekey() @@ -580,23 +559,17 @@ protected function createContainer( $createContainerOptions->setPublicAccess(PublicAccessType::NONE); $blobClient->createContainer( - $name, + $configuration->msazurecontainer(), $createContainerOptions ); } - protected function createContainerOptionsFactory() + protected function createContainerOptionsFactory(): CreateContainerOptions { return new CreateContainerOptions(); } - /** - * @param BlobRestProxy $blobClient - * @param string $container - * @param string $backupfile - * @return void - */ - protected function deleteBlob($blobClient, $container, $backupfile) + protected function deleteBlob(BlobRestProxy $blobClient, string $container, string $backupfile): void { $blobClient->deleteBlob( $container, @@ -605,12 +578,9 @@ protected function deleteBlob($blobClient, $container, $backupfile) } /** - * @param BlobRestProxy $blobClient - * @param string $container - * @param \MicrosoftAzure\Storage\Blob\Models\ListBlobsOptions $options * @return Blob[] */ - protected function getBlobs($blobClient, $container, $options) + protected function getBlobs(BlobRestProxy $blobClient, string $container, ListBlobsOptions $options): array { return $blobClient->listBlobs( $container, @@ -619,38 +589,24 @@ protected function getBlobs($blobClient, $container, $options) } /** - * @param BlobRestProxy $blobClient - * @return \MicrosoftAzure\Storage\Blob\Models\Container[] + * @return Container[] */ - protected function getContainers($blobClient) + protected function getContainers(BlobRestProxy $blobClient): array { return $blobClient->listContainers()->getContainers(); } - /** - * @return \MicrosoftAzure\Storage\Blob\Models\BlockList - */ - protected function createBlockList() + protected function createBlockList(): BlockList { - $blocklist = new MicrosoftAzure\Storage\Blob\Models\BlockList(); - - return $blocklist; + return new BlockList(); } - /** - * @return \MicrosoftAzure\Storage\Blob\Models\ListBlobsOptions - */ - protected function createListBlobsOptions() + protected function createListBlobsOptions(): ListBlobsOptions { - $blob_options = new MicrosoftAzure\Storage\Blob\Models\ListBlobsOptions(); - - return $blob_options; + return new ListBlobsOptions(); } - /** - * @return false|string - */ - protected function msazureDir() + protected function msazureDir(): string { $msazureDir = trailingslashit( str_replace( @@ -679,15 +635,18 @@ protected function msazureDir() /** * It extracts the job id from a job destination string. + * * @param string $jobDestination String containing a job destination, ex. 1_SOME_DESTINATION. - * @return int + * * @throws RuntimeException + * + * @return int */ - protected function extractJobIdFromDestination($jobDestination) + protected function extractJobIdFromDestination(string $jobDestination) { $jobId = intval(substr($jobDestination, 0, strpos($jobDestination, '_', 1))); - if (!$jobId || $jobId === 0) { + if (!$jobId) { throw new RuntimeException( sprintf(__('Could not extract job id from destination %s.', 'backwpup'), $jobDestination) ); diff --git a/inc/class-destination-rsc.php b/inc/class-destination-rsc.php index 307b01fe..62814816 100755 --- a/inc/class-destination-rsc.php +++ b/inc/class-destination-rsc.php @@ -3,405 +3,386 @@ // http://www.rackspace.com/cloud/files/ // https://github.com/rackspace/php-opencloud -use \Inpsyde\BackWPupShared\File\MimeTypeExtractor; - -class BackWPup_Destination_RSC extends BackWPup_Destinations { - - - /** - * @return array - */ - public function option_defaults() { - - return array( 'rscusername' => '', 'rscapikey' => '', 'rsccontainer' => '', 'rscregion' => 'DFW', 'rscdir' => trailingslashit( sanitize_file_name( get_bloginfo( 'name' ) ) ), 'rscmaxbackups' => 15, 'rscsyncnodelete' => TRUE ); - } - - /** - * Get Auht url by region code - * - * @param $region string region code - * @return string - */ - public static function get_auth_url_by_region( $region ) { - - $region = strtoupper( $region ); - - if ( $region === 'LON' ) { - return RACKSPACE_UK; - } - - return RACKSPACE_US; - } - - /** - * @param $jobid - */ - public function edit_tab( $jobid ) { - ?> -

+use Inpsyde\BackWPupShared\File\MimeTypeExtractor; +use OpenCloud\Rackspace; + +class BackWPup_Destination_RSC extends BackWPup_Destinations +{ + public function option_defaults(): array + { + return ['rscusername' => '', 'rscapikey' => '', 'rsccontainer' => '', 'rscregion' => 'DFW', 'rscdir' => trailingslashit(sanitize_file_name(get_bloginfo('name'))), 'rscmaxbackups' => 15, 'rscsyncnodelete' => true]; + } + + /** + * Get Auth url by region code. + * + * @param string $region Region code + */ + public static function get_auth_url_by_region(string $region): string + { + $region = strtoupper($region); + + if ($region === 'LON') { + return RACKSPACE_UK; + } + + return RACKSPACE_US; + } + + public function edit_tab(int $jobid): void + { + ?> +

- + - +
- +
- +
-

+

- + - + - +
- + + + + + +
- - edit_ajax( array( - 'rscusername' => BackWPup_Option::get( $jobid, 'rscusername' ), - 'rscregion' => BackWPup_Option::get( $jobid, 'rscregion' ), - 'rscapikey' => BackWPup_Encryption::decrypt( BackWPup_Option::get( $jobid, 'rscapikey' ) ), - 'rscselected' => BackWPup_Option::get( $jobid, 'rsccontainer' ) - ) ); ?> + + edit_ajax([ + 'rscusername' => BackWPup_Option::get($jobid, 'rscusername'), + 'rscregion' => BackWPup_Option::get($jobid, 'rscregion'), + 'rscapikey' => BackWPup_Encryption::decrypt(BackWPup_Option::get($jobid, 'rscapikey')), + 'rscselected' => BackWPup_Option::get($jobid, 'rsccontainer'), + ]); + } ?>
-

+

- + - +
- +
+ if (BackWPup_Option::get($jobid, 'backuptype') === 'archive') { + ?> -

Warning: Files belonging to this job are now tracked. Old backup archives which are untracked will not be automatically deleted.', 'backwpup' ) ?>

- +

Warning: Files belonging to this job are now tracked. Old backup archives which are untracked will not be automatically deleted.', 'backwpup'); ?>

+
$_POST[ 'rscusername' ], - 'apiKey' => $_POST[ 'rscapikey' ] - ), - array( - 'ssl.certificate_authority' => BackWPup::get_plugin_data('cacert') - ) + } + + public function edit_form_post_save(int $id): void + { + BackWPup_Option::update($id, 'rscusername', sanitize_text_field($_POST['rscusername'])); + BackWPup_Option::update($id, 'rscapikey', sanitize_text_field($_POST['rscapikey'])); + BackWPup_Option::update($id, 'rsccontainer', isset($_POST['rsccontainer']) ? sanitize_text_field($_POST['rsccontainer']) : ''); + BackWPup_Option::update($id, 'rscregion', !empty($_POST['rscregion']) ? sanitize_text_field($_POST['rscregion']) : 'DFW'); + + $_POST['rscdir'] = trailingslashit(str_replace('//', '/', str_replace('\\', '/', trim(sanitize_text_field($_POST['rscdir']))))); + if (substr($_POST['rscdir'], 0, 1) === '/') { + $_POST['rscdir'] = substr($_POST['rscdir'], 1); + } + if ($_POST['rscdir'] === '/') { + $_POST['rscdir'] = ''; + } + BackWPup_Option::update($id, 'rscdir', $_POST['rscdir']); + + BackWPup_Option::update($id, 'rscmaxbackups', !empty($_POST['rscmaxbackups']) ? absint($_POST['rscmaxbackups']) : 0); + BackWPup_Option::update($id, 'rscsyncnodelete', !empty($_POST['rscsyncnodelete'])); + + if (!empty($_POST['rscusername']) && !empty($_POST['rscapikey']) && !empty($_POST['newrsccontainer'])) { + try { + $conn = new Rackspace( + self::get_auth_url_by_region($_POST['rscregion']), + [ + 'username' => $_POST['rscusername'], + 'apiKey' => $_POST['rscapikey'], + ], + [ + 'ssl.certificate_authority' => BackWPup::get_plugin_data('cacert'), + ] ); - $ostore = $conn->objectStoreService( 'cloudFiles', sanitize_text_field( $_POST[ 'rscregion' ] ), 'publicURL' ); - $ostore->createContainer( $_POST[ 'newrsccontainer' ] ); - BackWPup_Option::update( $id, 'rsccontainer', sanitize_text_field( $_POST[ 'newrsccontainer' ] ) ); - BackWPup_Admin::message( sprintf( __( 'Rackspace Cloud container "%s" created.', 'backwpup' ), esc_html( sanitize_text_field( $_POST[ 'newrsccontainer' ] ) ) ) ); - - } - catch ( Exception $e ) { - BackWPup_Admin::message( sprintf( __( 'Rackspace Cloud API: %s', 'backwpup' ), $e->getMessage() ), TRUE ); - } - } - } - - /** - * @param $jobdest - * @param $backupfile - */ - public function file_delete( $jobdest, $backupfile ) { - - $files = get_site_transient( 'backwpup_'. strtolower( $jobdest ) ); - list( $jobid, $dest ) = explode( '_', $jobdest ); - - if ( BackWPup_Option::get( $jobid, 'rscusername' ) && BackWPup_Option::get( $jobid, 'rscapikey' ) && BackWPup_Option::get( $jobid, 'rsccontainer' ) ) { - try { - $conn = new OpenCloud\Rackspace( - self::get_auth_url_by_region( BackWPup_Option::get( $jobid, 'rscregion' ) ), - array( - 'username' => BackWPup_Option::get( $jobid, 'rscusername' ), - 'apiKey' => BackWPup_Encryption::decrypt( BackWPup_Option::get( $jobid, 'rscapikey' ) ) - ), - array( - 'ssl.certificate_authority' => BackWPup::get_plugin_data('cacert') - ) + $ostore = $conn->objectStoreService('cloudFiles', sanitize_text_field($_POST['rscregion']), 'publicURL'); + $ostore->createContainer($_POST['newrsccontainer']); + BackWPup_Option::update($id, 'rsccontainer', sanitize_text_field($_POST['newrsccontainer'])); + BackWPup_Admin::message(sprintf(__('Rackspace Cloud container "%s" created.', 'backwpup'), esc_html(sanitize_text_field($_POST['newrsccontainer'])))); + } catch (Exception $e) { + BackWPup_Admin::message(sprintf(__('Rackspace Cloud API: %s', 'backwpup'), $e->getMessage()), true); + } + } + } + + public function file_delete(string $jobdest, string $backupfile): void + { + $files = get_site_transient('backwpup_' . strtolower($jobdest)); + [$jobid, $dest] = explode('_', $jobdest); + + if (BackWPup_Option::get($jobid, 'rscusername') && BackWPup_Option::get($jobid, 'rscapikey') && BackWPup_Option::get($jobid, 'rsccontainer')) { + try { + $conn = new Rackspace( + self::get_auth_url_by_region(BackWPup_Option::get($jobid, 'rscregion')), + [ + 'username' => BackWPup_Option::get($jobid, 'rscusername'), + 'apiKey' => BackWPup_Encryption::decrypt(BackWPup_Option::get($jobid, 'rscapikey')), + ], + [ + 'ssl.certificate_authority' => BackWPup::get_plugin_data('cacert'), + ] ); - $ostore = $conn->objectStoreService( 'cloudFiles' , BackWPup_Option::get( $jobid, 'rscregion' ), 'publicURL'); - $container = $ostore->getContainer( BackWPup_Option::get( $jobid, 'rsccontainer' ) ); - $fileobject = $container->getObject( $backupfile ); - $fileobject->delete(); - //update file list - foreach ( $files as $key => $file ) { - if ( is_array( $file ) && $file[ 'file' ] == $backupfile ) - unset( $files[ $key ] ); - } - - } - catch ( Exception $e ) { - BackWPup_Admin::message( 'RSC: ' . $e->getMessage(), TRUE ); - } - } - - set_site_transient( 'backwpup_'. strtolower( $jobdest ), $files, YEAR_IN_SECONDS ); - } - - /** - * @param $jobid - * @param $get_file - * @param $local_file_path - */ - public function file_download( $jobid, $get_file, $local_file_path = null ) { - - try { - $conn = new OpenCloud\Rackspace( - self::get_auth_url_by_region( BackWPup_Option::get( $jobid, 'rscregion' ) ), - array( - 'username' => BackWPup_Option::get( $jobid, 'rscusername' ), - 'apiKey' => BackWPup_Encryption::decrypt( BackWPup_Option::get( $jobid, 'rscapikey' ) ) - ), - array( - 'ssl.certificate_authority' => BackWPup::get_plugin_data('cacert') - ) + $ostore = $conn->objectStoreService('cloudFiles', BackWPup_Option::get($jobid, 'rscregion'), 'publicURL'); + $container = $ostore->getContainer(BackWPup_Option::get($jobid, 'rsccontainer')); + $fileobject = $container->getObject($backupfile); + $fileobject->delete(); + //update file list + foreach ($files as $key => $file) { + if (is_array($file) && $file['file'] == $backupfile) { + unset($files[$key]); + } + } + } catch (Exception $e) { + BackWPup_Admin::message('RSC: ' . $e->getMessage(), true); + } + } + + set_site_transient('backwpup_' . strtolower($jobdest), $files, YEAR_IN_SECONDS); + } + + public function file_download(int $jobid, string $get_file, ?string $local_file_path = null): void + { + try { + $conn = new Rackspace( + self::get_auth_url_by_region(BackWPup_Option::get($jobid, 'rscregion')), + [ + 'username' => BackWPup_Option::get($jobid, 'rscusername'), + 'apiKey' => BackWPup_Encryption::decrypt(BackWPup_Option::get($jobid, 'rscapikey')), + ], + [ + 'ssl.certificate_authority' => BackWPup::get_plugin_data('cacert'), + ] ); - $ostore = $conn->objectStoreService( 'cloudFiles' , BackWPup_Option::get( $jobid, 'rscregion' ), 'publicURL'); - $container = $ostore->getContainer( BackWPup_Option::get( $jobid, 'rsccontainer' ) ); - $backupfile = $container->getObject( $get_file ); - if ( $level = ob_get_level() ) { - for ( $i = 0; $i < $level; $i ++ ) { - ob_end_clean(); - } - } - @set_time_limit( 300 ); - nocache_headers(); - header( 'Content-Description: File Transfer' ); - header( 'Content-Type: ' . MimeTypeExtractor::fromFilePath( $get_file ) ); - header( 'Content-Disposition: attachment; filename="' . basename( $get_file ) . '"' ); - header( 'Content-Transfer-Encoding: binary' ); - header( 'Content-Length: ' . $backupfile->getContentLength() ); - echo $backupfile->getContent(); - die(); - } - catch ( Exception $e ) { - die( $e->getMessage() ); - } - } - - /** - * @inheritdoc - */ - public function file_get_list( $jobdest ) { - - $list = (array) get_site_transient( 'backwpup_' . strtolower( $jobdest ) ); - $list = array_filter( $list ); - - return $list; - } - - /** - * @param $job_object BAckWPup_Job - * @return bool - */ - public function job_run_archive( BackWPup_Job $job_object ) { - - $job_object->substeps_todo = 2 + $job_object->backup_filesize; - $job_object->substeps_done = 0; - $job_object->log( sprintf( __( '%d. Trying to send backup file to Rackspace cloud …', 'backwpup' ), $job_object->steps_data[ $job_object->step_working ][ 'STEP_TRY' ] ), E_USER_NOTICE ); - - try { - - $conn = new OpenCloud\Rackspace( - self::get_auth_url_by_region( $job_object->job[ 'rscregion' ] ), - array( - 'username' => $job_object->job[ 'rscusername' ], - 'apiKey' => BackWPup_Encryption::decrypt( $job_object->job[ 'rscapikey' ] ) - ), - array( - 'ssl.certificate_authority' => BackWPup::get_plugin_data('cacert') - ) + $ostore = $conn->objectStoreService('cloudFiles', BackWPup_Option::get($jobid, 'rscregion'), 'publicURL'); + $container = $ostore->getContainer(BackWPup_Option::get($jobid, 'rsccontainer')); + $backupfile = $container->getObject($get_file); + if ($level = ob_get_level()) { + for ($i = 0; $i < $level; ++$i) { + ob_end_clean(); + } + } + @set_time_limit(300); + nocache_headers(); + header('Content-Description: File Transfer'); + header('Content-Type: ' . MimeTypeExtractor::fromFilePath($get_file)); + header('Content-Disposition: attachment; filename="' . basename($get_file) . '"'); + header('Content-Transfer-Encoding: binary'); + header('Content-Length: ' . $backupfile->getContentLength()); + echo $backupfile->getContent(); + + exit(); + } catch (Exception $e) { + exit($e->getMessage()); + } + } + + /** + * {@inheritdoc} + */ + public function file_get_list(string $jobdest): array + { + $list = (array) get_site_transient('backwpup_' . strtolower($jobdest)); + + return array_filter($list); + } + + public function job_run_archive(BackWPup_Job $job_object): bool + { + $job_object->substeps_todo = 2 + $job_object->backup_filesize; + $job_object->substeps_done = 0; + $job_object->log(sprintf(__('%d. Trying to send backup file to Rackspace cloud …', 'backwpup'), $job_object->steps_data[$job_object->step_working]['STEP_TRY']), E_USER_NOTICE); + + try { + $conn = new Rackspace( + self::get_auth_url_by_region($job_object->job['rscregion']), + [ + 'username' => $job_object->job['rscusername'], + 'apiKey' => BackWPup_Encryption::decrypt($job_object->job['rscapikey']), + ], + [ + 'ssl.certificate_authority' => BackWPup::get_plugin_data('cacert'), + ] ); - //connect to cloud files - $ostore = $conn->objectStoreService( 'cloudFiles' , $job_object->job[ 'rscregion' ], 'publicURL' ); - - $container = $ostore->getContainer( $job_object->job[ 'rsccontainer' ] ); - $job_object->log( sprintf(__( 'Connected to Rackspace cloud files container %s', 'backwpup' ), $job_object->job[ 'rsccontainer' ] ) ); - } - catch ( Exception $e ) { - $job_object->log( E_USER_ERROR, sprintf( __( 'Rackspace Cloud API: %s', 'backwpup' ), $e->getMessage() ), $e->getFile(), $e->getLine() ); - - return FALSE; - } - - - try { - //Transfer Backup to Rackspace Cloud - $job_object->substeps_done = 0; - $job_object->log( __( 'Upload to Rackspace cloud started …', 'backwpup' ), E_USER_NOTICE ); - - if ( $handle = fopen( $job_object->backup_folder . $job_object->backup_file, 'rb' ) ) { - $uploded = $container->uploadObject( $job_object->job[ 'rscdir' ] . $job_object->backup_file, $handle ); - fclose( $handle ); - } else { - $job_object->log( __( 'Can not open source file for transfer.', 'backwpup' ), E_USER_ERROR ); - return FALSE; - } - -// $transfer = $container->setupObjectTransfer( array( -// 'name' => $job_object->job[ 'rscdir' ] . $job_object->backup_file, -// 'path' => $job_object->backup_folder . $job_object->backup_file, -// 'concurrency' => 1, -// 'partSize' => 4 * 1024 * 1024 -// ) ); -// $uploded = $transfer->upload(); - - if ( $uploded ) { - $job_object->log( __( 'Backup File transferred to RSC://', 'backwpup' ) . $job_object->job[ 'rsccontainer' ] . '/' . $job_object->job[ 'rscdir' ] . $job_object->backup_file, E_USER_NOTICE ); - $job_object->substeps_done = 1 + $job_object->backup_filesize; - if ( ! empty( $job_object->job[ 'jobid' ] ) ) { - BackWPup_Option::update( $job_object->job[ 'jobid' ], 'lastbackupdownloadurl', network_admin_url( 'admin.php' ) . '?page=backwpupbackups&action=downloadrsc&file=' . $job_object->job[ 'rscdir' ] . $job_object->backup_file . '&jobid=' . $job_object->job[ 'jobid' ] ); - } - } else { - $job_object->log( __( 'Cannot transfer backup to Rackspace cloud.', 'backwpup' ), E_USER_ERROR ); - - return FALSE; - } - } - catch ( Exception $e ) { - $job_object->log( E_USER_ERROR, sprintf( __( 'Rackspace Cloud API: %s', 'backwpup' ), $e->getMessage() ), $e->getFile(), $e->getLine() ); - - return FALSE; - } - - try { - $backupfilelist = array(); - $filecounter = 0; - $files = array(); - $objlist = $container->objectList( array( 'prefix' => $job_object->job[ 'rscdir' ] ) ); - while ( $object = $objlist->next() ) { - $file = basename( $object->getName() ); - if ( $job_object->job[ 'rscdir' ] . $file == $object->getName() ) { //only in the folder and not in complete bucket - if ( $this->is_backup_archive( $file ) && $this->is_backup_owned_by_job( $file, $job_object->job['jobid'] ) == true ) - $backupfilelist[ strtotime( $object->getLastModified() ) ] = $object; - } - $files[ $filecounter ][ 'folder' ] = "RSC://" . $job_object->job[ 'rsccontainer' ] . "/" . dirname( $object->getName() ) . "/"; - $files[ $filecounter ][ 'file' ] = $object->getName(); - $files[ $filecounter ][ 'filename' ] = basename( $object->getName() ); - $files[ $filecounter ][ 'downloadurl' ] = network_admin_url( 'admin.php' ) . '?page=backwpupbackups&action=downloadrsc&file=' . $object->getName() . '&jobid=' . $job_object->job[ 'jobid' ]; - $files[ $filecounter ][ 'filesize' ] = $object->getContentLength(); - $files[ $filecounter ][ 'time' ] = strtotime( $object->getLastModified() ); - $filecounter ++; - } - if ( ! empty( $job_object->job[ 'rscmaxbackups' ] ) && $job_object->job[ 'rscmaxbackups' ] > 0 ) { //Delete old backups - if ( count( $backupfilelist ) > $job_object->job[ 'rscmaxbackups' ] ) { - ksort( $backupfilelist ); - $numdeltefiles = 0; - while ( $file = array_shift( $backupfilelist ) ) { - if ( count( $backupfilelist ) < $job_object->job[ 'rscmaxbackups' ] ) - break; - foreach ( $files as $key => $filedata ) { - if ( $filedata[ 'file' ] == $file->getName() ) - unset( $files[ $key ] ); - } - $file->delete(); - $numdeltefiles ++; - } - if ( $numdeltefiles > 0 ) - $job_object->log( sprintf( _n( 'One file deleted on Rackspace cloud container.', '%d files deleted on Rackspace cloud container.', $numdeltefiles, 'backwpup' ), $numdeltefiles ), E_USER_NOTICE ); - } - } - set_site_transient( 'backwpup_' . $job_object->job[ 'jobid' ] . '_rsc', $files, YEAR_IN_SECONDS ); - } - catch ( Exception $e ) { - $job_object->log( E_USER_ERROR, sprintf( __( 'Rackspace Cloud API: %s', 'backwpup' ), $e->getMessage() ), $e->getFile(), $e->getLine() ); - - return FALSE; - } - $job_object->substeps_done ++; - - return TRUE; - } - - /** - * @param $job_settings array - * @return bool - */ - public function can_run( array $job_settings ) { - - if ( empty( $job_settings[ 'rscusername'] ) ) - return FALSE; - - if ( empty( $job_settings[ 'rscapikey'] ) ) - return FALSE; - - if ( empty( $job_settings[ 'rsccontainer'] ) ) - return FALSE; - - return TRUE; - } - - public function edit_inline_js() { - ?> + //connect to cloud files + $ostore = $conn->objectStoreService('cloudFiles', $job_object->job['rscregion'], 'publicURL'); + + $container = $ostore->getContainer($job_object->job['rsccontainer']); + $job_object->log(sprintf(__('Connected to Rackspace cloud files container %s', 'backwpup'), $job_object->job['rsccontainer'])); + } catch (Exception $e) { + $job_object->log(E_USER_ERROR, sprintf(__('Rackspace Cloud API: %s', 'backwpup'), $e->getMessage()), $e->getFile(), $e->getLine()); + + return false; + } + + try { + //Transfer Backup to Rackspace Cloud + $job_object->substeps_done = 0; + $job_object->log(__('Upload to Rackspace cloud started …', 'backwpup'), E_USER_NOTICE); + + if ($handle = fopen($job_object->backup_folder . $job_object->backup_file, 'rb')) { + $uploded = $container->uploadObject($job_object->job['rscdir'] . $job_object->backup_file, $handle); + fclose($handle); + } else { + $job_object->log(__('Can not open source file for transfer.', 'backwpup'), E_USER_ERROR); + + return false; + } + + // $transfer = $container->setupObjectTransfer( array( + // 'name' => $job_object->job[ 'rscdir' ] . $job_object->backup_file, + // 'path' => $job_object->backup_folder . $job_object->backup_file, + // 'concurrency' => 1, + // 'partSize' => 4 * 1024 * 1024 + // ) ); + // $uploded = $transfer->upload(); + + if ($uploded) { + $job_object->log(__('Backup File transferred to RSC://', 'backwpup') . $job_object->job['rsccontainer'] . '/' . $job_object->job['rscdir'] . $job_object->backup_file, E_USER_NOTICE); + $job_object->substeps_done = 1 + $job_object->backup_filesize; + if (!empty($job_object->job['jobid'])) { + BackWPup_Option::update($job_object->job['jobid'], 'lastbackupdownloadurl', network_admin_url('admin.php') . '?page=backwpupbackups&action=downloadrsc&file=' . $job_object->job['rscdir'] . $job_object->backup_file . '&jobid=' . $job_object->job['jobid']); + } + } else { + $job_object->log(__('Cannot transfer backup to Rackspace cloud.', 'backwpup'), E_USER_ERROR); + + return false; + } + } catch (Exception $e) { + $job_object->log(E_USER_ERROR, sprintf(__('Rackspace Cloud API: %s', 'backwpup'), $e->getMessage()), $e->getFile(), $e->getLine()); + + return false; + } + + try { + $backupfilelist = []; + $filecounter = 0; + $files = []; + $objlist = $container->objectList(['prefix' => $job_object->job['rscdir']]); + + while ($object = $objlist->next()) { + $file = basename($object->getName()); + if ($job_object->job['rscdir'] . $file == $object->getName()) { //only in the folder and not in complete bucket + if ($this->is_backup_archive($file) && $this->is_backup_owned_by_job($file, $job_object->job['jobid']) == true) { + $backupfilelist[strtotime($object->getLastModified())] = $object; + } + } + $files[$filecounter]['folder'] = 'RSC://' . $job_object->job['rsccontainer'] . '/' . dirname($object->getName()) . '/'; + $files[$filecounter]['file'] = $object->getName(); + $files[$filecounter]['filename'] = basename($object->getName()); + $files[$filecounter]['downloadurl'] = network_admin_url('admin.php') . '?page=backwpupbackups&action=downloadrsc&file=' . $object->getName() . '&jobid=' . $job_object->job['jobid']; + $files[$filecounter]['filesize'] = $object->getContentLength(); + $files[$filecounter]['time'] = strtotime($object->getLastModified()); + ++$filecounter; + } + if (!empty($job_object->job['rscmaxbackups']) && $job_object->job['rscmaxbackups'] > 0) { //Delete old backups + if (count($backupfilelist) > $job_object->job['rscmaxbackups']) { + ksort($backupfilelist); + $numdeltefiles = 0; + + while ($file = array_shift($backupfilelist)) { + if (count($backupfilelist) < $job_object->job['rscmaxbackups']) { + break; + } + + foreach ($files as $key => $filedata) { + if ($filedata['file'] == $file->getName()) { + unset($files[$key]); + } + } + $file->delete(); + ++$numdeltefiles; + } + if ($numdeltefiles > 0) { + $job_object->log(sprintf(_n('One file deleted on Rackspace cloud container.', '%d files deleted on Rackspace cloud container.', $numdeltefiles, 'backwpup'), $numdeltefiles), E_USER_NOTICE); + } + } + } + set_site_transient('backwpup_' . $job_object->job['jobid'] . '_rsc', $files, YEAR_IN_SECONDS); + } catch (Exception $e) { + $job_object->log(E_USER_ERROR, sprintf(__('Rackspace Cloud API: %s', 'backwpup'), $e->getMessage()), $e->getFile(), $e->getLine()); + + return false; + } + ++$job_object->substeps_done; + + return true; + } + + /** + * @param array $job_settings array + */ + public function can_run(array $job_settings): bool + { + if (empty($job_settings['rscusername'])) { + return false; + } + + if (empty($job_settings['rscapikey'])) { + return false; + } + + return !(empty($job_settings['rsccontainer'])); + } + + public function edit_inline_js(): void + { + ?> '; - - $container_list = array(); - if ( ! empty( $args[ 'rscusername' ] ) && ! empty( $args[ 'rscapikey' ] ) && ! empty( $args[ 'rscregion' ] ) ) { - try { - $conn = new OpenCloud\Rackspace( - self::get_auth_url_by_region( $args[ 'rscregion' ] ), - array( - 'username' => $args[ 'rscusername' ], - 'apiKey' => BackWPup_Encryption::decrypt( $args[ 'rscapikey' ] ), - ), - array( - 'ssl.certificate_authority' => BackWPup::get_plugin_data('cacert') - ) + } + + public function edit_ajax(array $args = []): void + { + $error = ''; + $ajax = false; + + if (isset($_POST['rscusername']) || isset($_POST['rscapikey'])) { + if (!current_user_can('backwpup_jobs_edit')) { + wp_die(-1); + } + check_ajax_referer('backwpup_ajax_nonce'); + $args['rscusername'] = sanitize_text_field($_POST['rscusername']); + $args['rscapikey'] = sanitize_text_field($_POST['rscapikey']); + $args['rscselected'] = sanitize_text_field($_POST['rscselected']); + $args['rscregion'] = sanitize_text_field($_POST['rscregion']); + $ajax = true; + } + echo ''; + + $container_list = []; + if (!empty($args['rscusername']) && !empty($args['rscapikey']) && !empty($args['rscregion'])) { + try { + $conn = new Rackspace( + self::get_auth_url_by_region($args['rscregion']), + [ + 'username' => $args['rscusername'], + 'apiKey' => BackWPup_Encryption::decrypt($args['rscapikey']), + ], + [ + 'ssl.certificate_authority' => BackWPup::get_plugin_data('cacert'), + ] ); - $ostore = $conn->objectStoreService( 'cloudFiles' , $args[ 'rscregion' ], 'publicURL' ); - $containerlist = $ostore->listContainers(); - while( $container = $containerlist->next() ) { - $container_list[] = $container->name; - } - } - catch ( Exception $e ) { - $error = $e->getMessage(); - } - } - - if ( empty( $args[ 'rscusername' ] ) ) - _e( 'Missing username!', 'backwpup' ); - elseif ( empty( $args[ 'rscapikey' ] ) ) - _e( 'Missing API Key!', 'backwpup' ); - elseif ( ! empty( $error ) ) - echo esc_html( $error ); - elseif ( empty( $container_list ) ) - _e( "A container could not be found!", 'backwpup' ); - echo ''; - - if ( ! empty( $container_list ) ) { - echo ''; - } - - if ( $ajax ) - die(); - else - return; - } + $ostore = $conn->objectStoreService('cloudFiles', $args['rscregion'], 'publicURL'); + $containerlist = $ostore->listContainers(); + + while ($container = $containerlist->next()) { + $container_list[] = $container->name; + } + } catch (Exception $e) { + $error = $e->getMessage(); + } + } + + if (empty($args['rscusername'])) { + _e('Missing username!', 'backwpup'); + } elseif (empty($args['rscapikey'])) { + _e('Missing API Key!', 'backwpup'); + } elseif (!empty($error)) { + echo esc_html($error); + } elseif (empty($container_list)) { + _e('A container could not be found!', 'backwpup'); + } + echo ''; + + if (!empty($container_list)) { + echo ''; + } + + if ($ajax) { + exit(); + } + } } diff --git a/inc/class-destination-s3-downloader.php b/inc/class-destination-s3-downloader.php index 5a557a7e..86e9abab 100755 --- a/inc/class-destination-s3-downloader.php +++ b/inc/class-destination-s3-downloader.php @@ -1,130 +1,123 @@ data = $data; - $this->s3_client(); - } - - /** - * Clean stuffs - */ - public function __destruct() { - - fclose( $this->local_file_handler ); - } - - /** - * @inheritdoc - */ - public function download_chunk( $start_byte, $end_byte ) { - - $file = $this->s3_client->getObject( array( - 'Bucket' => BackWPup_Option::get( $this->data->job_id(), self::OPTION_BUCKET ), - 'Key' => $this->data->source_file_path(), - 'Range' => 'bytes=' . $start_byte . '-' . $end_byte, - ) ); - - if ( empty( $file['ContentType'] ) || $file['ContentLength'] === 0 ) { - throw new \RuntimeException( __( 'Could not write data to file. Empty source file.', 'backwpup' ) ); - } - - $this->local_file_handler( $start_byte ); - - $bytes = (int) fwrite( $this->local_file_handler, (string) $file['Body'] ); - if ( $bytes === 0 ) { - throw new \RuntimeException( __( 'Could not write data to file.', 'backwpup' ) ); - } - } - - /** - * @inheritdoc - */ - public function calculate_size() { - - $file = $this->s3_client->getObject( array( - 'Bucket' => BackWPup_Option::get( $this->data->job_id(), self::OPTION_BUCKET ), - 'Key' => $this->data->source_file_path(), - ) ); - - return (int) ( ! empty( $file['ContentType'] ) ? $file['ContentLength'] : 0 ); - } - - /** - * @param int $start_byte - */ - private function local_file_handler( $start_byte ) { - - if ( is_resource( $this->local_file_handler ) ) { - return; - } - - $this->local_file_handler = fopen( $this->data->local_file_path(), $start_byte == 0 ? 'wb' : 'ab' ); - - if ( ! is_resource( $this->local_file_handler ) ) { - throw new \RuntimeException( __( 'File could not be opened for writing.', 'backwpup' ) ); - } - } - - /** - * Build S3 Client - */ - private function s3_client() { - - if ($this->s3_client) { - return; - } - - if ( empty( BackWPup_Option::get( $this->data->job_id(), self::OPTION_BASE_URL ) ) ) { - $aws_destination = BackWPup_S3_Destination::fromOption( - BackWPup_Option::get( $this->data->job_id(), self::OPTION_BASE_URL ) - ); - } else { - $aws_destination = BackWPup_S3_Destination::fromJobId( $this->data->job_id() ); - } - - $this->s3_client = $aws_destination->client( - BackWPup_Option::get($this->data->job_id(), self::OPTION_ACCESS_KEY), - BackWPup_Option::get($this->data->job_id(), self::OPTION_SECRET_KEY) - ); - } +final class BackWPup_Destination_S3_Downloader implements BackWPup_Destination_Downloader_Interface +{ + private const OPTION_BASE_URL = 's3base_url'; + private const OPTION_REGION = 's3region'; + private const OPTION_BUCKET = 's3bucket'; + private const OPTION_ACCESS_KEY = 's3accesskey'; + private const OPTION_SECRET_KEY = 's3secretkey'; + + /** + * @var BackWpUp_Destination_Downloader_Data + */ + private $data; + + /** + * @var S3Client + */ + private $s3Client; + + /** + * @var resource + */ + private $localHandle; + + /** + * BackWPup_Destination_S3_Downloader constructor. + */ + public function __construct(BackWpUp_Destination_Downloader_Data $data) + { + $this->data = $data; + $this->initializeS3Client(); + } + + /** + * Clean stuffs. + */ + public function __destruct() + { + fclose($this->localHandle); + } + + /** + * {@inheritdoc} + */ + public function download_chunk($start_byte, $end_byte): void + { + $file = $this->s3Client->getObject([ + 'Bucket' => BackWPup_Option::get($this->data->job_id(), self::OPTION_BUCKET), + 'Key' => $this->data->source_file_path(), + 'Range' => 'bytes=' . $start_byte . '-' . $end_byte, + ]); + + if (empty($file['ContentType']) || $file['ContentLength'] === 0) { + throw new RuntimeException(__('Could not write data to file. Empty source file.', 'backwpup')); + } + + $this->openLocalHandle($start_byte); + + $bytes = (int) fwrite($this->localHandle, (string) $file['Body']); + if ($bytes === 0) { + throw new RuntimeException(__('Could not write data to file.', 'backwpup')); + } + } + + /** + * {@inheritdoc} + */ + public function calculate_size(): int + { + $file = $this->s3Client->getObject([ + 'Bucket' => BackWPup_Option::get($this->data->job_id(), self::OPTION_BUCKET), + 'Key' => $this->data->source_file_path(), + ]); + + return (int) (!empty($file['ContentType']) ? $file['ContentLength'] : 0); + } + + private function openLocalHandle(int $start_byte): void + { + if (is_resource($this->localHandle)) { + return; + } + + $this->localHandle = fopen($this->data->local_file_path(), $start_byte === 0 ? 'wb' : 'ab'); + + if (!is_resource($this->localHandle)) { + throw new RuntimeException(__('File could not be opened for writing.', 'backwpup')); + } + } + + /** + * Build S3 Client. + */ + private function initializeS3Client(): void + { + if ($this->s3Client) { + return; + } + + if (empty(BackWPup_Option::get($this->data->job_id(), self::OPTION_BASE_URL))) { + $aws_destination = BackWPup_S3_Destination::fromOption( + BackWPup_Option::get($this->data->job_id(), self::OPTION_REGION) + ); + } else { + $aws_destination = BackWPup_S3_Destination::fromJobId($this->data->job_id()); + } + + $this->s3Client = $aws_destination->client( + BackWPup_Option::get($this->data->job_id(), self::OPTION_ACCESS_KEY), + BackWPup_Option::get($this->data->job_id(), self::OPTION_SECRET_KEY) + ); + } } diff --git a/inc/class-destination-s3.php b/inc/class-destination-s3.php index 49028845..c2ffd838 100755 --- a/inc/class-destination-s3.php +++ b/inc/class-destination-s3.php @@ -4,19 +4,17 @@ // https://github.com/aws/aws-sdk-php // http://docs.aws.amazon.com/general/latest/gr/rande.html#s3_region -use \Inpsyde\BackWPupShared\File\MimeTypeExtractor; +use Aws\Exception\AwsException; +use Aws\S3\Exception\S3Exception; +use Inpsyde\BackWPupShared\File\MimeTypeExtractor; /** - * Documentation: http://docs.amazonwebservices.com/aws-sdk-php-2/latest/class-Aws.S3.S3Client.html + * Documentation: http://docs.amazonwebservices.com/aws-sdk-php-2/latest/class-Aws.S3.S3Client.html. */ -class BackWPup_Destination_S3 extends BackWPup_Destinations { - - - /** - * @return array - */ - public function option_defaults() { - +class BackWPup_Destination_S3 extends BackWPup_Destinations +{ + public function option_defaults(): array + { return [ 's3base_url' => '', 's3base_multipart' => true, @@ -35,14 +33,11 @@ public function option_defaults() { ]; } - /** - * @param $jobid - */ - public function edit_tab( $jobid ) { - - ?> + public function edit_tab(int $jobid): void + { + ?>

- +

@@ -55,13 +50,13 @@ public function edit_tab( $jobid ) { @@ -77,7 +72,7 @@ public function edit_tab( $jobid ) { - + + 'Pathstyle-Only Bucket', + 'backwpup' + ); ?> @@ -181,44 +180,44 @@ class="regular-text" @@ -229,50 +228,52 @@ class="regular-text"
- @@ -86,91 +81,95 @@ public function edit_tab( $jobid ) { name="s3base_url" type="text" value="" + BackWPup_Option::get($jobid, 's3base_url') + ); ?>" class="regular-text" autocomplete="off" />

+ 'Leave it empty to use a destination from S3 service list', + 'backwpup' + ); ?>

+ 'Region', + 'backwpup' + ); ?>* - +

+ 'Specify S3 region like "us-west-1"', + 'backwpup' + ); ?>

+ 'Multipart', + 'backwpup' + ); ?> + ); ?>
+ 'Pathstyle-Only Bucket', + 'backwpup' + ); ?> + 'Destination provides only Pathstyle buckets', + 'backwpup' + ); ?>

+ 'Example: http://s3.example.com/bucket-name', + 'backwpup' + ); ?>

" + ) : 'latest'; ?>" placeholder="latest">

+ 'The S3 version for the API like "2006-03-01", default "latest"', + 'backwpup' + ); ?>

+ 'Signature', + 'backwpup' + ); ?> " + ) : 'v4'; ?>" placeholder="v4">

+ 'The signature for the API like "v4"', + 'backwpup' + ); ?>

- +

- +
+ value="" class="regular-text" autocomplete="off"/>

- +

+ } ?>
@@ -325,128 +325,137 @@ class="regular-text"

- +

- +
+ if (BackWPup_Option::get($jobid, 'backuptype') === 'archive') { + ?>

Warning: Files belonging to this job are now tracked. Old backup archives which are untracked will not be automatically deleted.', - 'backwpup' - ) ?> + 'Warning: Files belonging to this job are now tracked. Old backup archives which are untracked will not be automatically deleted.', + 'backwpup' + ); ?>

- + - +
-

+

+
+ name="s3ssencrypt" id="ids3ssencrypt" /> - +
'; - - if ( ! empty( $args['s3accesskey'] ) && ! empty( $args['s3secretkey'] ) ) { + echo ''; - if ( empty($args['s3base_url']) ) { + if (!empty($args['s3accesskey']) && !empty($args['s3secretkey'])) { + if (empty($args['s3base_url'])) { $aws_destination = BackWPup_S3_Destination::fromOption($args['s3region']); - }else{ + } else { $options = [ 'label' => __('Custom S3 destination', 'backwpup'), 'endpoint' => $args['s3base_url'], @@ -485,79 +493,73 @@ public function edit_ajax( $args = '' ) { $aws_destination = BackWPup_S3_Destination::fromOptionArray($options); } - try { - $s3 = $aws_destination->client($args['s3accesskey'], $args['s3secretkey']); - $buckets = $s3->listBuckets(); - if ( ! empty( $buckets['Buckets'] ) ) { - $buckets_list = $buckets['Buckets']; - } - - while ( ! empty( $vaults['Marker'] ) ) { - $buckets = $s3->listBuckets( array( 'marker' => $buckets['Marker'] ) ); - if ( ! empty( $buckets['Buckets'] ) ) { - $buckets_list = array_merge( $buckets_list, $buckets['Buckets'] ); - } - } - } + try { + $s3 = $aws_destination->client($args['s3accesskey'], $args['s3secretkey']); + $buckets = $s3->listBuckets(); + if (!empty($buckets['Buckets'])) { + $buckets_list = $buckets['Buckets']; + } - catch ( Exception $e ) { - $error = $e->getMessage(); - if ( $e instanceof Aws\Exception\AwsException ) { - $error = $e->getAwsErrorMessage(); + while (!empty($vaults['Marker'])) { + $buckets = $s3->listBuckets(['marker' => $buckets['Marker']]); + if (!empty($buckets['Buckets'])) { + $buckets_list = array_merge($buckets_list, $buckets['Buckets']); + } + } + } catch (Exception $e) { + $error = $e->getMessage(); + if ($e instanceof AwsException) { + $error = $e->getAwsErrorMessage(); } - } - } - - if ( empty( $args['s3accesskey'] ) ) { - esc_html_e( 'Missing access key!', 'backwpup' ); - } elseif ( empty( $args['s3secretkey'] ) ) { - esc_html_e( 'Missing secret access key!', 'backwpup' ); - } elseif ( ! empty( $error ) && $error === 'Access Denied' ) { - echo ''; - } elseif ( ! empty( $error ) ) { - echo esc_html( $error ); - } elseif ( ! isset( $buckets ) || count( $buckets['Buckets'] ) < 1 ) { - esc_html_e( 'No bucket found!', 'backwpup' ); - } - echo ''; - - if ( ! empty( $buckets_list ) ) { - echo ''; - } - - if ( $ajax ) { - die(); - } - } - - /** - * @param $jobid - * - * @return string - */ - public function edit_form_post_save( $jobid ) { - - BackWPup_Option::update( $jobid, 's3accesskey', sanitize_text_field( $_POST['s3accesskey'] ) ); - BackWPup_Option::update( - $jobid, - 's3secretkey', - isset( $_POST['s3secretkey'] ) - ? BackWPup_Encryption::encrypt( $_POST['s3secretkey'] ) - : '' - ); - BackWPup_Option::update( - $jobid, - 's3base_url', - isset( $_POST['s3base_url'] ) - ? backwpup_esc_url_default_secure( $_POST['s3base_url'], [ 'http', 'https' ] ) - : '' - ); + } + } + + if (empty($args['s3accesskey'])) { + esc_html_e('Missing access key!', 'backwpup'); + } elseif (empty($args['s3secretkey'])) { + esc_html_e('Missing secret access key!', 'backwpup'); + } elseif (!empty($error) && $error === 'Access Denied') { + echo ''; + } elseif (!empty($error)) { + echo esc_html($error); + } elseif (empty($buckets) || count($buckets['Buckets']) < 1) { + esc_html_e('No bucket found!', 'backwpup'); + } + echo ''; + + if (!empty($buckets_list)) { + echo ''; + } + + if ($ajax) { + exit(); + } + } + + public function edit_form_post_save(int $jobid): void + { + BackWPup_Option::update($jobid, 's3accesskey', sanitize_text_field($_POST['s3accesskey'])); + BackWPup_Option::update( + $jobid, + 's3secretkey', + isset($_POST['s3secretkey']) + ? BackWPup_Encryption::encrypt($_POST['s3secretkey']) + : '' + ); + BackWPup_Option::update( + $jobid, + 's3base_url', + isset($_POST['s3base_url']) + ? backwpup_esc_url_default_secure($_POST['s3base_url'], ['http', 'https']) + : '' + ); BackWPup_Option::update( $jobid, 's3base_region', @@ -589,143 +591,150 @@ public function edit_form_post_save( $jobid ) { ); BackWPup_Option::update($jobid, 's3region', sanitize_text_field($_POST['s3region'])); - BackWPup_Option::update( $jobid, 's3storageclass', sanitize_text_field( $_POST['s3storageclass'] ) ); - BackWPup_Option::update( $jobid, - 's3ssencrypt', - ( isset( $_POST['s3ssencrypt'] ) && $_POST['s3ssencrypt'] === 'AES256' ) ? 'AES256' : '' ); - BackWPup_Option::update( $jobid, - 's3bucket', - isset( $_POST['s3bucket'] ) ? sanitize_text_field( $_POST['s3bucket'] ) : '' ); - - $_POST['s3dir'] = trailingslashit( str_replace( '//', - '/', - str_replace( '\\', '/', trim( sanitize_text_field( $_POST['s3dir'] ) ) ) ) ); - if (strpos($_POST['s3dir'], '/') === 0) { - $_POST['s3dir'] = substr( $_POST['s3dir'], 1 ); - } - if ( $_POST['s3dir'] === '/' ) { - $_POST['s3dir'] = ''; - } - BackWPup_Option::update( $jobid, 's3dir', $_POST['s3dir'] ); - - BackWPup_Option::update( $jobid, - 's3maxbackups', - ! empty( $_POST['s3maxbackups'] ) ? absint( $_POST['s3maxbackups'] ) : 0 ); - BackWPup_Option::update( $jobid, 's3syncnodelete', ! empty( $_POST['s3syncnodelete'] ) ); - - //create new bucket - if ( ! empty( $_POST['s3newbucket'] ) ) { - try { - $region = BackWPup_Option::get($jobid, 's3base_url'); - if (empty($region) ) { - $region = BackWPup_Option::get($jobid, 's3region'); + BackWPup_Option::update($jobid, 's3storageclass', sanitize_text_field($_POST['s3storageclass'])); + BackWPup_Option::update( + $jobid, + 's3ssencrypt', + (isset($_POST['s3ssencrypt']) && $_POST['s3ssencrypt'] === 'AES256') ? 'AES256' : '' + ); + BackWPup_Option::update( + $jobid, + 's3bucket', + isset($_POST['s3bucket']) ? sanitize_text_field($_POST['s3bucket']) : '' + ); + + $_POST['s3dir'] = trailingslashit(str_replace( + '//', + '/', + str_replace('\\', '/', trim(sanitize_text_field($_POST['s3dir']))) + )); + if (strpos($_POST['s3dir'], '/') === 0) { + $_POST['s3dir'] = substr($_POST['s3dir'], 1); + } + if ($_POST['s3dir'] === '/') { + $_POST['s3dir'] = ''; + } + BackWPup_Option::update($jobid, 's3dir', $_POST['s3dir']); + + BackWPup_Option::update( + $jobid, + 's3maxbackups', + !empty($_POST['s3maxbackups']) ? absint($_POST['s3maxbackups']) : 0 + ); + BackWPup_Option::update($jobid, 's3syncnodelete', !empty($_POST['s3syncnodelete'])); + + //create new bucket + if (!empty($_POST['s3newbucket'])) { + try { + $region = BackWPup_Option::get($jobid, 's3base_url'); + if (empty($region)) { + $region = BackWPup_Option::get($jobid, 's3region'); $aws_destination = BackWPup_S3_Destination::fromOption($region); - }else{ + } else { $aws_destination = BackWPup_S3_Destination::fromJobId($jobid); } - $s3 = $aws_destination->client( + $s3 = $aws_destination->client( BackWPup_Option::get($jobid, 's3accesskey'), BackWPup_Option::get($jobid, 's3secretkey') ); $s3->createBucket( - array( - 'Bucket' => sanitize_text_field( $_POST['s3newbucket'] ), - 'PathStyle' => $aws_destination->onlyPathStyleBucket(), + [ + 'Bucket' => sanitize_text_field($_POST['s3newbucket']), + 'PathStyle' => $aws_destination->onlyPathStyleBucket(), 'LocationConstraint' => $aws_destination->region(), - ) + ] ); BackWPup_Admin::message( - sprintf( __( 'Bucket %1$s created.', 'backwpup' ), - sanitize_text_field( $_POST['s3newbucket'] ) ) + sprintf( + __('Bucket %1$s created.', 'backwpup'), + sanitize_text_field($_POST['s3newbucket']) + ) ); - } catch ( Aws\S3\Exception\S3Exception $e ) { - BackWPup_Admin::message( $e->getMessage(), true ); - } - BackWPup_Option::update( $jobid, 's3bucket', sanitize_text_field( $_POST['s3newbucket'] ) ); - } - } - - /** - * @param $jobdest - * @param $backupfile - */ - public function file_delete( $jobdest, $backupfile ) { - - $files = get_site_transient( 'backwpup_' . strtolower( $jobdest ) ); - list( $jobid, $dest ) = explode( '_', $jobdest ); - - if ( BackWPup_Option::get( $jobid, 's3accesskey' ) && BackWPup_Option::get( $jobid, - 's3secretkey' ) && BackWPup_Option::get( $jobid, 's3bucket' ) ) { - try { - $region = BackWPup_Option::get($jobid, 's3base_url'); - if (empty($region) ) { - $region = BackWPup_Option::get($jobid, 's3region'); + } catch (S3Exception $e) { + BackWPup_Admin::message($e->getMessage(), true); + } + BackWPup_Option::update($jobid, 's3bucket', sanitize_text_field($_POST['s3newbucket'])); + } + } + + public function file_delete(string $jobdest, string $backupfile): void + { + $files = get_site_transient('backwpup_' . strtolower($jobdest)); + [$jobid, $dest] = explode('_', $jobdest); + + if (BackWPup_Option::get($jobid, 's3accesskey') && BackWPup_Option::get( + $jobid, + 's3secretkey' + ) && BackWPup_Option::get($jobid, 's3bucket')) { + try { + $region = BackWPup_Option::get($jobid, 's3base_url'); + if (empty($region)) { + $region = BackWPup_Option::get($jobid, 's3region'); $aws_destination = BackWPup_S3_Destination::fromOption($region); - }else{ + } else { $aws_destination = BackWPup_S3_Destination::fromJobId($jobid); } - $s3 = $aws_destination->client( + $s3 = $aws_destination->client( BackWPup_Option::get($jobid, 's3accesskey'), BackWPup_Option::get($jobid, 's3secretkey') ); - $s3->deleteObject( array( - 'Bucket' => BackWPup_Option::get( $jobid, 's3bucket' ), - 'Key' => $backupfile, - ) ); - //update file list - foreach ( (array) $files as $key => $file ) { - if ( is_array( $file ) && $file['file'] === $backupfile ) { - unset( $files[ $key ] ); - } - } - unset( $s3 ); - } catch ( Exception $e ) { - $errorMessage = $e->getMessage(); - if ( $e instanceof Aws\Exception\AwsException ) { - $errorMessage = $e->getAwsErrorMessage(); + $s3->deleteObject([ + 'Bucket' => BackWPup_Option::get($jobid, 's3bucket'), + 'Key' => $backupfile, + ]); + //update file list + foreach ((array) $files as $key => $file) { + if (is_array($file) && $file['file'] === $backupfile) { + unset($files[$key]); + } + } + unset($s3); + } catch (Exception $e) { + $errorMessage = $e->getMessage(); + if ($e instanceof AwsException) { + $errorMessage = $e->getAwsErrorMessage(); } - BackWPup_Admin::message( sprintf( __( 'S3 Service API: %s', 'backwpup' ), $errorMessage ), true ); - } - } - - set_site_transient( 'backwpup_' . strtolower( $jobdest ), $files, YEAR_IN_SECONDS ); - } - - /** - * @inheritdoc - */ - public function file_get_list( $jobdest ) { - - $list = (array) get_site_transient( 'backwpup_' . strtolower( $jobdest ) ); - $list = array_filter( $list ); - - return $list; - } - - /** - * File Update List - * - * Update the list of files in the transient. - * - * @param BackWPup_Job|int $job Either the job object or job ID - * @param bool $delete Whether to delete old backups. - */ - public function file_update_list( $job, $delete = false ) { - - if ( $job instanceof BackWPup_Job ) { - $job_object = $job; - $jobid = $job->job['jobid']; - } else { - $job_object = null; - $jobid = $job; - } - - if ( empty($job_object->job['s3base_url']) ) { + BackWPup_Admin::message(sprintf(__('S3 Service API: %s', 'backwpup'), $errorMessage), true); + } + } + + set_site_transient('backwpup_' . strtolower($jobdest), $files, YEAR_IN_SECONDS); + } + + /** + * {@inheritdoc} + */ + public function file_get_list(string $jobdest): array + { + $list = (array) get_site_transient('backwpup_' . strtolower($jobdest)); + + return array_filter($list); + } + + /** + * File Update List. + * + * Update the list of files in the transient. + * + * @param BackWPup_Job|int $job Either the job object or job ID + * @param bool $delete whether to delete old backups + */ + public function file_update_list($job, bool $delete = false): void + { + if ($job instanceof BackWPup_Job) { + $job_object = $job; + $jobid = $job->job['jobid']; + } else { + $job_object = null; + $jobid = $job; + } + + if (empty($job_object->job['s3base_url'])) { $aws_destination = BackWPup_S3_Destination::fromOption($job_object->job['s3region']); - }else{ + } else { $aws_destination = BackWPup_S3_Destination::fromJobId($job_object->job['jobid']); } $s3 = $aws_destination->client( @@ -733,107 +742,109 @@ public function file_update_list( $job, $delete = false ) { BackWPup_Option::get($jobid, 's3secretkey') ); - $backupfilelist = array(); - $filecounter = 0; - $files = array(); - $args = array( - 'Bucket' => BackWPup_Option::get( $jobid, 's3bucket' ), - 'Prefix' => (string) BackWPup_Option::get( $jobid, 's3dir' ), - ); - $objects = $s3->getIterator( 'ListObjects', $args ); - - if ( is_object( $objects ) ) { - foreach ( $objects as $object ) { - $file = basename( $object['Key'] ); - $changetime = strtotime( $object['LastModified'] ) + ( get_option( 'gmt_offset' ) * 3600 ); - - if ( $this->is_backup_archive( $file ) && $this->is_backup_owned_by_job( $file, $jobid ) ) { - $backupfilelist[ $changetime ] = $file; - } + $backupfilelist = []; + $filecounter = 0; + $files = []; + $args = [ + 'Bucket' => BackWPup_Option::get($jobid, 's3bucket'), + 'Prefix' => BackWPup_Option::get($jobid, 's3dir'), + ]; + $objects = $s3->getIterator('ListObjects', $args); - $files[ $filecounter ]['folder'] = $s3->getObjectUrl(BackWPup_Option::get( $jobid, 's3bucket' ), dirname( $object['Key'] ) ); - $files[ $filecounter ]['file'] = $object['Key']; - $files[ $filecounter ]['filename'] = basename( $object['Key'] ); + if (is_object($objects)) { + foreach ($objects as $object) { + $file = basename($object['Key']); + $changetime = strtotime($object['LastModified']) + (get_option('gmt_offset') * 3600); - if ( ! empty( $object['StorageClass'] ) ) { - $files[ $filecounter ]['info'] = sprintf( __( 'Storage Class: %s', 'backwpup' ), - $object['StorageClass'] ); - } + if ($this->is_backup_archive($file) && $this->is_backup_owned_by_job($file, $jobid)) { + $backupfilelist[$changetime] = $file; + } - $files[ $filecounter ]['downloadurl'] = network_admin_url( 'admin.php' ) . '?page=backwpupbackups&action=downloads3&file=' . $object['Key'] . '&local_file=' . basename( $object['Key'] ) . '&jobid=' . $jobid; - $files[ $filecounter ]['filesize'] = (int)$object['Size']; - $files[ $filecounter ]['time'] = $changetime; - - $filecounter ++; - } - } - - if ( $delete && $job_object && $job_object->job['s3maxbackups'] > 0 && is_object( $s3 ) ) { //Delete old backups - if ( count( $backupfilelist ) > $job_object->job['s3maxbackups'] ) { - ksort( $backupfilelist ); - $numdeltefiles = 0; - while ( $file = array_shift( $backupfilelist ) ) { - if ( count( $backupfilelist ) < $job_object->job['s3maxbackups'] ) { - break; - } - //delete files on S3 - $args = array( - 'Bucket' => $job_object->job['s3bucket'], - 'Key' => $job_object->job['s3dir'] . $file, - ); - - if ( $s3->deleteObject( $args ) ) { - foreach ( $files as $key => $filedata ) { - if ( $filedata['file'] == $job_object->job['s3dir'] . $file ) { - unset( $files[ $key ] ); - } - } - $numdeltefiles ++; - } else { - $job_object->log( - sprintf( __( 'Cannot delete backup from %s.', 'backwpup' ), - $s3->getObjectUrl($job_object->job['s3bucket'],$job_object->job['s3dir'] . $file )), - E_USER_ERROR - ); - } - } + $files[$filecounter]['folder'] = $s3->getObjectUrl(BackWPup_Option::get($jobid, 's3bucket'), dirname($object['Key'])); + $files[$filecounter]['file'] = $object['Key']; + $files[$filecounter]['filename'] = basename($object['Key']); + if (!empty($object['StorageClass'])) { + $files[$filecounter]['info'] = sprintf( + __('Storage Class: %s', 'backwpup'), + $object['StorageClass'] + ); + } - if ( $numdeltefiles > 0 ) { - $job_object->log( sprintf( _n( 'One file deleted on S3 Bucket.', - '%d files deleted on S3 Bucket', - $numdeltefiles, - 'backwpup' ), - $numdeltefiles ) ); - } - } - } - set_site_transient( 'backwpup_' . $jobid . '_s3', $files, YEAR_IN_SECONDS ); - - } - - /** - * @param $job_object BackWPup_Job - * - * @return bool - */ - public function job_run_archive( BackWPup_Job $job_object ) { - - $job_object->substeps_todo = 2 + $job_object->backup_filesize; - - if ( $job_object->steps_data[ $job_object->step_working ]['SAVE_STEP_TRY'] != $job_object->steps_data[ $job_object->step_working ]['STEP_TRY'] ) { - $job_object->log( - sprintf( - __( '%d. Trying to send backup file to S3 Service …', 'backwpup' ), - $job_object->steps_data[ $job_object->step_working ]['STEP_TRY'] - ) - ); - } - - try { - if ( empty($job_object->job['s3base_url']) ) { + $files[$filecounter]['downloadurl'] = network_admin_url('admin.php') . '?page=backwpupbackups&action=downloads3&file=' . $object['Key'] . '&local_file=' . basename($object['Key']) . '&jobid=' . $jobid; + $files[$filecounter]['filesize'] = (int) $object['Size']; + $files[$filecounter]['time'] = $changetime; + + ++$filecounter; + } + } + + if ($delete && $job_object && $job_object->job['s3maxbackups'] > 0 && is_object($s3)) { //Delete old backups + if (count($backupfilelist) > $job_object->job['s3maxbackups']) { + ksort($backupfilelist); + $numdeltefiles = 0; + + while ($file = array_shift($backupfilelist)) { + if (count($backupfilelist) < $job_object->job['s3maxbackups']) { + break; + } + //delete files on S3 + $args = [ + 'Bucket' => $job_object->job['s3bucket'], + 'Key' => $job_object->job['s3dir'] . $file, + ]; + + if ($s3->deleteObject($args)) { + foreach ($files as $key => $filedata) { + if ($filedata['file'] == $job_object->job['s3dir'] . $file) { + unset($files[$key]); + } + } + ++$numdeltefiles; + } else { + $job_object->log( + sprintf( + __('Cannot delete backup from %s.', 'backwpup'), + $s3->getObjectUrl($job_object->job['s3bucket'], $job_object->job['s3dir'] . $file) + ), + E_USER_ERROR + ); + } + } + + if ($numdeltefiles > 0) { + $job_object->log(sprintf( + _n( + 'One file deleted on S3 Bucket.', + '%d files deleted on S3 Bucket', + $numdeltefiles, + 'backwpup' + ), + $numdeltefiles + )); + } + } + } + set_site_transient('backwpup_' . $jobid . '_s3', $files, YEAR_IN_SECONDS); + } + + public function job_run_archive(BackWPup_Job $job_object): bool + { + $job_object->substeps_todo = 2 + $job_object->backup_filesize; + + if ($job_object->steps_data[$job_object->step_working]['SAVE_STEP_TRY'] != $job_object->steps_data[$job_object->step_working]['STEP_TRY']) { + $job_object->log( + sprintf( + __('%d. Trying to send backup file to S3 Service …', 'backwpup'), + $job_object->steps_data[$job_object->step_working]['STEP_TRY'] + ) + ); + } + + try { + if (empty($job_object->job['s3base_url'])) { $aws_destination = BackWPup_S3_Destination::fromOption($job_object->job['s3region']); - }else{ + } else { $aws_destination = BackWPup_S3_Destination::fromJobId($job_object->job['jobid']); } @@ -842,262 +853,282 @@ public function job_run_archive( BackWPup_Job $job_object ) { $job_object->job['s3secretkey'] ); - if ( $job_object->steps_data[ $job_object->step_working ]['SAVE_STEP_TRY'] != $job_object->steps_data[ $job_object->step_working ]['STEP_TRY'] && $job_object->substeps_done < $job_object->backup_filesize ) { - if ( $s3->doesBucketExist( $job_object->job['s3bucket'] ) ) { - $bucketregion = $s3->getBucketLocation( array( 'Bucket' => $job_object->job['s3bucket'] ) ); - $job_object->log( sprintf( __( 'Connected to S3 Bucket "%1$s" in %2$s', 'backwpup' ), - $job_object->job['s3bucket'], - $bucketregion->get( 'LocationConstraint' ) ) + if ($job_object->steps_data[$job_object->step_working]['SAVE_STEP_TRY'] != $job_object->steps_data[$job_object->step_working]['STEP_TRY'] && $job_object->substeps_done < $job_object->backup_filesize) { + if ($s3->doesBucketExist($job_object->job['s3bucket'])) { + $bucketregion = $s3->getBucketLocation(['Bucket' => $job_object->job['s3bucket']]); + $job_object->log( + sprintf( + __('Connected to S3 Bucket "%1$s" in %2$s', 'backwpup'), + $job_object->job['s3bucket'], + $bucketregion->get('LocationConstraint') + ) ); - } else { - $job_object->log( sprintf( __( 'S3 Bucket "%s" does not exist!' , 'backwpup' ), - $job_object->job['s3bucket'] ),E_USER_ERROR ); - return true; - } + } else { + $job_object->log(sprintf( + __('S3 Bucket "%s" does not exist!', 'backwpup'), + $job_object->job['s3bucket'] + ), E_USER_ERROR); - if ( $aws_destination->supportsMultipart() && empty( $job_object->steps_data[ $job_object->step_working ]['UploadId'] ) ) { - //Check for aboded Multipart Uploads - $job_object->log( __( 'Checking for not aborted multipart Uploads …', 'backwpup' ) ); - $multipart_uploads = $s3->listMultipartUploads( array( - 'Bucket' => $job_object->job['s3bucket'], - 'Prefix' => (string) $job_object->job['s3dir'], - ) ); - $uploads = $multipart_uploads->get( 'Uploads' ); - if ( ! empty( $uploads ) ) { - foreach ( $uploads as $upload ) { - $s3->abortMultipartUpload( array( - 'Bucket' => $job_object->job['s3bucket'], - 'Key' => $upload['Key'], - 'UploadId' => $upload['UploadId'], - ) ); - $job_object->log( sprintf( __( 'Upload for %s aborted.', 'backwpup' ), $upload['Key'] ) ); - } - } - } + return true; + } - //transfer file to S3 - $job_object->log( __( 'Starting upload to S3 Service …', 'backwpup' ) ); - } + if ($aws_destination->supportsMultipart() && empty($job_object->steps_data[$job_object->step_working]['UploadId'])) { + //Check for aborted Multipart Uploads + $job_object->log(__('Checking for not aborted multipart Uploads …', 'backwpup')); + $multipart_uploads = $s3->listMultipartUploads([ + 'Bucket' => $job_object->job['s3bucket'], + 'Prefix' => (string) $job_object->job['s3dir'], + ]); + $uploads = $multipart_uploads->get('Uploads'); + if (!empty($uploads)) { + foreach ($uploads as $upload) { + $s3->abortMultipartUpload([ + 'Bucket' => $job_object->job['s3bucket'], + 'Key' => $upload['Key'], + 'UploadId' => $upload['UploadId'], + ]); + $job_object->log(sprintf(__('Upload for %s aborted.', 'backwpup'), $upload['Key'])); + } + } + } + //transfer file to S3 + $job_object->log(__('Starting upload to S3 Service …', 'backwpup')); + } - if ( ! $aws_destination->supportsMultipart() || $job_object->backup_filesize < 1048576 * 6 ) { - // Prepare Upload - if ( ! $up_file_handle = fopen( $job_object->backup_folder . $job_object->backup_file, 'rb' ) ) { - $job_object->log( __( 'Can not open source file for transfer.', 'backwpup' ), E_USER_ERROR ); + if (!$aws_destination->supportsMultipart() || $job_object->backup_filesize < 1048576 * 6) { + // Prepare Upload + if (!$up_file_handle = fopen($job_object->backup_folder . $job_object->backup_file, 'rb')) { + $job_object->log(__('Can not open source file for transfer.', 'backwpup'), E_USER_ERROR); - return false; - } - $create_args = array(); - $create_args['Bucket'] = $job_object->job['s3bucket']; - $create_args['ACL'] = 'private'; - // Encryption - if ( ! empty( $job_object->job['s3ssencrypt'] ) ) { - $create_args['ServerSideEncryption'] = $job_object->job['s3ssencrypt']; - } - // Storage Class - if ( ! empty( $job_object->job['s3storageclass'] ) ) { - $create_args['StorageClass'] = $job_object->job['s3storageclass']; - } - $create_args['Metadata'] = array( 'BackupTime' => date( 'Y-m-d H:i:s', $job_object->start_time ) ); - - $create_args['Body'] = $up_file_handle; - $create_args['Key'] = $job_object->job['s3dir'] . $job_object->backup_file; - $create_args['ContentType'] = MimeTypeExtractor::fromFilePath( $job_object->backup_folder . $job_object->backup_file ); - - try { - $s3->putObject( $create_args ); - } catch ( Exception $e ) { - $errorMessage = $e->getMessage(); - if ( $e instanceof Aws\Exception\AwsException ) { - $errorMessage = $e->getAwsErrorMessage(); + return false; + } + $create_args = []; + $create_args['Bucket'] = $job_object->job['s3bucket']; + $create_args['ACL'] = 'private'; + // Encryption + if (!empty($job_object->job['s3ssencrypt'])) { + $create_args['ServerSideEncryption'] = $job_object->job['s3ssencrypt']; + } + // Storage Class + if (!empty($job_object->job['s3storageclass'])) { + $create_args['StorageClass'] = $job_object->job['s3storageclass']; + } + $create_args['Metadata'] = ['BackupTime' => date('Y-m-d H:i:s', $job_object->start_time)]; + + $create_args['Body'] = $up_file_handle; + $create_args['Key'] = $job_object->job['s3dir'] . $job_object->backup_file; + $create_args['ContentType'] = MimeTypeExtractor::fromFilePath($job_object->backup_folder . $job_object->backup_file); + $create_args['ContentMD5'] = base64_encode(md5_file($job_object->backup_folder . $job_object->backup_file, true)); + + try { + $s3->putObject($create_args); + } catch (Exception $e) { + $errorMessage = $e->getMessage(); + if ($e instanceof AwsException) { + $errorMessage = $e->getAwsErrorMessage(); } - $job_object->log( E_USER_ERROR, - sprintf( __( 'S3 Service API: %s', 'backwpup' ), $errorMessage ), - $e->getFile(), - $e->getLine() ); + $job_object->log( + E_USER_ERROR, + sprintf(__('S3 Service API: %s', 'backwpup'), $errorMessage), + $e->getFile(), + $e->getLine() + ); - return false; - } - } else { - // Prepare Upload - if ( $file_handle = fopen( $job_object->backup_folder . $job_object->backup_file, 'rb' ) ) { - fseek( $file_handle, $job_object->substeps_done ); - - try { - - if ( empty ( $job_object->steps_data[ $job_object->step_working ]['UploadId'] ) ) { - $args = array( - 'ACL' => 'private', - 'Bucket' => $job_object->job['s3bucket'], - 'ContentType' => MimeTypeExtractor::fromFilePath( $job_object->backup_folder . $job_object->backup_file ), - 'Key' => $job_object->job['s3dir'] . $job_object->backup_file, - ); - if ( ! empty( $job_object->job['s3ssencrypt'] ) ) { - $args['ServerSideEncryption'] = $job_object->job['s3ssencrypt']; - } - if ( ! empty( $job_object->job['s3storageclass'] ) ) { - $args['StorageClass'] = empty( $job_object->job['s3storageclass'] ) ? '' : $job_object->job['s3storageclass']; - } - - $upload = $s3->createMultipartUpload( $args ); - - $job_object->steps_data[ $job_object->step_working ]['UploadId'] = $upload->get( 'UploadId' ); - $job_object->steps_data[ $job_object->step_working ]['Parts'] = array(); - $job_object->steps_data[ $job_object->step_working ]['Part'] = 1; - } - - while ( ! feof( $file_handle ) ) { - $chunk_upload_start = microtime( true ); - $part_data = fread( $file_handle, 1048576 * 5 ); //5MB Minimum part size - $part = $s3->uploadPart( array( - 'Bucket' => $job_object->job['s3bucket'], - 'UploadId' => $job_object->steps_data[ $job_object->step_working ]['UploadId'], - 'Key' => $job_object->job['s3dir'] . $job_object->backup_file, - 'PartNumber' => $job_object->steps_data[ $job_object->step_working ]['Part'], - 'Body' => $part_data, - ) ); - $chunk_upload_time = microtime( true ) - $chunk_upload_start; - $job_object->substeps_done = $job_object->substeps_done + strlen( $part_data ); - $job_object->steps_data[ $job_object->step_working ]['Parts'][] = array( - 'ETag' => $part->get( 'ETag' ), - 'PartNumber' => $job_object->steps_data[ $job_object->step_working ]['Part'], - ); - $job_object->steps_data[ $job_object->step_working ]['Part'] ++; - $time_remaining = $job_object->do_restart_time(); - if ( $time_remaining < $chunk_upload_time ) { - $job_object->do_restart_time( true ); - } - $job_object->update_working_data(); - gc_collect_cycles(); - } - - $parts = $s3->listParts(array( + return false; + } + } else { + // Prepare Upload + if (!($file_handle = fopen( + $job_object->backup_folder . $job_object->backup_file, + 'rb' + ))) { + $job_object->log( + __('Can not open source file for transfer.', 'backwpup'), + E_USER_ERROR + ); + + return false; + } + + fseek($file_handle, $job_object->substeps_done); + + try { + if (empty($job_object->steps_data[$job_object->step_working]['UploadId'])) { + $args = [ + 'ACL' => 'private', 'Bucket' => $job_object->job['s3bucket'], - 'Key' => $job_object->job['s3dir'] . $job_object->backup_file, - 'UploadId' => $job_object->steps_data[ $job_object->step_working ]['UploadId'] - )); - - $s3->completeMultipartUpload( array( - 'Bucket' => $job_object->job['s3bucket'], - 'UploadId' => $job_object->steps_data[ $job_object->step_working ]['UploadId'], - 'MultipartUpload' => array( - 'Parts' => $parts['Parts'], + 'ContentType' => MimeTypeExtractor::fromFilePath( + $job_object->backup_folder . $job_object->backup_file ), - 'Key' => $job_object->job['s3dir'] . $job_object->backup_file, - ) ); + 'Key' => $job_object->job['s3dir'] . $job_object->backup_file, + ]; + if (!empty($job_object->job['s3ssencrypt'])) { + $args['ServerSideEncryption'] = $job_object->job['s3ssencrypt']; + } + if (!empty($job_object->job['s3storageclass'])) { + $args['StorageClass'] = empty($job_object->job['s3storageclass']) ? '' : $job_object->job['s3storageclass']; + } + + $upload = $s3->createMultipartUpload($args); + + $job_object->steps_data[$job_object->step_working]['UploadId'] = $upload->get( + 'UploadId' + ); + $job_object->steps_data[$job_object->step_working]['Parts'] = []; + $job_object->steps_data[$job_object->step_working]['Part'] = 1; + } - } catch ( Exception $e ) { - $errorMessage = $e->getMessage(); - if ( $e instanceof Aws\Exception\AwsException ) { - $errorMessage = $e->getAwsErrorMessage(); + while (!feof($file_handle)) { + $chunk_upload_start = microtime(true); + $part_data = fread($file_handle, 1048576 * 5); //5MB Minimum part size + $part = $s3->uploadPart([ + 'Bucket' => $job_object->job['s3bucket'], + 'UploadId' => $job_object->steps_data[$job_object->step_working]['UploadId'], + 'Key' => $job_object->job['s3dir'] . $job_object->backup_file, + 'PartNumber' => $job_object->steps_data[$job_object->step_working]['Part'], + 'Body' => $part_data, + ]); + $chunk_upload_time = microtime(true) - $chunk_upload_start; + $job_object->substeps_done = $job_object->substeps_done + strlen( + $part_data + ); + $job_object->steps_data[$job_object->step_working]['Parts'][] = [ + 'ETag' => $part->get('ETag'), + 'PartNumber' => $job_object->steps_data[$job_object->step_working]['Part'], + ]; + ++$job_object->steps_data[$job_object->step_working]['Part']; + $time_remaining = $job_object->do_restart_time(); + if ($time_remaining < $chunk_upload_time) { + $job_object->do_restart_time(true); } - $job_object->log( E_USER_ERROR, - sprintf( __( 'S3 Service API: %s', 'backwpup' ), $errorMessage ), - $e->getFile(), - $e->getLine() ); - if ( ! empty( $job_object->steps_data[ $job_object->step_working ]['uploadId'] ) ) { - $s3->abortMultipartUpload( array( - 'Bucket' => $job_object->job['s3bucket'], - 'UploadId' => $job_object->steps_data[ $job_object->step_working ]['uploadId'], - 'Key' => $job_object->job['s3dir'] . $job_object->backup_file, - ) ); - } - unset( $job_object->steps_data[ $job_object->step_working ]['UploadId'] ); - unset( $job_object->steps_data[ $job_object->step_working ]['Parts'] ); - unset( $job_object->steps_data[ $job_object->step_working ]['Part'] ); - $job_object->substeps_done = 0; - if ( is_resource( $file_handle ) ) { - fclose( $file_handle ); - } - - return false; - } - fclose( $file_handle ); - } else { - $job_object->log( __( 'Can not open source file for transfer.', 'backwpup' ), E_USER_ERROR ); - - return false; - } - } - - $result = $s3->headObject( array( - 'Bucket' => $job_object->job['s3bucket'], - 'Key' => $job_object->job['s3dir'] . $job_object->backup_file, - ) ); - - if ( $result->get( 'ContentLength' ) == filesize( $job_object->backup_folder . $job_object->backup_file ) ) { - $job_object->substeps_done = 1 + $job_object->backup_filesize; - $job_object->log( - sprintf( __( 'Backup transferred to %s.', 'backwpup' ), - $s3->getObjectUrl($job_object->job['s3bucket'], $job_object->job['s3dir'] . $job_object->backup_file ) ) - ); - - if ( ! empty( $job_object->job['jobid'] ) ) { - BackWPup_Option::update( - $job_object->job['jobid'], - 'lastbackupdownloadurl', - network_admin_url( 'admin.php' ) . '?page=backwpupbackups&action=downloads3&file=' . $job_object->job['s3dir'] . $job_object->backup_file . '&jobid=' . $job_object->job['jobid'] - ); - } - } else { - $job_object->log( sprintf( __( 'Cannot transfer backup to S3! (%1$d) %2$s', 'backwpup' ), - $result->get( "status" ), - $result->get( "Message" ) ), - E_USER_ERROR ); - } - } catch ( Exception $e ) { - $errorMessage = $e->getMessage(); - if ( $e instanceof Aws\Exception\AwsException ) { - $errorMessage = $e->getAwsErrorMessage(); - } - $job_object->log( E_USER_ERROR, - sprintf( __( 'S3 Service API: %s', 'backwpup' ), $errorMessage ), - $e->getFile(), - $e->getLine() ); - - return false; - } - - try { - $this->file_update_list( $job_object, true ); - } catch ( Exception $e ) { - $errorMessage = $e->getMessage(); - if ( $e instanceof Aws\Exception\AwsException ) { - $errorMessage = $e->getAwsErrorMessage(); + $job_object->update_working_data(); + gc_collect_cycles(); + } + + $parts = $s3->listParts([ + 'Bucket' => $job_object->job['s3bucket'], + 'Key' => $job_object->job['s3dir'] . $job_object->backup_file, + 'UploadId' => $job_object->steps_data[$job_object->step_working]['UploadId'], + ]); + + $s3->completeMultipartUpload([ + 'Bucket' => $job_object->job['s3bucket'], + 'UploadId' => $job_object->steps_data[$job_object->step_working]['UploadId'], + 'MultipartUpload' => [ + 'Parts' => $parts['Parts'], + ], + 'Key' => $job_object->job['s3dir'] . $job_object->backup_file, + ]); + } catch (Exception $e) { + $errorMessage = $e->getMessage(); + if ($e instanceof AwsException) { + $errorMessage = $e->getAwsErrorMessage(); + } + $job_object->log( + E_USER_ERROR, + sprintf(__('S3 Service API: %s', 'backwpup'), $errorMessage), + $e->getFile(), + $e->getLine() + ); + if (!empty($job_object->steps_data[$job_object->step_working]['uploadId'])) { + $s3->abortMultipartUpload([ + 'Bucket' => $job_object->job['s3bucket'], + 'UploadId' => $job_object->steps_data[$job_object->step_working]['uploadId'], + 'Key' => $job_object->job['s3dir'] . $job_object->backup_file, + ]); + } + unset($job_object->steps_data[$job_object->step_working]['UploadId'], $job_object->steps_data[$job_object->step_working]['Parts'], $job_object->steps_data[$job_object->step_working]['Part']); + + $job_object->substeps_done = 0; + if (is_resource($file_handle)) { + fclose($file_handle); + } + + return false; + } + fclose($file_handle); } - $job_object->log( E_USER_ERROR, - sprintf( __( 'S3 Service API: %s', 'backwpup' ), $errorMessage ), - $e->getFile(), - $e->getLine() ); - return false; - } - $job_object->substeps_done = 2 + $job_object->backup_filesize; + $result = $s3->headObject([ + 'Bucket' => $job_object->job['s3bucket'], + 'Key' => $job_object->job['s3dir'] . $job_object->backup_file, + ]); + + if ($result->get('ContentLength') == filesize($job_object->backup_folder . $job_object->backup_file)) { + $job_object->substeps_done = 1 + $job_object->backup_filesize; + $job_object->log( + sprintf( + __('Backup transferred to %s.', 'backwpup'), + $s3->getObjectUrl($job_object->job['s3bucket'], $job_object->job['s3dir'] . $job_object->backup_file) + ) + ); + + if (!empty($job_object->job['jobid'])) { + BackWPup_Option::update( + $job_object->job['jobid'], + 'lastbackupdownloadurl', + network_admin_url('admin.php') . '?page=backwpupbackups&action=downloads3&file=' . $job_object->job['s3dir'] . $job_object->backup_file . '&jobid=' . $job_object->job['jobid'] + ); + } + } else { + $job_object->log( + sprintf( + __('Cannot transfer backup to S3! (%1$d) %2$s', 'backwpup'), + $result->get('status'), + $result->get('Message') + ), + E_USER_ERROR + ); + } + } catch (Exception $e) { + $errorMessage = $e->getMessage(); + if ($e instanceof AwsException) { + $errorMessage = $e->getAwsErrorMessage(); + } + $job_object->log( + E_USER_ERROR, + sprintf(__('S3 Service API: %s', 'backwpup'), $errorMessage), + $e->getFile(), + $e->getLine() + ); - return true; - } + return false; + } - /** - * @param $job_settings array - * - * @return bool - */ - public function can_run( array $job_settings ) { + try { + $this->file_update_list($job_object, true); + } catch (Exception $e) { + $errorMessage = $e->getMessage(); + if ($e instanceof AwsException) { + $errorMessage = $e->getAwsErrorMessage(); + } + $job_object->log( + E_USER_ERROR, + sprintf(__('S3 Service API: %s', 'backwpup'), $errorMessage), + $e->getFile(), + $e->getLine() + ); - if ( empty( $job_settings['s3accesskey'] ) ) { - return false; - } + return false; + } + $job_object->substeps_done = 2 + $job_object->backup_filesize; - if ( empty( $job_settings['s3secretkey'] ) ) { - return false; - } + return true; + } - return true; - } + public function can_run(array $job_settings): bool + { + if (empty($job_settings['s3accesskey'])) { + return false; + } - public function edit_inline_js() { + return !(empty($job_settings['s3secretkey'])); + } - ?> + public function edit_inline_js(): void + { + ?> '', 'sugarroot' => '', 'sugardir' => trailingslashit(sanitize_file_name(get_bloginfo('name'))), 'sugarmaxbackups' => 15]; + } - /** - * @return array - */ - public function option_defaults() { - return array( 'sugarrefreshtoken' => '', 'sugarroot' => '', 'sugardir' => trailingslashit( sanitize_file_name( get_bloginfo( 'name' ) ) ), 'sugarmaxbackups' => 15 ); - } - - - /** - * @param $jobid - */ - public function edit_tab( $jobid ) { - ?> -

+ public function edit_tab(int $jobid): void + { + $syncfolders = null; ?> +

- + - + - +
- +
- +

- -       + +      
- - + +
-

+

- +
user(); - $syncfolders = $sugarsync->get( $user->syncfolders ); - if ( ! is_object( $syncfolders ) ) - echo '' . __( 'No Syncfolders found!', 'backwpup' ) . ''; - } - catch ( Exception $e ) { - echo '' . $e->getMessage() . ''; - } - if ( isset( $syncfolders ) && is_object( $syncfolders ) ) { - echo ''; - } - ?> + try { + $sugarsync = new BackWPup_Destination_SugarSync_API(BackWPup_Option::get($jobid, 'sugarrefreshtoken')); + $user = $sugarsync->user(); + $syncfolders = $sugarsync->get($user->syncfolders); + if (!is_object($syncfolders)) { + echo '' . __('No Syncfolders found!', 'backwpup') . ''; + } + } catch (Exception $e) { + echo '' . $e->getMessage() . ''; + } + if (isset($syncfolders) && is_object($syncfolders)) { + echo ''; + } ?>
-

+

- + - +
- +
+ if (BackWPup_Option::get($jobid, 'backuptype') === 'archive') { + ?> -

Warning: Files belonging to this job are now tracked. Old backup archives which are untracked will not be automatically deleted.', 'backwpup' ) ?>

- +

Warning: Files belonging to this job are now tracked. Old backup archives which are untracked will not be automatically deleted.', 'backwpup'); ?>

+
get_Refresh_Token( sanitize_email( $_POST[ 'sugaremail' ] ), $_POST[ 'sugarpass' ] ); - if ( ! empty( $refresh_token ) ) - BackWPup_Option::update( $jobid, 'sugarrefreshtoken', $refresh_token ); - } - catch ( Exception $e ) { - BackWPup_Admin::message( 'SUGARSYNC: ' . $e->getMessage(), TRUE ); - } - } - - if ( isset( $_POST[ 'authbutton' ] ) && $_POST[ 'authbutton' ] === __( 'Delete Sugarsync authentication!', 'backwpup' ) ) { - BackWPup_Option::delete( $jobid, 'sugarrefreshtoken' ); - } - - if ( isset( $_POST[ 'authbutton' ] ) && $_POST[ 'authbutton' ] === __( 'Create Sugarsync account', 'backwpup' ) ) { - try { - $sugarsync = new BackWPup_Destination_SugarSync_API(); - $sugarsync->create_account( sanitize_email( $_POST[ 'sugaremail' ] ), $_POST[ 'sugarpass' ] ); - } - catch ( Exception $e ) { - BackWPup_Admin::message( 'SUGARSYNC: ' . $e->getMessage(), TRUE ); - } - } - - $_POST[ 'sugardir' ] = trailingslashit( str_replace( '//', '/', str_replace( '\\', '/', trim( sanitize_text_field( $_POST[ 'sugardir' ] ) ) ) ) ); - if ( substr( $_POST[ 'sugardir' ], 0, 1 ) == '/' ) - $_POST[ 'sugardir' ] = substr( $_POST[ 'sugardir' ], 1 ); - if ( $_POST[ 'sugardir' ] == '/' ) - $_POST[ 'sugardir' ] = ''; - BackWPup_Option::update( $jobid, 'sugardir', $_POST[ 'sugardir' ] ); - - BackWPup_Option::update( $jobid, 'sugarroot', isset( $_POST[ 'sugarroot' ] ) ? sanitize_text_field( $_POST[ 'sugarroot' ] ) : '' ); - BackWPup_Option::update( $jobid, 'sugarmaxbackups', isset( $_POST[ 'sugarmaxbackups' ] ) ? absint( $_POST[ 'sugarmaxbackups' ] ) : 0 ); - } - - /** - * @param $jobdest - * @param $backupfile - */ - public function file_delete( $jobdest, $backupfile ) { - - $files = get_site_transient( 'backwpup_' . strtolower( $jobdest ) ); - list( $jobid, $dest ) = explode( '_', $jobdest ); - - if ( BackWPup_Option::get( $jobid, 'sugarrefreshtoken' ) ) { - try { - $sugarsync = new BackWPup_Destination_SugarSync_API( BackWPup_Option::get( $jobid, 'sugarrefreshtoken' ) ); - $sugarsync->delete( urldecode( $backupfile ) ); - //update file list - foreach ( $files as $key => $file ) { - if ( is_array( $file ) && $file[ 'file' ] == $backupfile ) - unset( $files[ $key ] ); - } - unset( $sugarsync ); - } - catch ( Exception $e ) { - BackWPup_Admin::message( 'SUGARSYNC: ' . $e->getMessage(), TRUE ); - } - } - - set_site_transient( 'backwpup_' . strtolower( $jobdest ), $files, YEAR_IN_SECONDS ); - } - - /** - * @param $jobid - * @param $get_file - * @param $local_file_path - */ - public function file_download( $jobid, $get_file, $local_file_path = null ) { - - try { - $sugarsync = new BackWPup_Destination_SugarSync_API( BackWPup_Option::get( $jobid, 'sugarrefreshtoken' ) ); - $response = $sugarsync->get( urldecode( $get_file ) ); - if ( $level = ob_get_level() ) { - for ( $i = 0; $i < $level; $i ++ ) { - ob_end_clean(); - } - } - @set_time_limit( 300 ); - nocache_headers(); - header( 'Content-Description: File Transfer' ); - header( 'Content-Type: ' . MimeTypeExtractor::fromFilePath( (string) $response->displayName ) ); - header( 'Content-Disposition: attachment; filename="' . (string) $response->displayName . '"' ); - header( 'Content-Transfer-Encoding: binary' ); - header( 'Content-Length: ' . (int) $response->size ); - echo $sugarsync->download( urldecode( $get_file ) ); - die(); - } - catch ( Exception $e ) { - die( $e->getMessage() ); - } - } - - /** - * @inheritdoc - */ - public function file_get_list( $jobdest ) { - - $list = (array) get_site_transient( 'backwpup_' . strtolower( $jobdest ) ); - $list = array_filter( $list ); - - return $list; - } - - /** - * @param $job_object BackWPup_Job - * @return bool - */ - public function job_run_archive( BackWPup_Job $job_object ) { - - $job_object->substeps_todo = 2 + $job_object->backup_filesize; - $job_object->log( sprintf( __( '%d. Try to send backup to SugarSync …', 'backwpup' ), $job_object->steps_data[ $job_object->step_working ][ 'STEP_TRY' ] ), E_USER_NOTICE ); - - try { - $sugarsync = new BackWPup_Destination_SugarSync_API( $job_object->job[ 'sugarrefreshtoken' ] ); - //Check Quota - $user = $sugarsync->user(); - if ( ! empty( $user->nickname ) ) - $job_object->log( sprintf( __( 'Authenticated to SugarSync with nickname %s', 'backwpup' ), $user->nickname ), E_USER_NOTICE ); - $sugarsyncfreespase = (float)$user->quota->limit - (float)$user->quota->usage; //float fixes bug for display of no free space - if ( $job_object->backup_filesize > $sugarsyncfreespase ) { - $job_object->log( sprintf( _x( 'Not enough disk space available on SugarSync. Available: %s.','Available space on SugarSync', 'backwpup' ), size_format( $sugarsyncfreespase, 2 ) ), E_USER_ERROR ); - $job_object->substeps_todo = 1 + $job_object->backup_filesize; - - return TRUE; - } - else { - $job_object->log( sprintf( __( '%s available at SugarSync', 'backwpup' ), size_format( $sugarsyncfreespase, 2 ) ), E_USER_NOTICE ); - } - //Create and change folder - $sugarsync->mkdir( $job_object->job[ 'sugardir' ], $job_object->job[ 'sugarroot' ] ); - $dirid = $sugarsync->chdir( $job_object->job[ 'sugardir' ], $job_object->job[ 'sugarroot' ] ); - //Upload to SugarSync - $job_object->substeps_done = 0; - $job_object->log( __( 'Starting upload to SugarSync …', 'backwpup' ), E_USER_NOTICE ); - self::$backwpup_job_object = &$job_object; - $reponse = $sugarsync->upload( $job_object->backup_folder . $job_object->backup_file ); - if ( is_object( $reponse ) ) { - if ( ! empty( $job_object->job[ 'jobid' ] ) ) - BackWPup_Option::update( $job_object->job[ 'jobid' ], 'lastbackupdownloadurl', network_admin_url( 'admin.php' ) . '?page=backwpupbackups&action=downloadsugarsync&file=' . (string)$reponse . '&jobid=' . $job_object->job[ 'jobid' ] ); - $job_object->substeps_done ++; - $job_object->log( sprintf( __( 'Backup transferred to %s', 'backwpup' ), 'https://' . $user->nickname . '.sugarsync.com/' . $sugarsync->showdir( $dirid ) . $job_object->backup_file ), E_USER_NOTICE ); - } - else { - $job_object->log( __( 'Cannot transfer backup to SugarSync!', 'backwpup' ), E_USER_ERROR ); - - return FALSE; - } - - $backupfilelist = array(); - $files = array(); - $filecounter = 0; - $dir = $sugarsync->showdir( $dirid ); - $getfiles = $sugarsync->getcontents( 'file' ); - if ( is_object( $getfiles ) ) { - foreach ( $getfiles->file as $getfile ) { - $getfile->displayName = utf8_decode( (string)$getfile->displayName ); - if ( $this->is_backup_archive( $getfile->displayName ) && $this->is_backup_owned_by_job( $getfile->displayName, $job_object->job['jobid'] ) == true ) - $backupfilelist[ strtotime( (string)$getfile->lastModified ) ] = (string)$getfile->ref; - $files[ $filecounter ][ 'folder' ] = 'https://' . (string)$user->nickname . '.sugarsync.com/' . $dir; - $files[ $filecounter ][ 'file' ] = (string)$getfile->ref; - $files[ $filecounter ][ 'filename' ] = (string)$getfile->displayName; - $files[ $filecounter ][ 'downloadurl' ] = network_admin_url( 'admin.php' ) . '?page=backwpupbackups&action=downloadsugarsync&file=' . (string)$getfile->ref . '&jobid=' . $job_object->job[ 'jobid' ]; - $files[ $filecounter ][ 'filesize' ] = (int)$getfile->size; - $files[ $filecounter ][ 'time' ] = strtotime( (string)$getfile->lastModified ) + ( get_option( 'gmt_offset' ) * 3600 ); - $filecounter ++; - } - } - if ( ! empty( $job_object->job[ 'sugarmaxbackups' ] ) && $job_object->job[ 'sugarmaxbackups' ] > 0 ) { //Delete old backups - if ( count( $backupfilelist ) > $job_object->job[ 'sugarmaxbackups' ] ) { - ksort( $backupfilelist ); - $numdeltefiles = 0; - while ( $file = array_shift( $backupfilelist ) ) { - if ( count( $backupfilelist ) < $job_object->job[ 'sugarmaxbackups' ] ) - break; - $sugarsync->delete( $file ); //delete files on Cloud - foreach ( $files as $key => $filedata ) { - if ( $filedata[ 'file' ] == $file ) - unset( $files[ $key ] ); - } - $numdeltefiles ++; - } - if ( $numdeltefiles > 0 ) - $job_object->log( sprintf( _n( 'One file deleted on SugarSync folder', '%d files deleted on SugarSync folder', $numdeltefiles, 'backwpup' ), $numdeltefiles ), E_USER_NOTICE ); - } - } - set_site_transient( 'BackWPup_' . $job_object->job[ 'jobid' ] . '_SUGARSYNC', $files, YEAR_IN_SECONDS ); - } - catch ( Exception $e ) { - $job_object->log( E_USER_ERROR, sprintf( __( 'SugarSync API: %s', 'backwpup' ), $e->getMessage() ), $e->getFile(), $e->getLine() ); - - return FALSE; - } - $job_object->substeps_done ++; - - return TRUE; - } - - /** - * @param $job_settings array - * @return bool - */ - public function can_run( array $job_settings ) { - - if ( empty( $job_settings[ 'sugarrefreshtoken' ] ) ) - return FALSE; - - if ( empty( $job_settings[ 'sugarroot' ] ) ) - return FALSE; - - return TRUE; - } + } + + public function edit_form_post_save(int $jobid): void + { + if (!empty($_POST['sugaremail']) && !empty($_POST['sugarpass']) && $_POST['authbutton'] === __('Authenticate with Sugarsync!', 'backwpup')) { + try { + $sugarsync = new BackWPup_Destination_SugarSync_API(); + $refresh_token = $sugarsync->get_Refresh_Token(sanitize_email($_POST['sugaremail']), $_POST['sugarpass']); + if (!empty($refresh_token)) { + BackWPup_Option::update($jobid, 'sugarrefreshtoken', $refresh_token); + } + } catch (Exception $e) { + BackWPup_Admin::message('SUGARSYNC: ' . $e->getMessage(), true); + } + } + + if (isset($_POST['authbutton']) && $_POST['authbutton'] === __('Delete Sugarsync authentication!', 'backwpup')) { + BackWPup_Option::delete($jobid, 'sugarrefreshtoken'); + } + + if (isset($_POST['authbutton']) && $_POST['authbutton'] === __('Create Sugarsync account', 'backwpup')) { + try { + $sugarsync = new BackWPup_Destination_SugarSync_API(); + $sugarsync->create_account(sanitize_email($_POST['sugaremail']), $_POST['sugarpass']); + } catch (Exception $e) { + BackWPup_Admin::message('SUGARSYNC: ' . $e->getMessage(), true); + } + } + + $_POST['sugardir'] = trailingslashit(str_replace('//', '/', str_replace('\\', '/', trim(sanitize_text_field($_POST['sugardir']))))); + if (substr($_POST['sugardir'], 0, 1) == '/') { + $_POST['sugardir'] = substr($_POST['sugardir'], 1); + } + if ($_POST['sugardir'] == '/') { + $_POST['sugardir'] = ''; + } + BackWPup_Option::update($jobid, 'sugardir', $_POST['sugardir']); + + BackWPup_Option::update($jobid, 'sugarroot', isset($_POST['sugarroot']) ? sanitize_text_field($_POST['sugarroot']) : ''); + BackWPup_Option::update($jobid, 'sugarmaxbackups', isset($_POST['sugarmaxbackups']) ? absint($_POST['sugarmaxbackups']) : 0); + } + + public function file_delete(string $jobdest, string $backupfile): void + { + $files = get_site_transient('backwpup_' . strtolower($jobdest)); + [$jobid, $dest] = explode('_', $jobdest); + + if (BackWPup_Option::get($jobid, 'sugarrefreshtoken')) { + try { + $sugarsync = new BackWPup_Destination_SugarSync_API(BackWPup_Option::get($jobid, 'sugarrefreshtoken')); + $sugarsync->delete(urldecode($backupfile)); + //update file list + foreach ($files as $key => $file) { + if (is_array($file) && $file['file'] == $backupfile) { + unset($files[$key]); + } + } + unset($sugarsync); + } catch (Exception $e) { + BackWPup_Admin::message('SUGARSYNC: ' . $e->getMessage(), true); + } + } + + set_site_transient('backwpup_' . strtolower($jobdest), $files, YEAR_IN_SECONDS); + } + + public function file_download(int $jobid, string $get_file, ?string $local_file_path = null): void + { + try { + $sugarsync = new BackWPup_Destination_SugarSync_API(BackWPup_Option::get($jobid, 'sugarrefreshtoken')); + $response = $sugarsync->get(urldecode($get_file)); + if ($level = ob_get_level()) { + for ($i = 0; $i < $level; ++$i) { + ob_end_clean(); + } + } + + @set_time_limit(300); + + $fh = fopen(untrailingslashit(BackWPup::get_plugin_data('temp')) . '/' . basename($local_file_path ?: $get_file), 'w'); + fwrite($fh, $sugarsync->download(urldecode($get_file))); + fclose($fh); + + echo "event: message\n" . + 'data: ' . wp_json_encode([ + 'state' => 'done', + 'message' => esc_html__( + 'Your download is being generated …', + 'backwpup' + ), + ]) . "\n\n"; + flush(); + + exit(); + } catch (Exception $e) { + exit($e->getMessage()); + } + } + + /** + * {@inheritdoc} + */ + public function file_get_list(string $jobdest): array + { + $list = (array) get_site_transient('backwpup_' . strtolower($jobdest)); + + return array_filter($list); + } + + public function job_run_archive(BackWPup_Job $job_object): bool + { + $job_object->substeps_todo = 2 + $job_object->backup_filesize; + $job_object->log(sprintf(__('%d. Try to send backup to SugarSync …', 'backwpup'), $job_object->steps_data[$job_object->step_working]['STEP_TRY']), E_USER_NOTICE); + + try { + $sugarsync = new BackWPup_Destination_SugarSync_API($job_object->job['sugarrefreshtoken']); + //Check Quota + $user = $sugarsync->user(); + if (!empty($user->nickname)) { + $job_object->log(sprintf(__('Authenticated to SugarSync with nickname %s', 'backwpup'), $user->nickname), E_USER_NOTICE); + } + $sugarsyncfreespase = (float) $user->quota->limit - (float) $user->quota->usage; //float fixes bug for display of no free space + if ($job_object->backup_filesize > $sugarsyncfreespase) { + $job_object->log(sprintf(_x('Not enough disk space available on SugarSync. Available: %s.', 'Available space on SugarSync', 'backwpup'), size_format($sugarsyncfreespase, 2)), E_USER_ERROR); + $job_object->substeps_todo = 1 + $job_object->backup_filesize; + + return true; + } + + $job_object->log(sprintf(__('%s available at SugarSync', 'backwpup'), size_format($sugarsyncfreespase, 2)), E_USER_NOTICE); + + //Create and change folder + $sugarsync->mkdir($job_object->job['sugardir'], $job_object->job['sugarroot']); + $dirid = $sugarsync->chdir($job_object->job['sugardir'], $job_object->job['sugarroot']); + //Upload to SugarSync + $job_object->substeps_done = 0; + $job_object->log(__('Starting upload to SugarSync …', 'backwpup'), E_USER_NOTICE); + self::$backwpup_job_object = &$job_object; + $response = $sugarsync->upload($job_object->backup_folder . $job_object->backup_file); + if (is_object($response)) { + if (!empty($job_object->job['jobid'])) { + BackWPup_Option::update( + $job_object->job['jobid'], + 'lastbackupdownloadurl', + sprintf( + '%s?page=backwpupbackups&action=downloadsugarsync&file=%s&local_file=%s&jobid=%d', + network_admin_url('admin.php'), + (string) $response, + $job_object->backup_file, + $job_object->job['jobid'] + ) + ); + } + ++$job_object->substeps_done; + $job_object->log(sprintf(__('Backup transferred to %s', 'backwpup'), 'https://' . $user->nickname . '.sugarsync.com/' . $sugarsync->showdir($dirid) . $job_object->backup_file), E_USER_NOTICE); + } else { + $job_object->log(__('Cannot transfer backup to SugarSync!', 'backwpup'), E_USER_ERROR); + + return false; + } + + $backupfilelist = []; + $files = []; + $filecounter = 0; + $dir = $sugarsync->showdir($dirid); + $getfiles = $sugarsync->getcontents('file'); + if (is_object($getfiles)) { + foreach ($getfiles->file as $getfile) { + $getfile->displayName = utf8_decode((string) $getfile->displayName); + if ($this->is_backup_archive($getfile->displayName) && $this->is_backup_owned_by_job($getfile->displayName, $job_object->job['jobid']) == true) { + $backupfilelist[strtotime((string) $getfile->lastModified)] = (string) $getfile->ref; + } + $files[$filecounter]['folder'] = 'https://' . (string) $user->nickname . '.sugarsync.com/' . $dir; + $files[$filecounter]['file'] = (string) $getfile->ref; + $files[$filecounter]['filename'] = (string) $getfile->displayName; + $files[$filecounter]['downloadurl'] = sprintf( + '%s?page=backwpupbackups&action=downloadsugarsync&file=%s&local_file=%s&jobid=%d', + network_admin_url('admin.php'), + (string) $getfile->ref, + (string) $getfile->displayName, + $job_object->job['jobid'] + ); + $files[$filecounter]['filesize'] = (int) $getfile->size; + $files[$filecounter]['time'] = strtotime((string) $getfile->lastModified) + (get_option('gmt_offset') * 3600); + ++$filecounter; + } + } + if (!empty($job_object->job['sugarmaxbackups']) && $job_object->job['sugarmaxbackups'] > 0) { //Delete old backups + if (count($backupfilelist) > $job_object->job['sugarmaxbackups']) { + ksort($backupfilelist); + $numdeltefiles = 0; + + while ($file = array_shift($backupfilelist)) { + if (count($backupfilelist) < $job_object->job['sugarmaxbackups']) { + break; + } + $sugarsync->delete($file); //delete files on Cloud + + foreach ($files as $key => $filedata) { + if ($filedata['file'] == $file) { + unset($files[$key]); + } + } + ++$numdeltefiles; + } + if ($numdeltefiles > 0) { + $job_object->log(sprintf(_n('One file deleted on SugarSync folder', '%d files deleted on SugarSync folder', $numdeltefiles, 'backwpup'), $numdeltefiles), E_USER_NOTICE); + } + } + } + set_site_transient('BackWPup_' . $job_object->job['jobid'] . '_SUGARSYNC', $files, YEAR_IN_SECONDS); + } catch (Exception $e) { + $job_object->log(sprintf(__('SugarSync API: %s', 'backwpup'), $e->getMessage()), E_USER_ERROR, $e->getFile(), $e->getLine()); + + return false; + } + ++$job_object->substeps_done; + + return true; + } + + public function can_run(array $job_settings): bool + { + if (empty($job_settings['sugarrefreshtoken'])) { + return false; + } + + return !(empty($job_settings['sugarroot'])); + } } -class BackWPup_Destination_SugarSync_API { - - /** - * url for the sugarsync-api - */ - const API_URL = 'https://api.sugarsync.com'; - - /** - * - * @var string - */ - protected $folder = ''; - - /** - * @var mixed|string - */ - protected $encoding = 'UTF-8'; - - /** - * @var null|string - */ - protected $refresh_token = ''; - - /** - * The Auth-token - * - * @var string - */ - protected $access_token = ''; - - // class methods - /** - * Default constructor/Auth - */ - public function __construct( $refresh_token = NULL ) { - - //auth xml - $this->encoding = mb_internal_encoding(); - - //get access token - if ( isset( $refresh_token ) and ! empty( $refresh_token ) ) { - $this->refresh_token = $refresh_token; - $this->get_Access_Token(); - } - } - - /** - * Make the call - * - * @return string - * - * @param string $url The url to call. - * @param string $data - * @param string $method - * @throws BackWPup_Destination_SugarSync_API_Exception - * @internal param $string [optiona] $data File on put, xml on post. - * @internal param $string [optional] $method The method to use. Possible values are GET, POST, PUT, DELETE. - */ - private function doCall( $url, $data = '', $method = 'GET' ) { - - // allowed methods - $allowedMethods = array( 'GET', 'POST', 'PUT', 'DELETE' ); - - // redefine - $url = (string)$url; - $method = (string)$method; - - // validate method - if ( ! in_array( $method, $allowedMethods, true ) ) - throw new BackWPup_Destination_SugarSync_API_Exception( 'Unknown method (' . $method . '). Allowed methods are: ' . implode( ', ', $allowedMethods ) ); - - // check auth token - if ( empty( $this->access_token ) ) - throw new BackWPup_Destination_SugarSync_API_Exception( 'Auth Token not set correctly!' ); - else - $headers[ ] = 'Authorization: ' . $this->access_token; - $headers[ ] = 'Expect:'; - - // init - $curl = curl_init(); - //set options - curl_setopt( $curl, CURLOPT_URL, $url ); - curl_setopt( $curl, CURLOPT_USERAGENT, BackWPup::get_plugin_data( 'User-Agent' ) ); - if ( ini_get( 'open_basedir' ) == '' ) curl_setopt( $curl, CURLOPT_FOLLOWLOCATION, TRUE ); - curl_setopt( $curl, CURLOPT_RETURNTRANSFER, TRUE ); - if ( BackWPup::get_plugin_data( 'cacert' ) ) { - curl_setopt( $curl, CURLOPT_SSLVERSION, 1 ); - curl_setopt( $curl, CURLOPT_SSL_VERIFYPEER, TRUE ); - curl_setopt( $curl, CURLOPT_CAINFO, BackWPup::get_plugin_data( 'cacert' ) ); - curl_setopt( $curl, CURLOPT_CAPATH, dirname( BackWPup::get_plugin_data( 'cacert' ) ) ); - } else { - curl_setopt( $curl, CURLOPT_SSL_VERIFYPEER, FALSE ); - } - - if ( $method == 'POST' ) { - $headers[ ] = 'Content-Type: application/xml; charset=UTF-8'; - curl_setopt( $curl, CURLOPT_POSTFIELDS, $data ); - curl_setopt( $curl, CURLOPT_POST, TRUE ); - $headers[ ] = 'Content-Length: ' . strlen( $data ); - } - elseif ( $method == 'PUT' ) { - if ( is_readable( $data ) ) { - $headers[ ] = 'Content-Length: ' . filesize( $data ); - $datafilefd = fopen( $data, 'rb' ); - curl_setopt( $curl, CURLOPT_PUT, TRUE ); - curl_setopt( $curl, CURLOPT_INFILE, $datafilefd ); - curl_setopt( $curl, CURLOPT_INFILESIZE, filesize( $data ) ); - curl_setopt( $curl, CURLOPT_READFUNCTION, array( BackWPup_Destination_SugarSync::$backwpup_job_object, 'curl_read_callback' ) ); - } - else { - throw new BackWPup_Destination_SugarSync_API_Exception( 'Is not a readable file:' . $data ); - } - } - elseif ( $method == 'DELETE' ) { - curl_setopt( $curl, CURLOPT_CUSTOMREQUEST, 'DELETE' ); - } - else { - curl_setopt( $curl, CURLOPT_POST, FALSE ); - } - - // set headers - curl_setopt( $curl, CURLOPT_HTTPHEADER, $headers ); - curl_setopt( $curl, CURLINFO_HEADER_OUT, TRUE ); - // execute - $response = curl_exec( $curl ); - $curlgetinfo = curl_getinfo( $curl ); - - // fetch curl errors - if ( curl_errno( $curl ) != 0 ) - throw new BackWPup_Destination_SugarSync_API_Exception( 'cUrl Error: ' . curl_error( $curl ) ); - curl_close( $curl ); - if ( ! empty( $datafilefd ) && is_resource( $datafilefd ) ) - fclose( $datafilefd ); - - if ( $curlgetinfo[ 'http_code' ] >= 200 && $curlgetinfo[ 'http_code' ] < 300 ) { - if ( FALSE !== stripos( $curlgetinfo[ 'content_type' ], 'xml' ) && ! empty( $response ) ) - return simplexml_load_string( $response ); - else - return $response; - } - else { - if ( $curlgetinfo[ 'http_code' ] == 401 ) - throw new BackWPup_Destination_SugarSync_API_Exception( 'Http Error: ' . $curlgetinfo[ 'http_code' ] . ' Authorization required.' ); - elseif ( $curlgetinfo[ 'http_code' ] == 403 ) - throw new BackWPup_Destination_SugarSync_API_Exception( 'Http Error: ' . $curlgetinfo[ 'http_code' ] . ' (Forbidden) Authentication failed.' ); - elseif ( $curlgetinfo[ 'http_code' ] == 404 ) - throw new BackWPup_Destination_SugarSync_API_Exception( 'Http Error: ' . $curlgetinfo[ 'http_code' ] . ' Not found' ); - else - throw new BackWPup_Destination_SugarSync_API_Exception( 'Http Error: ' . $curlgetinfo[ 'http_code' ] ); - } - } - - - /** - * @return string - * @throws BackWPup_Destination_SugarSync_API_Exception - */ - private function get_Access_Token() { - - $auth = ''; - $auth .= ''; - $auth .= '' . get_site_option( 'backwpup_cfg_sugarsynckey', base64_decode( "TlRBek1EY3lOakV6TkRrMk1URXhNemM0TWpJ" ) ) . ''; - $auth .= '' . BackWPup_Encryption::decrypt( get_site_option( 'backwpup_cfg_sugarsyncsecret', base64_decode( "TkRFd01UazRNVEpqTW1Ga05EaG1NR0k1TVRFNFpqa3lPR1V6WlRVMk1tTQ==" ) ) ) . ''; - $auth .= '' . trim( $this->refresh_token ) . ''; - $auth .= ''; - // init - $curl = curl_init(); - //set options - curl_setopt( $curl, CURLOPT_URL, self::API_URL . '/authorization' ); - curl_setopt( $curl, CURLOPT_USERAGENT, BackWPup::get_plugin_data( 'User-Agent' ) ); - if ( ini_get( 'open_basedir' ) == '' ) curl_setopt( $curl, CURLOPT_FOLLOWLOCATION, TRUE ); - curl_setopt( $curl, CURLOPT_RETURNTRANSFER, TRUE ); - if ( BackWPup::get_plugin_data( 'cacert' ) ) { - curl_setopt( $curl, CURLOPT_SSLVERSION, 1 ); - curl_setopt( $curl, CURLOPT_SSL_VERIFYPEER, TRUE ); - curl_setopt( $curl, CURLOPT_CAINFO, BackWPup::get_plugin_data( 'cacert' ) ); - curl_setopt( $curl, CURLOPT_CAPATH, dirname( BackWPup::get_plugin_data( 'cacert' ) ) ); - } else { - curl_setopt( $curl, CURLOPT_SSL_VERIFYPEER, FALSE ); - } - curl_setopt( $curl, CURLOPT_HEADER, TRUE ); - curl_setopt( $curl, CURLOPT_HTTPHEADER, array( 'Content-Type: application/xml; charset=UTF-8', 'Content-Length: ' . strlen( $auth ) ) ); - curl_setopt( $curl, CURLOPT_POSTFIELDS, $auth ); - curl_setopt( $curl, CURLOPT_POST, TRUE ); - // execute - $response = curl_exec( $curl ); - $curlgetinfo = curl_getinfo( $curl ); - // fetch curl errors - if ( curl_errno( $curl ) != 0 ) - throw new BackWPup_Destination_SugarSync_API_Exception( 'cUrl Error: ' . curl_error( $curl ) ); - - curl_close( $curl ); - - if ( $curlgetinfo[ 'http_code' ] >= 200 && $curlgetinfo[ 'http_code' ] < 300 ) { - if ( preg_match( '/Location:(.*?)\r/i', $response, $matches ) ) - $this->access_token = trim( $matches[ 1 ] ); - - return $this->access_token; - } - else { - if ( $curlgetinfo[ 'http_code' ] == 401 ) - throw new BackWPup_Destination_SugarSync_API_Exception( 'Http Error: ' . $curlgetinfo[ 'http_code' ] . ' Authorization required.' ); - elseif ( $curlgetinfo[ 'http_code' ] == 403 ) - throw new BackWPup_Destination_SugarSync_API_Exception( 'Http Error: ' . $curlgetinfo[ 'http_code' ] . ' (Forbidden) Authentication failed.' ); - elseif ( $curlgetinfo[ 'http_code' ] == 404 ) - throw new BackWPup_Destination_SugarSync_API_Exception( 'Http Error: ' . $curlgetinfo[ 'http_code' ] . ' Not found' ); - else - throw new BackWPup_Destination_SugarSync_API_Exception( 'Http Error: ' . $curlgetinfo[ 'http_code' ] ); - } - } - - /** - * @param $email - * @param $password - * @return null|string - * @throws BackWPup_Destination_SugarSync_API_Exception - */ - public function get_Refresh_Token( $email, $password ) { - - $auth = ''; - $auth .= ''; - $auth .= '' . mb_convert_encoding( $email, 'UTF-8', $this->encoding ) . ''; - $auth .= '' . mb_convert_encoding( $password, 'UTF-8', $this->encoding ) . ''; - $auth .= '' . get_site_option( 'backwpup_cfg_sugarsyncappid', "/sc/5030726/449_18207099" ) . ''; - $auth .= '' . get_site_option( 'backwpup_cfg_sugarsynckey',base64_decode( "TlRBek1EY3lOakV6TkRrMk1URXhNemM0TWpJ" ) ) . ''; - $auth .= '' . BackWPup_Encryption::decrypt( get_site_option( 'backwpup_cfg_sugarsyncsecret', base64_decode( "TkRFd01UazRNVEpqTW1Ga05EaG1NR0k1TVRFNFpqa3lPR1V6WlRVMk1tTQ==" ) ) ) . ''; - $auth .= ''; - // init - $curl = curl_init(); - //set options - curl_setopt( $curl, CURLOPT_URL, self::API_URL . '/app-authorization' ); - curl_setopt( $curl, CURLOPT_USERAGENT, BackWPup::get_plugin_data( 'User-Agent' ) ); - if ( ini_get( 'open_basedir' ) == '' ) curl_setopt( $curl, CURLOPT_FOLLOWLOCATION, TRUE ); - curl_setopt( $curl, CURLOPT_RETURNTRANSFER, TRUE ); - if ( BackWPup::get_plugin_data( 'cacert' ) ) { - curl_setopt( $curl, CURLOPT_SSLVERSION, 1 ); - curl_setopt( $curl, CURLOPT_SSL_VERIFYPEER, TRUE ); - curl_setopt( $curl, CURLOPT_CAINFO, BackWPup::get_plugin_data( 'cacert' ) ); - curl_setopt( $curl, CURLOPT_CAPATH, dirname( BackWPup::get_plugin_data( 'cacert' ) ) ); - } else { - curl_setopt( $curl, CURLOPT_SSL_VERIFYPEER, FALSE ); - } - curl_setopt( $curl, CURLOPT_HEADER, TRUE ); - curl_setopt( $curl, CURLOPT_POSTFIELDS, $auth ); - curl_setopt( $curl, CURLOPT_POST, TRUE ); - curl_setopt( $curl, CURLOPT_HTTPHEADER, array( 'Content-Type: application/xml; charset=UTF-8', 'Content-Length: ' . strlen( $auth ) ) ); - // execute - $response = curl_exec( $curl ); - $curlgetinfo = curl_getinfo( $curl ); - // fetch curl errors - if ( curl_errno( $curl ) != 0 ) - throw new BackWPup_Destination_SugarSync_API_Exception( 'cUrl Error: ' . curl_error( $curl ) ); - - curl_close( $curl ); - - if ( $curlgetinfo[ 'http_code' ] >= 200 && $curlgetinfo[ 'http_code' ] < 300 ) { - if ( preg_match( '/Location:(.*?)\r/i', $response, $matches ) ) - $this->refresh_token = trim( $matches[ 1 ] ); - - return $this->refresh_token; - } - else { - if ( $curlgetinfo[ 'http_code' ] == 401 ) - throw new BackWPup_Destination_SugarSync_API_Exception( 'Http Error: ' . $curlgetinfo[ 'http_code' ] . ' Authorization required.' ); - elseif ( $curlgetinfo[ 'http_code' ] == 403 ) - throw new BackWPup_Destination_SugarSync_API_Exception( 'Http Error: ' . $curlgetinfo[ 'http_code' ] . ' (Forbidden) Authentication failed.' ); - elseif ( $curlgetinfo[ 'http_code' ] == 404 ) - throw new BackWPup_Destination_SugarSync_API_Exception( 'Http Error: ' . $curlgetinfo[ 'http_code' ] . ' Not found' ); - else - throw new BackWPup_Destination_SugarSync_API_Exception( 'Http Error: ' . $curlgetinfo[ 'http_code' ] ); - } - } - - /** - * @param $email - * @param $password - * @throws BackWPup_Destination_SugarSync_API_Exception - */ - public function create_account( $email, $password ) { - - $auth = ''; - $auth .= ''; - $auth .= '' . mb_convert_encoding( $email, 'UTF-8', $this->encoding ) . ''; - $auth .= '' . mb_convert_encoding( $password, 'UTF-8', $this->encoding ) . ''; - $auth .= '' . get_site_option( 'backwpup_cfg_sugarsynckey', base64_decode( "TlRBek1EY3lOakV6TkRrMk1URXhNemM0TWpJ" ) ) . ''; - $auth .= '' . BackWPup_Encryption::decrypt( get_site_option( 'backwpup_cfg_sugarsyncsecret', base64_decode( "TkRFd01UazRNVEpqTW1Ga05EaG1NR0k1TVRFNFpqa3lPR1V6WlRVMk1tTQ==" ) ) ) . ''; - $auth .= ''; - // init - $curl = curl_init(); - //set options - curl_setopt( $curl, CURLOPT_URL, 'https://provisioning-api.sugarsync.com/users' ); - curl_setopt( $curl, CURLOPT_USERAGENT, BackWPup::get_plugin_data( 'User-Agent' ) ); - if ( ini_get( 'open_basedir' ) == '' ) curl_setopt( $curl, CURLOPT_FOLLOWLOCATION, TRUE ); - curl_setopt( $curl, CURLOPT_RETURNTRANSFER, TRUE ); - if ( BackWPup::get_plugin_data( 'cacert' ) ) { - curl_setopt( $curl, CURLOPT_SSLVERSION, 1 ); - curl_setopt( $curl, CURLOPT_SSL_VERIFYPEER, TRUE ); - curl_setopt( $curl, CURLOPT_CAINFO, BackWPup::get_plugin_data( 'cacert' ) ); - curl_setopt( $curl, CURLOPT_CAPATH, dirname( BackWPup::get_plugin_data( 'cacert' ) ) ); - } else { - curl_setopt( $curl, CURLOPT_SSL_VERIFYPEER, FALSE ); - } - curl_setopt( $curl, CURLOPT_HEADER, TRUE ); - curl_setopt( $curl, CURLOPT_HTTPHEADER, array( 'Content-Type: application/xml; charset=UTF-8', 'Content-Length: ' . strlen( $auth ) ) ); - curl_setopt( $curl, CURLOPT_POSTFIELDS, $auth ); - curl_setopt( $curl, CURLOPT_POST, TRUE ); - // execute - $response = curl_exec( $curl ); - $curlgetinfo = curl_getinfo( $curl ); - // fetch curl errors - if ( curl_errno( $curl ) != 0 ) - throw new BackWPup_Destination_SugarSync_API_Exception( 'cUrl Error: ' . curl_error( $curl ) ); - - curl_close( $curl ); - - if ( $curlgetinfo[ 'http_code' ] == 201 ) { - throw new BackWPup_Destination_SugarSync_API_Exception( 'Account created.' ); - } - else { - if ( $curlgetinfo[ 'http_code' ] == 400 ) - throw new BackWPup_Destination_SugarSync_API_Exception( 'Http Error: ' . $curlgetinfo[ 'http_code' ] . ' ' . substr( $response, $curlgetinfo[ 'header_size' ] ) ); - elseif ( $curlgetinfo[ 'http_code' ] == 401 ) - throw new BackWPup_Destination_SugarSync_API_Exception( 'Http Error: ' . $curlgetinfo[ 'http_code' ] . ' Developer credentials cannot be verified. Either a developer with the specified accessKeyId does not exist or the privateKeyID does not match an assigned accessKeyId.' ); - elseif ( $curlgetinfo[ 'http_code' ] == 403 ) - throw new BackWPup_Destination_SugarSync_API_Exception( 'Http Error: ' . $curlgetinfo[ 'http_code' ] . ' ' . substr( $response, $curlgetinfo[ 'header_size' ] ) ); - elseif ( $curlgetinfo[ 'http_code' ] == 503 ) - throw new BackWPup_Destination_SugarSync_API_Exception( 'Http Error: ' . $curlgetinfo[ 'http_code' ] . ' ' . substr( $response, $curlgetinfo[ 'header_size' ] ) ); - else - throw new BackWPup_Destination_SugarSync_API_Exception( 'Http Error: ' . $curlgetinfo[ 'http_code' ] ); - } - } - - /** - * @param $folder - * @param string $root - * @return string - * @throws BackWPup_Destination_SugarSync_API_Exception - */ - public function chdir( $folder, $root = '' ) { - - $folder = rtrim( $folder, '/' ); - if ( substr( $folder, 0, 1 ) == '/' || empty( $this->folder ) ) { - if ( ! empty( $root ) ) - $this->folder = $root; - else - throw new BackWPup_Destination_SugarSync_API_Exception( 'chdir: root folder must set!' ); - } - $folders = explode( '/', $folder ); - foreach ( $folders as $dir ) { - if ( $dir == '..' ) { - $contents = $this->doCall( $this->folder ); - if ( ! empty( $contents->parent ) ) - $this->folder = $contents->parent; - } - elseif ( ! empty( $dir ) && $dir != '.' ) { - $isdir = FALSE; - $contents = $this->getcontents( 'folder' ); - foreach ( $contents->collection as $collection ) { - if ( strtolower( $collection->displayName ) == strtolower( $dir ) ) { - $isdir = TRUE; - $this->folder = $collection->ref; - break; - } - } - if ( ! $isdir ) - throw new BackWPup_Destination_SugarSync_API_Exception( 'chdir: Folder ' . $folder . ' not exitst' ); - } - } - - return $this->folder; - } - - /** - * @param $folderid - * @return string - */ - public function showdir( $folderid ) { - - $showfolder = ''; - while ( $folderid ) { - $contents = $this->doCall( $folderid ); - $showfolder = $contents->displayName . '/' . $showfolder; - if ( isset( $contents->parent ) ) - $folderid = $contents->parent; - else - break; - } - - return $showfolder; - } - - /** - * @param $folder - * @param string $root - * @return bool - * @throws BackWPup_Destination_SugarSync_API_Exception - */ - public function mkdir( $folder, $root = '' ) { - - $savefolder = $this->folder; - $folder = rtrim( $folder, '/' ); - if ( substr( $folder, 0, 1 ) == '/' || empty( $this->folder ) ) { - if ( ! empty( $root ) ) - $this->folder = $root; - else - throw new BackWPup_Destination_SugarSync_API_Exception( 'mkdir: root folder must set!' ); - } - $folders = explode( '/', $folder ); - foreach ( $folders as $dir ) { - if ( $dir == '..' ) { - $contents = $this->doCall( $this->folder ); - if ( ! empty( $contents->parent ) ) - $this->folder = $contents->parent; - } - elseif ( ! empty( $dir ) && $dir != '.' ) { - $isdir = FALSE; - $contents = $this->getcontents( 'folder' ); - foreach ( $contents->collection as $collection ) { - if ( strtolower( $collection->displayName ) == strtolower( $dir ) ) { - $isdir = TRUE; - $this->folder = $collection->ref; - break; - } - } - if ( ! $isdir ) { - $this->doCall( $this->folder, '' . mb_convert_encoding( $dir, 'UTF-8', $this->encoding ) . '', 'POST' ); - $contents = $this->getcontents( 'folder' ); - foreach ( $contents->collection as $collection ) { - if ( strtolower( $collection->displayName ) == strtolower( $dir ) ) { - $isdir = TRUE; - $this->folder = $collection->ref; - break; - } - } - } - } - } - $this->folder = $savefolder; - - return TRUE; - } - - - /** - * @return string - */ - public function user() { - return $this->doCall( self::API_URL . '/user' ); - } - - - /** - * @param $url - * @return string - */ - public function get( $url ) { - return $this->doCall( $url, '', 'GET' ); - } - - /** - * @param $url - * @return string - */ - public function download( $url ) { - return $this->doCall( $url . '/data' ); - } - - /** - * @param $url - * @return string - */ - public function delete( $url ) { - return $this->doCall( $url, '', 'DELETE' ); - } - - - /** - * @param string $type - * @param int $start - * @param int $max - * @return string - */ - public function getcontents( $type = '', $start = 0, $max = 500 ) { - - $parameters = ''; - - if ( strtolower( $type ) == 'folder' || strtolower( $type ) == 'file' ) - $parameters .= 'type=' . strtolower( $type ); - if ( ! empty( $start ) && is_integer( $start ) ) { - if ( ! empty( $parameters ) ) - $parameters .= '&'; - $parameters .= 'start=' . $start; - - } - if ( ! empty( $max ) && is_integer( $max ) ) { - if ( ! empty( $parameters ) ) - $parameters .= '&'; - $parameters .= 'max=' . $max; - } - - $request = $this->doCall( $this->folder . '/contents?' . $parameters ); - - return $request; - } - - /** - * @param $file - * @param string $name - * @return mixed - */ - public function upload( $file, $name = '' ) { - - if ( empty( $name ) ) { - $name = basename( $file ); - } - - $content_type = MimeTypeExtractor::fromFilePath( $file ); - - $xmlrequest = ''; - $xmlrequest .= ''; - $xmlrequest .= '' . mb_convert_encoding( $name, 'UTF-8', $this->encoding ) . ''; - $xmlrequest .= '' . $content_type . ''; - $xmlrequest .= ''; - - $this->doCall( $this->folder, $xmlrequest, 'POST' ); - $getfiles = $this->getcontents( 'file' ); - foreach ( $getfiles->file as $getfile ) { - if ( $getfile->displayName == $name ) { - $this->doCall( $getfile->ref . '/data', $file, 'PUT' ); - - return $getfile->ref; - } - } - } +class BackWPup_Destination_SugarSync_API +{ + /** + * url for the sugarsync-api. + */ + public const API_URL = 'https://api.sugarsync.com'; + + /** + * @var string + */ + protected $folder = ''; + + /** + * @var mixed|string + */ + protected $encoding = 'UTF-8'; + + /** + * @var string|null + */ + protected $refresh_token = ''; + + /** + * The Auth-token. + * + * @var string + */ + protected $access_token = ''; + + // class methods + + /** + * Default constructor/Auth. + */ + public function __construct($refresh_token = null) + { + //auth xml + $this->encoding = mb_internal_encoding(); + + //get access token + if (isset($refresh_token) and !empty($refresh_token)) { + $this->refresh_token = $refresh_token; + $this->get_Access_Token(); + } + } + + /** + * Make the call. + * + * @param string $url the url to call + * + * @throws BackWPup_Destination_SugarSync_API_Exception + * + * @return string|SimpleXMLElement + * + * @internal param $string [optiona] $data File on put, xml on post + * @internal param $string [optional] $method The method to use. Possible values are GET, POST, PUT, DELETE. + */ + private function doCall(string $url, string $data = '', string $method = 'GET') + { + $datafilefd = null; + // allowed methods + $allowedMethods = ['GET', 'POST', 'PUT', 'DELETE']; + + // redefine + $url = (string) $url; + $method = (string) $method; + $headers = []; + + // validate method + if (!in_array($method, $allowedMethods, true)) { + throw new BackWPup_Destination_SugarSync_API_Exception('Unknown method (' . $method . '). Allowed methods are: ' . implode(', ', $allowedMethods)); + } + + // check auth token + if (empty($this->access_token)) { + throw new BackWPup_Destination_SugarSync_API_Exception(__('Auth Token not set correctly!', 'backwpup')); + } + $headers[] = 'Authorization: ' . $this->access_token; + + $headers[] = 'Expect:'; + + // init + $curl = curl_init(); + //set options + curl_setopt($curl, CURLOPT_URL, $url); + curl_setopt($curl, CURLOPT_USERAGENT, BackWPup::get_plugin_data('User-Agent')); + if (ini_get('open_basedir') == '') { + curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true); + } + curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); + if (BackWPup::get_plugin_data('cacert')) { + curl_setopt($curl, CURLOPT_SSLVERSION, 1); + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, true); + curl_setopt($curl, CURLOPT_CAINFO, BackWPup::get_plugin_data('cacert')); + curl_setopt($curl, CURLOPT_CAPATH, dirname(BackWPup::get_plugin_data('cacert'))); + } else { + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); + } + + if ($method == 'POST') { + $headers[] = 'Content-Type: application/xml; charset=UTF-8'; + curl_setopt($curl, CURLOPT_POSTFIELDS, $data); + curl_setopt($curl, CURLOPT_POST, true); + $headers[] = 'Content-Length: ' . strlen($data); + } elseif ($method == 'PUT') { + if (is_readable($data)) { + $headers[] = 'Content-Length: ' . filesize($data); + $datafilefd = fopen($data, 'rb'); + curl_setopt($curl, CURLOPT_PUT, true); + curl_setopt($curl, CURLOPT_INFILE, $datafilefd); + curl_setopt($curl, CURLOPT_INFILESIZE, filesize($data)); + curl_setopt($curl, CURLOPT_READFUNCTION, [BackWPup_Destination_SugarSync::$backwpup_job_object, 'curl_read_callback']); + } else { + throw new BackWPup_Destination_SugarSync_API_Exception('Is not a readable file:' . $data); + } + } elseif ($method == 'DELETE') { + curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'DELETE'); + } else { + curl_setopt($curl, CURLOPT_POST, false); + } + + // set headers + curl_setopt($curl, CURLOPT_HTTPHEADER, $headers); + curl_setopt($curl, CURLINFO_HEADER_OUT, true); + // execute + $response = curl_exec($curl); + $curlgetinfo = curl_getinfo($curl); + + // fetch curl errors + if (curl_errno($curl) != 0) { + throw new BackWPup_Destination_SugarSync_API_Exception('cUrl Error: ' . curl_error($curl)); + } + curl_close($curl); + if (!empty($datafilefd) && is_resource($datafilefd)) { + fclose($datafilefd); + } + + if ($curlgetinfo['http_code'] >= 200 && $curlgetinfo['http_code'] < 300) { + if (false !== stripos($curlgetinfo['content_type'], 'xml') && !empty($response)) { + return simplexml_load_string($response); + } + + return $response; + } + + if ($curlgetinfo['http_code'] == 401) { + throw new BackWPup_Destination_SugarSync_API_Exception('Http Error: ' . $curlgetinfo['http_code'] . ' Authorization required.'); + } + if ($curlgetinfo['http_code'] == 403) { + throw new BackWPup_Destination_SugarSync_API_Exception('Http Error: ' . $curlgetinfo['http_code'] . ' (Forbidden) Authentication failed.'); + } + if ($curlgetinfo['http_code'] == 404) { + throw new BackWPup_Destination_SugarSync_API_Exception('Http Error: ' . $curlgetinfo['http_code'] . ' Not found'); + } + + throw new BackWPup_Destination_SugarSync_API_Exception('Http Error: ' . $curlgetinfo['http_code']); + } + + /** + * @throws BackWPup_Destination_SugarSync_API_Exception + */ + private function get_Access_Token(): string + { + $auth = ''; + $auth .= ''; + $auth .= '' . get_site_option('backwpup_cfg_sugarsynckey', base64_decode('TlRBek1EY3lOakV6TkRrMk1URXhNemM0TWpJ')) . ''; + $auth .= '' . BackWPup_Encryption::decrypt(get_site_option('backwpup_cfg_sugarsyncsecret', base64_decode('TkRFd01UazRNVEpqTW1Ga05EaG1NR0k1TVRFNFpqa3lPR1V6WlRVMk1tTQ=='))) . ''; + $auth .= '' . trim($this->refresh_token) . ''; + $auth .= ''; + // init + $curl = curl_init(); + //set options + curl_setopt($curl, CURLOPT_URL, self::API_URL . '/authorization'); + curl_setopt($curl, CURLOPT_USERAGENT, BackWPup::get_plugin_data('User-Agent')); + if (ini_get('open_basedir') == '') { + curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true); + } + curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); + if (BackWPup::get_plugin_data('cacert')) { + curl_setopt($curl, CURLOPT_SSLVERSION, 1); + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, true); + curl_setopt($curl, CURLOPT_CAINFO, BackWPup::get_plugin_data('cacert')); + curl_setopt($curl, CURLOPT_CAPATH, dirname(BackWPup::get_plugin_data('cacert'))); + } else { + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); + } + curl_setopt($curl, CURLOPT_HEADER, true); + curl_setopt($curl, CURLOPT_HTTPHEADER, ['Content-Type: application/xml; charset=UTF-8', 'Content-Length: ' . strlen($auth)]); + curl_setopt($curl, CURLOPT_POSTFIELDS, $auth); + curl_setopt($curl, CURLOPT_POST, true); + // execute + $response = curl_exec($curl); + $curlgetinfo = curl_getinfo($curl); + // fetch curl errors + if (curl_errno($curl) != 0) { + throw new BackWPup_Destination_SugarSync_API_Exception('cUrl Error: ' . curl_error($curl)); + } + + curl_close($curl); + + if ($curlgetinfo['http_code'] >= 200 && $curlgetinfo['http_code'] < 300) { + if (preg_match('/Location:(.*?)\r/i', $response, $matches)) { + $this->access_token = trim($matches[1]); + } + + return $this->access_token; + } + + if ($curlgetinfo['http_code'] == 401) { + throw new BackWPup_Destination_SugarSync_API_Exception('Http Error: ' . $curlgetinfo['http_code'] . ' Authorization required.'); + } + if ($curlgetinfo['http_code'] == 403) { + throw new BackWPup_Destination_SugarSync_API_Exception('Http Error: ' . $curlgetinfo['http_code'] . ' (Forbidden) Authentication failed.'); + } + if ($curlgetinfo['http_code'] == 404) { + throw new BackWPup_Destination_SugarSync_API_Exception('Http Error: ' . $curlgetinfo['http_code'] . ' Not found'); + } + + throw new BackWPup_Destination_SugarSync_API_Exception('Http Error: ' . $curlgetinfo['http_code']); + } + + /** + * @throws BackWPup_Destination_SugarSync_API_Exception + */ + public function get_Refresh_Token(string $email, string $password): ?string + { + $auth = ''; + $auth .= ''; + $auth .= '' . mb_convert_encoding($email, 'UTF-8', $this->encoding) . ''; + $auth .= '' . mb_convert_encoding($password, 'UTF-8', $this->encoding) . ''; + $auth .= '' . get_site_option('backwpup_cfg_sugarsyncappid', '/sc/5030726/449_18207099') . ''; + $auth .= '' . get_site_option('backwpup_cfg_sugarsynckey', base64_decode('TlRBek1EY3lOakV6TkRrMk1URXhNemM0TWpJ')) . ''; + $auth .= '' . BackWPup_Encryption::decrypt(get_site_option('backwpup_cfg_sugarsyncsecret', base64_decode('TkRFd01UazRNVEpqTW1Ga05EaG1NR0k1TVRFNFpqa3lPR1V6WlRVMk1tTQ=='))) . ''; + $auth .= ''; + // init + $curl = curl_init(); + //set options + curl_setopt($curl, CURLOPT_URL, self::API_URL . '/app-authorization'); + curl_setopt($curl, CURLOPT_USERAGENT, BackWPup::get_plugin_data('User-Agent')); + if (ini_get('open_basedir') == '') { + curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true); + } + curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); + if (BackWPup::get_plugin_data('cacert')) { + curl_setopt($curl, CURLOPT_SSLVERSION, 1); + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, true); + curl_setopt($curl, CURLOPT_CAINFO, BackWPup::get_plugin_data('cacert')); + curl_setopt($curl, CURLOPT_CAPATH, dirname(BackWPup::get_plugin_data('cacert'))); + } else { + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); + } + curl_setopt($curl, CURLOPT_HEADER, true); + curl_setopt($curl, CURLOPT_POSTFIELDS, $auth); + curl_setopt($curl, CURLOPT_POST, true); + curl_setopt($curl, CURLOPT_HTTPHEADER, ['Content-Type: application/xml; charset=UTF-8', 'Content-Length: ' . strlen($auth)]); + // execute + $response = curl_exec($curl); + $curlgetinfo = curl_getinfo($curl); + // fetch curl errors + if (curl_errno($curl) != 0) { + throw new BackWPup_Destination_SugarSync_API_Exception('cUrl Error: ' . curl_error($curl)); + } + + curl_close($curl); + + if ($curlgetinfo['http_code'] >= 200 && $curlgetinfo['http_code'] < 300) { + if (preg_match('/Location:(.*?)\r/i', $response, $matches)) { + $this->refresh_token = trim($matches[1]); + } + + return $this->refresh_token; + } + + if ($curlgetinfo['http_code'] == 401) { + throw new BackWPup_Destination_SugarSync_API_Exception('Http Error: ' . $curlgetinfo['http_code'] . ' Authorization required.'); + } + if ($curlgetinfo['http_code'] == 403) { + throw new BackWPup_Destination_SugarSync_API_Exception('Http Error: ' . $curlgetinfo['http_code'] . ' (Forbidden) Authentication failed.'); + } + if ($curlgetinfo['http_code'] == 404) { + throw new BackWPup_Destination_SugarSync_API_Exception('Http Error: ' . $curlgetinfo['http_code'] . ' Not found'); + } + + throw new BackWPup_Destination_SugarSync_API_Exception('Http Error: ' . $curlgetinfo['http_code']); + } + + /** + * @throws BackWPup_Destination_SugarSync_API_Exception + */ + public function create_account(string $email, string $password): void + { + $auth = ''; + $auth .= ''; + $auth .= '' . mb_convert_encoding($email, 'UTF-8', $this->encoding) . ''; + $auth .= '' . mb_convert_encoding($password, 'UTF-8', $this->encoding) . ''; + $auth .= '' . get_site_option('backwpup_cfg_sugarsynckey', base64_decode('TlRBek1EY3lOakV6TkRrMk1URXhNemM0TWpJ')) . ''; + $auth .= '' . BackWPup_Encryption::decrypt(get_site_option('backwpup_cfg_sugarsyncsecret', base64_decode('TkRFd01UazRNVEpqTW1Ga05EaG1NR0k1TVRFNFpqa3lPR1V6WlRVMk1tTQ=='))) . ''; + $auth .= ''; + // init + $curl = curl_init(); + //set options + curl_setopt($curl, CURLOPT_URL, 'https://provisioning-api.sugarsync.com/users'); + curl_setopt($curl, CURLOPT_USERAGENT, BackWPup::get_plugin_data('User-Agent')); + if (ini_get('open_basedir') == '') { + curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true); + } + curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); + if (BackWPup::get_plugin_data('cacert')) { + curl_setopt($curl, CURLOPT_SSLVERSION, 1); + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, true); + curl_setopt($curl, CURLOPT_CAINFO, BackWPup::get_plugin_data('cacert')); + curl_setopt($curl, CURLOPT_CAPATH, dirname(BackWPup::get_plugin_data('cacert'))); + } else { + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); + } + curl_setopt($curl, CURLOPT_HEADER, true); + curl_setopt($curl, CURLOPT_HTTPHEADER, ['Content-Type: application/xml; charset=UTF-8', 'Content-Length: ' . strlen($auth)]); + curl_setopt($curl, CURLOPT_POSTFIELDS, $auth); + curl_setopt($curl, CURLOPT_POST, true); + // execute + $response = curl_exec($curl); + $curlgetinfo = curl_getinfo($curl); + // fetch curl errors + if (curl_errno($curl) != 0) { + throw new BackWPup_Destination_SugarSync_API_Exception('cUrl Error: ' . curl_error($curl)); + } + + curl_close($curl); + + if ($curlgetinfo['http_code'] == 201) { + throw new BackWPup_Destination_SugarSync_API_Exception('Account created.'); + } + + if ($curlgetinfo['http_code'] == 400) { + throw new BackWPup_Destination_SugarSync_API_Exception('Http Error: ' . $curlgetinfo['http_code'] . ' ' . substr($response, $curlgetinfo['header_size'])); + } + if ($curlgetinfo['http_code'] == 401) { + throw new BackWPup_Destination_SugarSync_API_Exception('Http Error: ' . $curlgetinfo['http_code'] . ' Developer credentials cannot be verified. Either a developer with the specified accessKeyId does not exist or the privateKeyID does not match an assigned accessKeyId.'); + } + if ($curlgetinfo['http_code'] == 403) { + throw new BackWPup_Destination_SugarSync_API_Exception('Http Error: ' . $curlgetinfo['http_code'] . ' ' . substr($response, $curlgetinfo['header_size'])); + } + if ($curlgetinfo['http_code'] == 503) { + throw new BackWPup_Destination_SugarSync_API_Exception('Http Error: ' . $curlgetinfo['http_code'] . ' ' . substr($response, $curlgetinfo['header_size'])); + } + + throw new BackWPup_Destination_SugarSync_API_Exception('Http Error: ' . $curlgetinfo['http_code']); + } + + /** + * @throws BackWPup_Destination_SugarSync_API_Exception + * + * @return string + */ + public function chdir(string $folder, string $root = '') + { + $folder = rtrim($folder, '/'); + if (substr($folder, 0, 1) == '/' || empty($this->folder)) { + if (!empty($root)) { + $this->folder = $root; + } else { + throw new BackWPup_Destination_SugarSync_API_Exception('chdir: root folder must set!'); + } + } + $folders = explode('/', $folder); + + foreach ($folders as $dir) { + if ($dir == '..') { + $contents = $this->doCall($this->folder); + if (!empty($contents->parent)) { + $this->folder = $contents->parent; + } + } elseif (!empty($dir) && $dir != '.') { + $isdir = false; + $contents = $this->getcontents('folder'); + + foreach ($contents->collection as $collection) { + if (strtolower($collection->displayName) == strtolower($dir)) { + $isdir = true; + $this->folder = $collection->ref; + break; + } + } + if (!$isdir) { + throw new BackWPup_Destination_SugarSync_API_Exception('chdir: Folder ' . $folder . ' not exitst'); + } + } + } + + return $this->folder; + } + + public function showdir(string $folderid): string + { + $showfolder = ''; + + while ($folderid) { + $contents = $this->doCall($folderid); + $showfolder = $contents->displayName . '/' . $showfolder; + if (isset($contents->parent)) { + $folderid = $contents->parent; + } else { + break; + } + } + + return $showfolder; + } + + /** + * @throws BackWPup_Destination_SugarSync_API_Exception + */ + public function mkdir(string $folder, string $root = ''): bool + { + $savefolder = $this->folder; + $folder = rtrim($folder, '/'); + if (substr($folder, 0, 1) == '/' || empty($this->folder)) { + if (!empty($root)) { + $this->folder = $root; + } else { + throw new BackWPup_Destination_SugarSync_API_Exception('mkdir: root folder must set!'); + } + } + $folders = explode('/', $folder); + + foreach ($folders as $dir) { + if ($dir == '..') { + $contents = $this->doCall($this->folder); + if (!empty($contents->parent)) { + $this->folder = $contents->parent; + } + } elseif (!empty($dir) && $dir != '.') { + $isdir = false; + $contents = $this->getcontents('folder'); + + foreach ($contents->collection as $collection) { + if (strtolower($collection->displayName) == strtolower($dir)) { + $isdir = true; + $this->folder = $collection->ref; + break; + } + } + if (!$isdir) { + $this->doCall($this->folder, '' . mb_convert_encoding($dir, 'UTF-8', $this->encoding) . '', 'POST'); + $contents = $this->getcontents('folder'); + + foreach ($contents->collection as $collection) { + if (strtolower($collection->displayName) == strtolower($dir)) { + $isdir = true; + $this->folder = $collection->ref; + break; + } + } + } + } + } + $this->folder = $savefolder; + + return true; + } + + /** + * @return string|SimpleXMLElement + */ + public function user() + { + return $this->doCall(self::API_URL . '/user'); + } + + /** + * @return string|SimpleXMLElement + */ + public function get(string $url) + { + return $this->doCall($url, '', 'GET'); + } + + /** + * @return string + */ + public function download(string $url) + { + return $this->doCall($url . '/data'); + } + + /** + * @return string + */ + public function delete(string $url) + { + return $this->doCall($url, '', 'DELETE'); + } + + /** + * @return string|SimpleXMLElement + */ + public function getcontents(string $type = '', int $start = 0, int $max = 500) + { + $parameters = ''; + + if (strtolower($type) == 'folder' || strtolower($type) == 'file') { + $parameters .= 'type=' . strtolower($type); + } + if (!empty($start) && is_integer($start)) { + if (!empty($parameters)) { + $parameters .= '&'; + } + $parameters .= 'start=' . $start; + } + if (!empty($max) && is_integer($max)) { + if (!empty($parameters)) { + $parameters .= '&'; + } + $parameters .= 'max=' . $max; + } + + return $this->doCall($this->folder . '/contents?' . $parameters); + } + + /** + * @return mixed + */ + public function upload(string $file, string $name = '') + { + if (empty($name)) { + $name = basename($file); + } + + $content_type = MimeTypeExtractor::fromFilePath($file); + + $xmlrequest = ''; + $xmlrequest .= ''; + $xmlrequest .= '' . mb_convert_encoding($name, 'UTF-8', $this->encoding) . ''; + $xmlrequest .= '' . $content_type . ''; + $xmlrequest .= ''; + + $this->doCall($this->folder, $xmlrequest, 'POST'); + $getfiles = $this->getcontents('file'); + + foreach ($getfiles->file as $getfile) { + if ($getfile->displayName == $name) { + $this->doCall($getfile->ref . '/data', $file, 'PUT'); + + return $getfile->ref; + } + } + } } /** - * SugarSync Exception class + * SugarSync Exception class. * * @author Daniel Hüsken */ -class BackWPup_Destination_SugarSync_API_Exception extends Exception { - +class BackWPup_Destination_SugarSync_API_Exception extends Exception +{ } diff --git a/inc/class-destinations.php b/inc/class-destinations.php index 7ca22b23..38b8b6be 100644 --- a/inc/class-destinations.php +++ b/inc/class-destinations.php @@ -3,227 +3,159 @@ /** * Base class for adding BackWPup destinations. * - * @package BackWPup * @since 3.0.0 */ -abstract class BackWPup_Destinations { +abstract class BackWPup_Destinations +{ + /** + * @var string + */ + private const CAPABILITY = 'backwpup_backups_download'; + /** + * @var string[] + */ + private const EXTENSIONS = [ + '.tar.gz', + '.tar', + '.zip', + ]; - /** - * @return array - */ - abstract public function option_defaults(); + abstract public function option_defaults(): array; - /** - * @param $jobid int - */ - abstract public function edit_tab( $jobid ); + abstract public function edit_tab(int $jobid): void; - /** - * @param $jobid int - */ - public function edit_auth( $jobid ) { + public function edit_auth(int $jobid): void + { + } - } + abstract public function edit_form_post_save(int $jobid): void; - /** - * @param $jobid int - */ - abstract public function edit_form_post_save( $jobid ); - - /** - * use wp_enqueue_script() here to load js for tab - */ - public function admin_print_scripts() { - - } + /** + * use wp_enqueue_script() here to load js for tab. + */ + public function admin_print_scripts(): void + { + } - public function edit_inline_js() { - - } - - public function edit_ajax() { - - } - - public function wizard_admin_print_styles() { - - } - - public function wizard_admin_print_scripts() { - - } - - public function wizard_inline_js() { - - } - - /** - * @param $job_settings array - */ - public function wizard_page( array $job_settings ) { - - echo '
';
-		print_r( $job_settings );
-		echo '
'; - } - - /** - * @param $job_settings array - * - * @return array - */ - public function wizard_save( array $job_settings ) { - - return $job_settings; - } - - public function admin_print_styles() { - - } - - /** - * @param $jobdest string - * @param $backupfile - */ - public function file_delete( $jobdest, $backupfile ) { - - } - - /** - * @param $jobid int - * @param $file_path - * @param $local_file_path - */ - public function file_download( $jobid, $file_path, $local_file_path = null ) { - - $capability = 'backwpup_backups_download'; - $filename = untrailingslashit( BackWPup::get_plugin_data( 'temp' ) ) . '/' . basename( $local_file_path ?: $file_path ); - $job_id = filter_var( $_GET['jobid'], FILTER_SANITIZE_NUMBER_INT ); - - // Dynamically get downloader class - $class_name = get_class( $this ); - $parts = explode( '_', $class_name ); - $destination = array_pop( $parts ); - - $downloader = new BackWpup_Download_Handler( - new BackWPup_Download_File( - $filename, - function ( \BackWPup_Download_File_Interface $obj ) use ( - $filename, - $file_path, - $job_id, - $destination - ) { - - // Setup Destination service and download file. - $factory = new BackWPup_Destination_Downloader_Factory(); - $downloader = $factory->create( - $destination, - $job_id, - $file_path, - $filename - ); - $downloader->download_by_chunks(); - die(); - }, - $capability - ), - 'backwpup_action_nonce', - $capability, - 'download_backup_file' - ); - - // Download the file. - $downloader->handle(); - } - - /** - * @param $jobdest string - * - * @return array - */ - public function file_get_list( $jobdest ) { - - return array(); - } - - /** - * @param $job_object BackWPup_Job - */ - abstract public function job_run_archive( BackWPup_Job $job_object ); - - /** - * @param $job_object BackWPup_Job - */ - public function job_run_sync( BackWPup_Job $job_object ) { - - } - - /** - * Prepare Restore - * - * Method for preparing the restore process. - * - * @param $job_id int Number of job. - * @param $file_name string Name of backup. - * - * @return string The file path, empty string if file cannot be found. - */ - public function prepare_restore( $job_id, $file_name ) { - - } - - /** - * @param $job_settings array - * - * @return bool - */ - abstract public function can_run( array $job_settings ); - - /** - * Is Backup Archive - * - * Checks if given file is a backup archive. - * - * @param $file - * - * @return bool - */ - public function is_backup_archive( $file ) { - - $extensions = array( - '.tar.gz', - '.tar', - '.zip', - ); - - $file = trim( basename( $file ) ); - $filename = ''; - - foreach ( $extensions as $extension ) { - if ( substr( $file, ( strlen( $extension ) * - 1 ) ) === $extension ) { - $filename = substr( $file, 0, ( strlen( $extension ) * - 1 ) ); - } - } - - if ( ! $filename ) { - return false; - } - - return true; - } + public function edit_inline_js(): void + { + } + + public function edit_ajax(): void + { + } + + public function wizard_admin_print_styles(): void + { + } + + public function wizard_admin_print_scripts(): void + { + } + + public function wizard_inline_js(): void + { + } + + public function wizard_page(array $job_settings): void + { + echo '
';
+        print_r($job_settings);
+        echo '
'; + } + + public function wizard_save(array $job_settings): array + { + return $job_settings; + } + + public function admin_print_styles(): void + { + } + + public function file_delete(string $jobdest, string $backupfile): void + { + } + + public function file_download(int $jobid, string $file_path, ?string $local_file_path = null): void + { + $filename = untrailingslashit(BackWPup::get_plugin_data('temp')) . '/' . basename($local_file_path ?: $file_path); + + // Dynamically get downloader class + $class_name = get_class($this); + $parts = explode('_', $class_name); + $destination = array_pop($parts); + + $downloader = new BackWpup_Download_Handler( + new BackWPup_Download_File( + $filename, + static function (BackWPup_Download_File_Interface $obj) use ( + $filename, + $file_path, + $jobid, + $destination + ): void { + // Setup Destination service and download file. + $factory = new BackWPup_Destination_Downloader_Factory(); + $downloader = $factory->create( + $destination, + $jobid, + $file_path, + $filename + ); + $downloader->download_by_chunks(); + + exit(); + }, + self::CAPABILITY + ), + 'backwpup_action_nonce', + self::CAPABILITY, + 'download_backup_file' + ); + + // Download the file. + $downloader->handle(); + } + + public function file_get_list(string $jobdest): array + { + return []; + } + + abstract public function job_run_archive(BackWPup_Job $job_object): bool; + + public function job_run_sync(BackWPup_Job $job_object): bool + { + return true; + } + + abstract public function can_run(array $job_settings): bool; /** - * Checks if the given archive belongs to the given job. + * Is Backup Archive. * - * @param string $file - * @param int $jobid - * - * @return bool + * Checks if given file is a backup archive. */ - public function is_backup_owned_by_job($file, $jobid) + public function is_backup_archive(string $file): bool { + $file = trim(basename($file)); + $filename = ''; + + foreach (self::EXTENSIONS as $extension) { + if (substr($file, (strlen($extension) * -1)) === $extension) { + $filename = substr($file, 0, (strlen($extension) * -1)); + } + } + + return !(!$filename); + } + /** + * Checks if the given archive belongs to the given job. + */ + public function is_backup_owned_by_job(string $file, int $jobid): bool + { $info = pathinfo($file); $file = basename($file, '.' . $info['extension']); @@ -235,25 +167,17 @@ public function is_backup_owned_by_job($file, $jobid) $data = $this->getDecodedHashAndJobId($file, 9); } - if (!$data || !$this->dataContainsCorrectValues($data, $jobid)) { - return false; - } - - return true; + return $data && $this->dataContainsCorrectValues($data, $jobid); } /** - * @param string $file - * @param int $numberOfCharacters - * * @return array|bool */ - protected function getDecodedHashAndJobId($file, $numberOfCharacters) + protected function getDecodedHashAndJobId(string $file, int $numberOfCharacters) { + $data = []; - $data = array(); - - for ($i = strlen($file) - $numberOfCharacters; $i >= 0; $i--) { + for ($i = strlen($file) - $numberOfCharacters; $i >= 0; --$i) { $data = BackWPup_Option::decode_hash(substr($file, $i, $numberOfCharacters)); if ($data) { break; @@ -263,23 +187,12 @@ protected function getDecodedHashAndJobId($file, $numberOfCharacters) return $data; } - /** - * @param array $data - * @param int $jobid - * - * @return bool - */ - protected function dataContainsCorrectValues($data, $jobid) + protected function dataContainsCorrectValues(array $data, int $jobid): bool { - if ($data[0] !== BackWPup::get_plugin_data('hash')) { return false; } - if ($data[1] !== $jobid) { - return false; - } - - return true; + return $data[1] === $jobid; } } diff --git a/inc/class-directory.php b/inc/class-directory.php index 4bbe626b..b45249ec 100644 --- a/inc/class-directory.php +++ b/inc/class-directory.php @@ -5,17 +5,17 @@ * * @since 3.4.0 */ -class BackWPup_Directory extends DirectoryIterator { - - /** - * Creates the iterator. - * - * Fixes the path before calling the parent constructor. - * - * @param string $path - */ - public function __construct( $path ) { - parent::__construct( BackWPup_Path_Fixer::fix_path( $path ) ); - } - +class BackWPup_Directory extends DirectoryIterator +{ + /** + * Creates the iterator. + * + * Fixes the path before calling the parent constructor. + * + * @param string $path + */ + public function __construct($path) + { + parent::__construct(BackWPup_Path_Fixer::fix_path($path)); + } } diff --git a/inc/class-download-file-interface.php b/inc/class-download-file-interface.php index cf9c921d..b1115f58 100644 --- a/inc/class-download-file-interface.php +++ b/inc/class-download-file-interface.php @@ -1,36 +1,35 @@ filepath = $filepath; - $this->filename = basename( $filepath ); - $this->callback = $callback; - $this->length = file_exists( $filepath ) ? filesize( $filepath ) : 0; - $this->capability = $capability; - } - - /** - * @inheritdoc - */ - public function download() { - - if ( ! current_user_can( $this->capability ) ) { - wp_die( 'Cheating Uh?' ); - } - - $this->perform_download_callback(); - } - - /** - * @inheritdoc - */ - public function clean_ob() { - - $level = ob_get_level(); - if ( $level ) { - for ( $i = 0; $i < $level; $i ++ ) { - ob_end_clean(); - } - } - - return $this; - } - - /** - * @inheritdoc - */ - public function filepath() { - - return $this->filepath; - } - - /** - * @inheritdoc - */ - public function headers() { - - $mime = MimeTypeExtractor::fromFilePath( $this->filepath ); - - $level = ob_get_level(); - if ( $level ) { - for ( $i = 0; $i < $level; $i ++ ) { - ob_end_clean(); - } - } - - // phpcs:ignore - @set_time_limit( 300 ); - nocache_headers(); - - // Set headers. - header( 'Content-Description: File Transfer' ); - header( "Content-Type: {$mime}" ); - header( "Content-Disposition: attachment; filename={$this->filename}" ); - header( 'Content-Transfer-Encoding: ' . self::$encoding ); - header( "Content-Length: {$this->length}" ); - - return $this; - } - - /** - * Perform the Download - * - * Note: The callback must call `die` it self. - * - * @return void - */ - private function perform_download_callback() { - - call_user_func( $this->callback, $this ); - } +final class BackWPup_Download_File implements BackWPup_Download_File_Interface +{ + /** + * The file path. + * + * @var string The path of the file to download + */ + private $filepath; + + /** + * File Name. + * + * @var string The file name + */ + private $filename; + + /** + * @var string + * + * @string The encoding type + */ + private static $encoding = 'binary'; + + /** + * File content length. + * + * @var int The length of the file + */ + private $length; + + /** + * Callback. + * + * @var callable The callback to call that will perform the download action + */ + private $callback; + + /** + * Capability. + * + * @var @string The capability needed to download the file + */ + private $capability; + + /** + * BackWPup_Download_File constructor. + * + * @todo move the file stuffs into a specific class to manage only files. Blocked by class-file.php + * + * @param string $filepath the path of the file to download + * @param callable $callback the callback to call that will perform the download action + * @param string $capability the capability needed to download the file + * + * @throws \InvalidArgumentException in case the callback is not a valid callback + */ + public function __construct($filepath, $callback, $capability) + { + if (!is_callable($callback)) { + throw new \InvalidArgumentException( + sprintf('Invalid callback passed to %s. Callback parameter must be callable.', self::class) + ); + } + + $this->filepath = $filepath; + $this->filename = basename($filepath); + $this->callback = $callback; + $this->length = file_exists($filepath) ? filesize($filepath) : 0; + $this->capability = $capability; + } + + /** + * {@inheritdoc} + */ + public function download() + { + if (!current_user_can($this->capability)) { + wp_die('Cheating Uh?'); + } + + $this->perform_download_callback(); + } + + /** + * {@inheritdoc} + */ + public function clean_ob() + { + $level = ob_get_level(); + if ($level) { + for ($i = 0; $i < $level; ++$i) { + ob_end_clean(); + } + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function filepath() + { + return $this->filepath; + } + + /** + * {@inheritdoc} + */ + public function headers() + { + $mime = MimeTypeExtractor::fromFilePath($this->filepath); + + $level = ob_get_level(); + if ($level) { + for ($i = 0; $i < $level; ++$i) { + ob_end_clean(); + } + } + + // phpcs:ignore + @set_time_limit(300); + nocache_headers(); + + // Set headers. + header('Content-Description: File Transfer'); + header("Content-Type: {$mime}"); + header("Content-Disposition: attachment; filename={$this->filename}"); + header('Content-Transfer-Encoding: ' . self::$encoding); + header("Content-Length: {$this->length}"); + + return $this; + } + + /** + * Perform the Download. + * + * Note: The callback must call `die` it self. + */ + private function perform_download_callback() + { + call_user_func($this->callback, $this); + } } diff --git a/inc/class-download-handler.php b/inc/class-download-handler.php index 5b8a9d9e..ad20db30 100644 --- a/inc/class-download-handler.php +++ b/inc/class-download-handler.php @@ -1,86 +1,84 @@ downloader = $downloader; + $this->nonce_action = $nonce_action; + $this->capability = $capability; + $this->action = $action; + } - /** - * DownloadLogHandler constructor - * - * @param \BackWPup_Download_File_Interface $downloader The instance used to download the file. - * @param string $nonce_action The nonce to verify. - * @param string $capability The capability needed to download the file. - * @param string $action The action to perform. - */ - public function __construct( \BackWPup_Download_File_Interface $downloader, $nonce_action, $capability, $action ) { + /** + * Handle the Request. + */ + public function handle() + { + if (!$this->verify_request()) { + return; + } - $this->downloader = $downloader; - $this->nonce_action = $nonce_action; - $this->capability = $capability; - $this->action = $action; - } + $this->downloader->download(); + } - /** - * Handle the Request - * - * @return void - */ - public function handle() { + /** + * Verify Request. + * + * @return bool True if verified, false otherwise. Die if nonce is not valid + */ + private function verify_request() + { + // phpcs:ignore + if (!isset($_GET['action']) || $this->action !== filter_var($_GET['action'], FILTER_SANITIZE_STRING)) { + return false; + } - if ( ! $this->verify_request() ) { - return; - } + check_admin_referer($this->nonce_action, $this->nonce_action); - $this->downloader->download(); - } + if (!current_user_can($this->capability)) { + wp_die('Cheating Uh?'); + } - /** - * Verify Request - * - * @return bool True if verified, false otherwise. Die if nonce is not valid - */ - private function verify_request() { - - // phpcs:ignore - if ( ! isset( $_GET['action'] ) || $this->action !== filter_var( $_GET['action'], FILTER_SANITIZE_STRING ) ) { - return false; - } - - check_admin_referer( $this->nonce_action, $this->nonce_action ); - - if ( ! current_user_can( $this->capability ) ) { - wp_die( 'Cheating Uh?' ); - } - - return true; - } + return true; + } } diff --git a/inc/class-easycron.php b/inc/class-easycron.php index d101c0ba..52095781 100644 --- a/inc/class-easycron.php +++ b/inc/class-easycron.php @@ -1,221 +1,217 @@ NULL, - 'email_me' => 0, - 'log_output_length' => 0, - 'testfirst' => 0 - ); - - if ( empty( $backwpup_jobid ) ) { - $params[ 'id' ] = get_site_option( 'backwpup_cfg_easycronjobid' ); - $params[ 'cron_job_name' ] = sprintf( 'WordPress on %s', home_url() ); - $params[ 'cron_expression' ] = '*/5 * * * *'; - $url = BackWPup_Job::get_jobrun_url( 'runext', 0 ); - $url = remove_query_arg( '_nonce', $url[ 'url' ] ); - $url = remove_query_arg( 'doing_wp_cron', $url ); - $url = remove_query_arg( 'backwpup_run', $url ); - $url = add_query_arg( array( 'doing_wp_cron' => '' ), $url ); - $cookies = get_site_transient( 'backwpup_cookies' ); - $params[ 'url' ] = $url; - if ( ! empty( $cookies ) ) { - $params[ 'cookies' ] = http_build_query( $cookies ); - } - } else { - $params[ 'id' ] = BackWPup_Option::get( $backwpup_jobid, 'easycronjobid' ); - if ( empty( $params[ 'id' ] ) ) { - $params[ 'id' ] = NULL; - } - $params[ 'cron_job_name' ] = sprintf( 'BackWPup %s on %s', BackWPup_Option::get( $backwpup_jobid, 'name' ), home_url() ); - $params[ 'cron_expression' ] = BackWPup_Option::get( $backwpup_jobid, 'cron' ); - $url = BackWPup_Job::get_jobrun_url( 'runext', $backwpup_jobid ); - $cookies = get_site_transient( 'backwpup_cookies' ); - $params[ 'url' ] = $url[ 'url' ]; - if ( ! empty( $cookies ) ) { - $params[ 'cookies' ] = http_build_query( $cookies ); - } - } - - if ( empty( $params[ 'id' ] ) ) { - $message = self::query_api( 'add' ,$params ); - } else { - $message = self::query_api( 'edit', $params ); - } - - delete_site_transient( 'backwpup_easycron_' . $params[ 'id' ] ); - - if ( $message[ 'status' ] == 'success' && !empty( $message[ 'cron_job_id' ] ) ) { - if ( empty( $backwpup_jobid ) ) { - update_site_option( 'backwpup_cfg_easycronjobid', $message[ 'cron_job_id' ] ); - } else { - BackWPup_Option::update( $backwpup_jobid, 'easycronjobid', $message[ 'cron_job_id' ] ); - } - return TRUE; - } else { - if ( $message[ 'error' ][ 'code' ] == 25 ) { - if ( empty( $backwpup_jobid ) ) { - delete_site_option( 'backwpup_cfg_easycronjobid' ); - } else { - BackWPup_Option::delete( $backwpup_jobid, 'easycronjobid' ); - } - } - } - - return FALSE; - } - - - public static function delete( $backwpup_jobid ) { - - if ( empty( $backwpup_jobid ) ) { - $id = get_site_option( 'backwpup_cfg_easycronjobid' ); - } else { - $id = BackWPup_Option::get( $backwpup_jobid, 'easycronjobid' ); - } - - if ( empty( $id ) ) { - return TRUE; - } - - - $message = self::query_api( 'delete', array( 'id' => $id ) ); - - delete_site_transient( 'backwpup_easycron_' . $id ); - - if ( $message[ 'status' ] == 'success' && !empty( $message[ 'cron_job_id' ] ) ) { - if ( empty( $backwpup_jobid ) ) { - delete_site_option( 'backwpup_cfg_easycronjobid' ); - } else { - BackWPup_Option::delete( $backwpup_jobid, 'easycronjobid' ); - } - return TRUE; - } else { - if ( $message[ 'error' ][ 'code' ] == 25 ) { - if ( empty( $backwpup_jobid ) ) { - delete_site_option( 'backwpup_cfg_easycronjobid' ); - } else { - BackWPup_Option::delete( $backwpup_jobid, 'easycronjobid' ); - } - } - } - - return FALSE; - } - - - public static function status( $backwpup_jobid ) { - - if ( empty( $backwpup_jobid ) ) { - $id = get_site_option( 'backwpup_cfg_easycronjobid' ); - } else { - $id = BackWPup_Option::get( $backwpup_jobid, 'easycronjobid' ); - } - - if ( empty( $id ) ) { - return array(); - } - - $cron_job = get_site_transient( 'backwpup_easycron_' . $id ); - if ( ! empty( $cron_job ) ) { - return $cron_job; - } - - $message = self::query_api( 'detail', array( 'id' => $id ) ); - - if ( $message[ 'status' ] == 'success' && ! empty( $message[ 'cron_job' ] ) ) { - set_site_transient( 'backwpup_easycron_' . $id, $message[ 'cron_job' ], HOUR_IN_SECONDS - 30 ); - return $message[ 'cron_job' ]; - } else { - if ( $message[ 'error' ][ 'code' ] == 25 ) { - if ( empty( $backwpup_jobid ) ) { - delete_site_option( 'backwpup_cfg_easycronjobid' ); - } else { - BackWPup_Option::delete( $backwpup_jobid, 'easycronjobid' ); - } - } - } - - return array(); - } - - - private static function query_api( $endpoint, array $params ) { - - $message = array( 'status' => 'error', 'error' => array( 'code' => 0, 'message' => 'Please setup EasyCron auth api key in settings' ) ); - - $params[ 'token' ] = get_site_option( 'backwpup_cfg_easycronapikey' ); - if ( empty( $params[ 'token' ] ) ) { - return $message; - } - - $result = wp_remote_get( 'https://www.easycron.com/rest/' . $endpoint .'?' . http_build_query( $params ) ); - - if ( wp_remote_retrieve_response_code( $result ) != 200 ) { - $message[ 'error' ][ 'code' ] = wp_remote_retrieve_response_code( $result ); - $message[ 'error' ][ 'message' ] = wp_remote_retrieve_response_message( $result ); - } else { - $json = wp_remote_retrieve_body( $result ); - $message = json_decode( $json, TRUE ); - } - - if ( $message[ 'status' ] != 'success' ) { - BackWPup_Admin::message( sprintf( __( 'EasyCron.com API returns (%s): %s', 'backwpup' ), esc_attr( $message[ 'error' ][ 'code' ] ), esc_attr( $message[ 'error' ][ 'message' ] ) ), TRUE ); - } - - return $message; - } - - - public function api_key_form() { - ?> -

-

EasyCron.com API key to use this service.', 'backwpup' ); ?>

+class BackWPup_EasyCron +{ + public function __construct() + { + add_action('backwpup_page_settings_tab_apikey', [$this, 'api_key_form'], 11); + add_action('backwpup_page_settings_save', [$this, 'api_key_save_form'], 11); + } + + public static function update($backwpup_jobid) + { + $params = [ + 'id' => null, + 'email_me' => 0, + 'log_output_length' => 0, + 'testfirst' => 0, + ]; + + if (empty($backwpup_jobid)) { + $params['id'] = get_site_option('backwpup_cfg_easycronjobid'); + $params['cron_job_name'] = sprintf('WordPress on %s', home_url()); + $params['cron_expression'] = '*/5 * * * *'; + $url = BackWPup_Job::get_jobrun_url('runext', 0); + $url = remove_query_arg('_nonce', $url['url']); + $url = remove_query_arg('doing_wp_cron', $url); + $url = remove_query_arg('backwpup_run', $url); + $url = add_query_arg(['doing_wp_cron' => ''], $url); + $cookies = get_site_transient('backwpup_cookies'); + $params['url'] = $url; + if (!empty($cookies)) { + $params['cookies'] = http_build_query($cookies); + } + } else { + $params['id'] = BackWPup_Option::get($backwpup_jobid, 'easycronjobid'); + if (empty($params['id'])) { + $params['id'] = null; + } + $params['cron_job_name'] = sprintf('BackWPup %s on %s', BackWPup_Option::get($backwpup_jobid, 'name'), home_url()); + $params['cron_expression'] = BackWPup_Option::get($backwpup_jobid, 'cron'); + $url = BackWPup_Job::get_jobrun_url('runext', $backwpup_jobid); + $cookies = get_site_transient('backwpup_cookies'); + $params['url'] = $url['url']; + if (!empty($cookies)) { + $params['cookies'] = http_build_query($cookies); + } + } + + if (empty($params['id'])) { + $message = self::query_api('add', $params); + } else { + $message = self::query_api('edit', $params); + } + + delete_site_transient('backwpup_easycron_' . $params['id']); + + if ($message['status'] == 'success' && !empty($message['cron_job_id'])) { + if (empty($backwpup_jobid)) { + update_site_option('backwpup_cfg_easycronjobid', $message['cron_job_id']); + } else { + BackWPup_Option::update($backwpup_jobid, 'easycronjobid', $message['cron_job_id']); + } + + return true; + } + if ($message['error']['code'] == 25) { + if (empty($backwpup_jobid)) { + delete_site_option('backwpup_cfg_easycronjobid'); + } else { + BackWPup_Option::delete($backwpup_jobid, 'easycronjobid'); + } + } + + return false; + } + + public static function delete($backwpup_jobid) + { + if (empty($backwpup_jobid)) { + $id = get_site_option('backwpup_cfg_easycronjobid'); + } else { + $id = BackWPup_Option::get($backwpup_jobid, 'easycronjobid'); + } + + if (empty($id)) { + return true; + } + + $message = self::query_api('delete', ['id' => $id]); + + delete_site_transient('backwpup_easycron_' . $id); + + if ($message['status'] == 'success' && !empty($message['cron_job_id'])) { + if (empty($backwpup_jobid)) { + delete_site_option('backwpup_cfg_easycronjobid'); + } else { + BackWPup_Option::delete($backwpup_jobid, 'easycronjobid'); + } + + return true; + } + if ($message['error']['code'] == 25) { + if (empty($backwpup_jobid)) { + delete_site_option('backwpup_cfg_easycronjobid'); + } else { + BackWPup_Option::delete($backwpup_jobid, 'easycronjobid'); + } + } + + return false; + } + + public static function status($backwpup_jobid) + { + if (empty($backwpup_jobid)) { + $id = get_site_option('backwpup_cfg_easycronjobid'); + } else { + $id = BackWPup_Option::get($backwpup_jobid, 'easycronjobid'); + } + + if (empty($id)) { + return []; + } + + $cron_job = get_site_transient('backwpup_easycron_' . $id); + if (!empty($cron_job)) { + return $cron_job; + } + + $message = self::query_api('detail', ['id' => $id]); + + if ($message['status'] == 'success' && !empty($message['cron_job'])) { + set_site_transient('backwpup_easycron_' . $id, $message['cron_job'], HOUR_IN_SECONDS - 30); + + return $message['cron_job']; + } + if ($message['error']['code'] == 25) { + if (empty($backwpup_jobid)) { + delete_site_option('backwpup_cfg_easycronjobid'); + } else { + BackWPup_Option::delete($backwpup_jobid, 'easycronjobid'); + } + } + + return []; + } + + private static function query_api($endpoint, array $params) + { + $message = ['status' => 'error', 'error' => ['code' => 0, 'message' => 'Please setup EasyCron auth api key in settings']]; + + $params['token'] = get_site_option('backwpup_cfg_easycronapikey'); + if (empty($params['token'])) { + return $message; + } + + $result = wp_remote_get('https://www.easycron.com/rest/' . $endpoint . '?' . http_build_query($params)); + + if (wp_remote_retrieve_response_code($result) != 200) { + $message['error']['code'] = wp_remote_retrieve_response_code($result); + $message['error']['message'] = wp_remote_retrieve_response_message($result); + } else { + $json = wp_remote_retrieve_body($result); + $message = json_decode($json, true); + } + + if ($message['status'] != 'success') { + BackWPup_Admin::message(sprintf(__('EasyCron.com API returns (%s): %s', 'backwpup'), esc_attr($message['error']['code']), esc_attr($message['error']['message'])), true); + } + + return $message; + } + + public function api_key_form() + { + ?> +

+

EasyCron.com API key to use this service.', 'backwpup'); ?>

- + - +
- /> - + /> +
key = md5((string) $enc_key); + $this->key_type = (string) $key_type; + } - /** - * @param string $enc_key - * @param string $key_type - */ - public function __construct( $enc_key, $key_type ) { - $this->key = md5( (string) $enc_key ); - $this->key_type = (string) $key_type; - } + /** + * @return bool + */ + public static function supported() + { + /** @TODO: Should we inform the user about the security risk? and how? */ + return true; + } - /** - * - * Encrypt a string (Passwords) - * - * @param string $string value to encrypt - * - * @return string encrypted string - */ - public function encrypt( $string ) { + /** + * Encrypt a string (Passwords). + * + * @param string $string value to encrypt + * + * @return string encrypted string + */ + public function encrypt($string) + { + $result = ''; - $result = ''; - for ( $i = 0; $i < strlen( $string ); $i ++ ) { - $char = substr( $string, $i, 1 ); - $key_char = substr( $this->key, ( $i % strlen( $this->key ) ) - 1, 1 ); - $char = chr( ord( $char ) + ord( $key_char ) ); - $result .= $char; - } + for ($i = 0; $i < strlen($string); ++$i) { + $char = substr($string, $i, 1); + $key_char = substr($this->key, ($i % strlen($this->key)) - 1, 1); + $char = chr(ord($char) + ord($key_char)); + $result .= $char; + } - return BackWPup_Encryption::PREFIX . self::PREFIX . $this->key_type . base64_encode( $result ); + return BackWPup_Encryption::PREFIX . self::PREFIX . $this->key_type . base64_encode($result); + } - } + /** + * Decrypt a string (Passwords). + * + * @param string $string value to decrypt + * + * @return string decrypted string + */ + public function decrypt($string) + { + if ( + !is_string($string) + || !$string + || strpos($string, BackWPup_Encryption::PREFIX . self::PREFIX . $this->key_type) !== 0 + ) { + return ''; + } - /** - * - * Decrypt a string (Passwords) - * - * @param string $string value to decrypt - * - * @return string decrypted string - */ - public function decrypt( $string ) { + $no_prefix = substr($string, strlen(BackWPup_Encryption::PREFIX . self::PREFIX . $this->key_type)); - if ( - ! is_string( $string ) - || ! $string - || strpos( $string, BackWPup_Encryption::PREFIX . self::PREFIX . $this->key_type ) !== 0 - ) { - return ''; - } + $encrypted = base64_decode($no_prefix, true); + if ($encrypted === false) { + return ''; + } - $no_prefix = substr( $string, strlen( BackWPup_Encryption::PREFIX . self::PREFIX . $this->key_type ) ); + $result = ''; - $encrypted = base64_decode( $no_prefix, true ); - if ( $encrypted === false ) { - return ''; - } + for ($i = 0; $i < strlen($encrypted); ++$i) { + $char = substr($encrypted, $i, 1); + $key_char = substr($this->key, ($i % strlen($this->key)) - 1, 1); + $char = chr(ord($char) - ord($key_char)); + $result .= $char; + } - $result = ''; - for ( $i = 0; $i < strlen( $encrypted ); $i ++ ) { - $char = substr( $encrypted, $i, 1 ); - $key_char = substr( $this->key, ( $i % strlen( $this->key ) ) - 1, 1 ); - $char = chr( ord( $char ) - ord( $key_char ) ); - $result .= $char; - } - - return $result; - } + return $result; + } } diff --git a/inc/class-encryption-mcrypt.php b/inc/class-encryption-mcrypt.php index 5a5d5de3..70dcd2db 100644 --- a/inc/class-encryption-mcrypt.php +++ b/inc/class-encryption-mcrypt.php @@ -13,109 +13,118 @@ * interface in a file named `class-...php`. When we get rid of PHP 5.2, we setup a better autoloader and we get rid of * WP coding standard, we finally could consider to introduce an interface. */ -class BackWPup_Encryption_Mcrypt { - - const PREFIX = 'RIJNDAEL$'; - - /** - * @var bool - */ - private $deprecated = false; - - /** - * @return bool - */ - public static function supported() { - return function_exists( 'mcrypt_encrypt' ); - } - - /** - * @param string $enc_key - * @param string $key_type - */ - public function __construct( $enc_key, $key_type ) { - $this->key = md5( (string) $enc_key ); - $this->key_type = (string) $key_type; - $this->deprecated = (bool) version_compare( PHP_VERSION, '7.1', '>=' ); - - /** @TODO: Should we do something here to inform user about deprecation? */ - } - - /** - * - * Encrypt a string (Passwords) - * - * @param string $string value to encrypt - * - * @return string encrypted string - */ - public function encrypt( $string ) { - - if ( ! is_string( $string ) || ! $string ) { - return ''; - } - - $encrypted = $this->deprecated - ? @mcrypt_encrypt( MCRYPT_RIJNDAEL_256, $this->key, $string, MCRYPT_MODE_CBC, md5( $this->key ) ) - : mcrypt_encrypt( MCRYPT_RIJNDAEL_256, $this->key, $string, MCRYPT_MODE_CBC, md5( $this->key ) ); - - return BackWPup_Encryption::PREFIX . self::PREFIX . $this->key_type . base64_encode( $encrypted ); - - } - - /** - * - * Decrypt a string (Passwords) - * - * @param string $string value to decrypt - * - * @return string decrypted string - */ - public function decrypt( $string ) { - - if ( - ! is_string( $string ) - || ! $string - || strpos( $string, BackWPup_Encryption::PREFIX . self::PREFIX . $this->key_type ) !== 0 - ) { - return ''; - } - - $no_prefix = substr( $string, strlen( BackWPup_Encryption::PREFIX . self::PREFIX . $this->key_type ) ); - - $encrypted = base64_decode( $no_prefix, true ); - if ( $encrypted === false ) { - return ''; - } - - if ( defined( 'BACKWPUP_MCRYPT_KEY_MODE' ) && BACKWPUP_MCRYPT_KEY_MODE === 1 ) { - return $this->decrypt_deprecated( $encrypted ); - } - - $decrypted = $this->deprecated - ? @mcrypt_decrypt( MCRYPT_RIJNDAEL_256, $this->key, $encrypted, MCRYPT_MODE_CBC, md5( $this->key ) ) - : mcrypt_decrypt( MCRYPT_RIJNDAEL_256, $this->key, $encrypted, MCRYPT_MODE_CBC, md5( $this->key ) ); - - $skip_deprecated = defined( 'BACKWPUP_MCRYPT_KEY_MODE' ) && BACKWPUP_MCRYPT_KEY_MODE === 2; - - if ( ! $skip_deprecated && ! @wp_check_invalid_utf8( $decrypted ) ) { - $decrypted = $this->decrypt_deprecated( $encrypted ); - } - - return $decrypted; - } - - /** - * @param string $encrypted - * - * @return string - */ - private function decrypt_deprecated( $encrypted ) { - - $key = md5( $this->key ); - - return $this->deprecated - ? @mcrypt_decrypt( MCRYPT_RIJNDAEL_256, $key, $encrypted, MCRYPT_MODE_CBC, md5( $key ) ) - : mcrypt_decrypt( MCRYPT_RIJNDAEL_256, $key, $encrypted, MCRYPT_MODE_CBC, md5( $key ) ); - } +class BackWPup_Encryption_Mcrypt +{ + public const PREFIX = 'RIJNDAEL$'; + + /** + * @var string + */ + private $key; + + /** + * @var string + */ + private $key_type; + + /** + * @var bool + */ + private $deprecated = false; + + /** + * @param string $enc_key + * @param string $key_type + */ + public function __construct($enc_key, $key_type) + { + $this->key = md5((string) $enc_key); + $this->key_type = (string) $key_type; + $this->deprecated = (bool) version_compare(PHP_VERSION, '7.1', '>='); + + /** @TODO: Should we do something here to inform user about deprecation? */ + } + + /** + * @return bool + */ + public static function supported() + { + return function_exists('mcrypt_encrypt'); + } + + /** + * Encrypt a string (Passwords). + * + * @param string $string value to encrypt + * + * @return string encrypted string + */ + public function encrypt($string) + { + if (!is_string($string) || !$string) { + return ''; + } + + $encrypted = $this->deprecated + ? @mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $this->key, $string, MCRYPT_MODE_CBC, md5($this->key)) + : mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $this->key, $string, MCRYPT_MODE_CBC, md5($this->key)); + + return BackWPup_Encryption::PREFIX . self::PREFIX . $this->key_type . base64_encode($encrypted); + } + + /** + * Decrypt a string (Passwords). + * + * @param string $string value to decrypt + * + * @return string decrypted string + */ + public function decrypt($string) + { + if ( + !is_string($string) + || !$string + || strpos($string, BackWPup_Encryption::PREFIX . self::PREFIX . $this->key_type) !== 0 + ) { + return ''; + } + + $no_prefix = substr($string, strlen(BackWPup_Encryption::PREFIX . self::PREFIX . $this->key_type)); + + $encrypted = base64_decode($no_prefix, true); + if ($encrypted === false) { + return ''; + } + + if (defined('BACKWPUP_MCRYPT_KEY_MODE') && BACKWPUP_MCRYPT_KEY_MODE === 1) { + return $this->decrypt_deprecated($encrypted); + } + + $decrypted = $this->deprecated + ? @mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $this->key, $encrypted, MCRYPT_MODE_CBC, md5($this->key)) + : mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $this->key, $encrypted, MCRYPT_MODE_CBC, md5($this->key)); + + $skip_deprecated = defined('BACKWPUP_MCRYPT_KEY_MODE') && BACKWPUP_MCRYPT_KEY_MODE === 2; + + if (!$skip_deprecated && !@wp_check_invalid_utf8($decrypted)) { + $decrypted = $this->decrypt_deprecated($encrypted); + } + + return $decrypted; + } + + /** + * @param string $encrypted + * + * @return string + */ + private function decrypt_deprecated($encrypted) + { + $key = md5($this->key); + + return $this->deprecated + ? @mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $key, $encrypted, MCRYPT_MODE_CBC, md5($key)) + : mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $key, $encrypted, MCRYPT_MODE_CBC, md5($key)); + } } diff --git a/inc/class-encryption-openssl.php b/inc/class-encryption-openssl.php index 6ee1dea9..eca729da 100644 --- a/inc/class-encryption-openssl.php +++ b/inc/class-encryption-openssl.php @@ -10,54 +10,54 @@ * interface in a file named `class-...php`. When we get rid of PHP 5.2, we setup a better autoloader and we get rid of * WP coding standard, we finally could consider to introduce an interface. */ -class BackWPup_Encryption_OpenSSL { - - /** - * Prefix - * - * @var string - */ - const PREFIX = 'OSSL$'; - - /** - * Cipher Method - * - * @var string - */ - private static $cipher_method; - - private $key; - - private $key_type; - - /** - * Supported - * - * @return bool - */ - public static function supported() { - - return - function_exists( 'openssl_get_cipher_methods' ) - && self::cipher_method(); - } +class BackWPup_Encryption_OpenSSL +{ + /** + * Prefix. + * + * @var string + */ + public const PREFIX = 'OSSL$'; + + /** + * Cipher Method. + * + * @var string + */ + private static $cipher_method; + + private $key; + + private $key_type; /** - * BackWPup_Encryption_OpenSSL constructor + * BackWPup_Encryption_OpenSSL constructor. * * @param string $enc_key * @param string $key_type */ public function __construct($enc_key, $key_type) { - $this->key = md5((string)$enc_key); - $this->key_type = (string)$key_type; + $this->key = md5((string) $enc_key); + $this->key_type = (string) $key_type; } /** - * Encrypt a string using Open SSL lib with AES-256-CTR cypher + * Supported. * - * @param string $string value to encrypt. + * @return bool + */ + public static function supported() + { + return + function_exists('openssl_get_cipher_methods') + && self::cipher_method(); + } + + /** + * Encrypt a string using Open SSL lib with AES-256-CTR cypher. + * + * @param string $string value to encrypt * * @return string encrypted string */ @@ -81,9 +81,9 @@ public function encrypt($string) } /** - * Decrypt a string using Open SSL lib with AES-256-CTR cypher + * Decrypt a string using Open SSL lib with AES-256-CTR cypher. * - * @param string $string value to decrypt. + * @param string $string value to decrypt * * @return string decrypted string */ @@ -97,8 +97,10 @@ public function decrypt($string) return ''; } - $no_prefix = substr($string, - strlen(BackWPup_Encryption::PREFIX . self::PREFIX . $this->key_type)); + $no_prefix = substr( + $string, + strlen(BackWPup_Encryption::PREFIX . self::PREFIX . $this->key_type) + ); $encrypted = base64_decode($no_prefix, true); if ($encrypted === false) { @@ -110,46 +112,45 @@ public function decrypt($string) $to_decrypt = substr($encrypted, $nonce_size); $openssl_raw_data = defined('OPENSSL_RAW_DATA') ? OPENSSL_RAW_DATA : true; - $decrypted = openssl_decrypt( + return openssl_decrypt( $to_decrypt, self::cipher_method(), $this->key, $openssl_raw_data, $nonce ); - - return $decrypted; } - /** - * Cipher Method - * - * @return string - */ - private static function cipher_method() { + /** + * Cipher Method. + * + * @return string + */ + private static function cipher_method() + { + if (is_string(self::$cipher_method)) { + return self::$cipher_method; + } - if ( is_string( self::$cipher_method ) ) { - return self::$cipher_method; - } + $all_methods = openssl_get_cipher_methods(); + if (!$all_methods) { + self::$cipher_method = ''; - $all_methods = openssl_get_cipher_methods(); - if ( ! $all_methods ) { - self::$cipher_method = ''; + return ''; + } - return ''; - } + $preferred = ['AES-256-CTR', 'AES-128-CTR', 'AES-192-CTR']; - $preferred = array( 'AES-256-CTR', 'AES-128-CTR', 'AES-192-CTR' ); - foreach ( $preferred as $method ) { - if ( in_array( $method, $all_methods, true ) ) { - self::$cipher_method = $method; + foreach ($preferred as $method) { + if (in_array($method, $all_methods, true)) { + self::$cipher_method = $method; - return $method; - } - } + return $method; + } + } - self::$cipher_method = reset( $all_methods ); + self::$cipher_method = reset($all_methods); - return self::$cipher_method; - } + return self::$cipher_method; + } } diff --git a/inc/class-encryption.php b/inc/class-encryption.php index c843f1ad..9e574769 100644 --- a/inc/class-encryption.php +++ b/inc/class-encryption.php @@ -9,203 +9,191 @@ * When we get rid of PHP 5.2, we setup a better autoloader and we get rid of WP coding standard, we finally should * refactor to proper code. */ -class BackWPup_Encryption { - - const PREFIX = '$BackWPup$'; - const KEY_TYPE_CUSTOM = '$0'; - - private static $classes = array( - BackWPup_Encryption_OpenSSL::PREFIX => 'BackWPup_Encryption_OpenSSL', - BackWPup_Encryption_Mcrypt::PREFIX => 'BackWPup_Encryption_Mcrypt', - BackWPup_Encryption_Fallback::PREFIX => 'BackWPup_Encryption_Fallback', - ); - - /** - * - * Encrypt a string using the best algorithm available. - * - * In case the given string is encrypted with a weaker algorithm, it will first be decrypted then the plain text - * obtained is encrypted with the better algorithm available and returned. - * - * @param string $string value to encrypt - * - * @return string encrypted string - */ - public static function encrypt( $string ) { - - if ( ! is_string( $string ) || ! $string ) { - return ''; - } - - try { - $cypher_class = self::cypher_class_for_string( $string ); - } - catch ( Exception $e ) { - - /** @TODO what to do here? The string is encrypted, but cypher used to encrypt isn't supported in current system */ - - return $string; - } - - try { - list( $key, $key_type ) = self::get_encrypt_info( $cypher_class, $string ); - } - catch ( Exception $e ) { - - /** @TODO what to do here? The string is encrypted, a custom key was used to encrypt, but it is not available anymore */ - - return $string; - } - - $best_cipher_class = self::best_cypher(); - - // The given string is not encrypted, let's encrypt it and return - if ( ! $cypher_class ) { - - /** @var BackWPup_Encryption_OpenSSL|BackWPup_Encryption_Mcrypt|BackWPup_Encryption_Fallback $best_cypher */ - $best_cypher = new $best_cipher_class( $key, $key_type ); - - return $best_cypher->encrypt( $string ); - } - - $encryption_count = substr_count( $string, self::PREFIX ); - - // The given string is encrypted once using best cypher, let's just return it - if ( $encryption_count === 1 && $cypher_class === $best_cipher_class ) { - return $string; - } - - /** @var BackWPup_Encryption_OpenSSL|BackWPup_Encryption_Mcrypt|BackWPup_Encryption_Fallback $cypher */ - $cypher = new $cypher_class( $key, $key_type ); - - $string = $cypher->decrypt( $string ); - - return self::encrypt( $string ); - } - - /** - * - * Decrypt a string (Passwords) - * - * @param string $string value to decrypt - * - * @return string decrypted string - */ - public static function decrypt( $string ) { - - if ( ! is_string( $string ) || ! $string ) { - return ''; - } - - try { - $cypher_class = self::cypher_class_for_string( $string ); - } - catch ( Exception $e ) { - - /** @TODO what to do here? The cypher used to encrypt is not supported in current system */ - - return ''; - } - - if ( ! $cypher_class ) { - - /** @TODO what to do here? The string seems not encrypted or maybe is corrupted */ - - return $string; - } - - try { - list( $key, $key_type ) = self::get_encrypt_info( $cypher_class, $string ); - } - catch ( Exception $e ) { - - /** @TODO what to do here? A custom key was used to encrypt but it is not available anymore */ - return ''; - } - - /** @var BackWPup_Encryption_OpenSSL|BackWPup_Encryption_Mcrypt|BackWPup_Encryption_Fallback $cypher */ - $cypher = new $cypher_class( $key, $key_type ); - - return trim( stripslashes( $cypher->decrypt( $string ) ), "\0" ); - } - - /** - * @param string $string - * - * @return string - * @throws Exception - */ - private static function cypher_class_for_string( $string ) { - - foreach ( self::$classes as $prefix => $class ) { - - $enc_prefix = self::PREFIX . $prefix; - - if ( strpos( $string, $enc_prefix ) !== 0 ) { - continue; - } - - if ( ! call_user_func( array( $class, 'supported' ) ) ) { - throw new Exception( - "Give string was encrypted using {$class} but it is not currently supported in this system." - ); - } - - return $class; - } - - return ''; - } - - /** - * @return string - */ - private static function best_cypher() { - - foreach ( self::$classes as $prefix => $class ) { - - if ( ! call_user_func( array( $class, 'supported' ) ) ) { - continue; - } - - return $class; - } - - // This should never happen because BackWPup_Encryption_Fallback::supported() always returns true - - return ''; - } - - /** - * @param string|null $class - * @param string $string - * - * @return array - * @throws Exception - */ - private static function get_encrypt_info( $class = null, $string = '' ) { - - $default_key = DB_NAME . DB_USER . DB_PASSWORD; - - if ( ! is_string( $class ) || ! $class ) { - return defined( 'BACKWPUP_ENC_KEY' ) - ? array( BACKWPUP_ENC_KEY, self::KEY_TYPE_CUSTOM ) - : array( $default_key, '' ); - } - - $enc_prefix = self::PREFIX . constant( "{$class}::PREFIX" ); - $has_custom_key = strpos( $string, $enc_prefix . self::KEY_TYPE_CUSTOM ) === 0; - - if ( $has_custom_key && ! defined( 'BACKWPUP_ENC_KEY' ) ) { - throw new Exception( - "Give string was encrypted using a custom key but 'BACKWPUP_ENC_KEY' constant is not defined anymore." - ); - } - - if ( $has_custom_key ) { - return array( BACKWPUP_ENC_KEY, self::KEY_TYPE_CUSTOM ); - } - - return array( $default_key, '' ); - } +class BackWPup_Encryption +{ + public const PREFIX = '$BackWPup$'; + public const KEY_TYPE_CUSTOM = '$0'; + + private static $classes = [ + BackWPup_Encryption_OpenSSL::PREFIX => \BackWPup_Encryption_OpenSSL::class, + BackWPup_Encryption_Mcrypt::PREFIX => \BackWPup_Encryption_Mcrypt::class, + BackWPup_Encryption_Fallback::PREFIX => \BackWPup_Encryption_Fallback::class, + ]; + + /** + * Encrypt a string using the best algorithm available. + * + * In case the given string is encrypted with a weaker algorithm, it will first be decrypted then the plain text + * obtained is encrypted with the better algorithm available and returned. + * + * @param string $string value to encrypt + * + * @return string encrypted string + */ + public static function encrypt($string) + { + if (!is_string($string) || !$string) { + return ''; + } + + try { + $cypher_class = self::cypher_class_for_string($string); + } catch (Exception $e) { + /** @TODO what to do here? The string is encrypted, but cypher used to encrypt isn't supported in current system */ + + return $string; + } + + try { + [$key, $key_type] = self::get_encrypt_info($cypher_class, $string); + } catch (Exception $e) { + /** @TODO what to do here? The string is encrypted, a custom key was used to encrypt, but it is not available anymore */ + + return $string; + } + + $best_cipher_class = self::best_cypher(); + + // The given string is not encrypted, let's encrypt it and return + if (!$cypher_class) { + /** @var BackWPup_Encryption_OpenSSL|BackWPup_Encryption_Mcrypt|BackWPup_Encryption_Fallback $best_cypher */ + $best_cypher = new $best_cipher_class($key, $key_type); + + return $best_cypher->encrypt($string); + } + + $encryption_count = substr_count($string, self::PREFIX); + + // The given string is encrypted once using best cypher, let's just return it + if ($encryption_count === 1 && $cypher_class === $best_cipher_class) { + return $string; + } + + /** @var BackWPup_Encryption_OpenSSL|BackWPup_Encryption_Mcrypt|BackWPup_Encryption_Fallback $cypher */ + $cypher = new $cypher_class($key, $key_type); + + $string = $cypher->decrypt($string); + + return self::encrypt($string); + } + + /** + * Decrypt a string (Passwords). + * + * @param string $string value to decrypt + * + * @return string decrypted string + */ + public static function decrypt($string) + { + if (!is_string($string) || !$string) { + return ''; + } + + try { + $cypher_class = self::cypher_class_for_string($string); + } catch (Exception $e) { + /** @TODO what to do here? The cypher used to encrypt is not supported in current system */ + + return ''; + } + + if (!$cypher_class) { + /** @TODO what to do here? The string seems not encrypted or maybe is corrupted */ + + return $string; + } + + try { + [$key, $key_type] = self::get_encrypt_info($cypher_class, $string); + } catch (Exception $e) { + /** @TODO what to do here? A custom key was used to encrypt but it is not available anymore */ + return ''; + } + + /** @var BackWPup_Encryption_OpenSSL|BackWPup_Encryption_Mcrypt|BackWPup_Encryption_Fallback $cypher */ + $cypher = new $cypher_class($key, $key_type); + + return trim(stripslashes($cypher->decrypt($string)), "\0"); + } + + /** + * @param string $string + * + * @throws Exception + * + * @return string + */ + private static function cypher_class_for_string($string) + { + foreach (self::$classes as $prefix => $class) { + $enc_prefix = self::PREFIX . $prefix; + + if (strpos($string, $enc_prefix) !== 0) { + continue; + } + + if (!call_user_func([$class, 'supported'])) { + throw new Exception( + "Give string was encrypted using {$class} but it is not currently supported in this system." + ); + } + + return $class; + } + + return ''; + } + + /** + * @return string + */ + private static function best_cypher() + { + foreach (self::$classes as $prefix => $class) { + if (!call_user_func([$class, 'supported'])) { + continue; + } + + return $class; + } + + // This should never happen because BackWPup_Encryption_Fallback::supported() always returns true + + return ''; + } + + /** + * @param string|null $class + * @param string $string + * + * @throws Exception + * + * @return array + */ + private static function get_encrypt_info($class = null, $string = '') + { + $default_key = DB_NAME . DB_USER . DB_PASSWORD; + + if (!is_string($class) || !$class) { + return defined('BACKWPUP_ENC_KEY') + ? [BACKWPUP_ENC_KEY, self::KEY_TYPE_CUSTOM] + : [$default_key, '']; + } + + $enc_prefix = self::PREFIX . constant("{$class}::PREFIX"); + $has_custom_key = strpos($string, $enc_prefix . self::KEY_TYPE_CUSTOM) === 0; + + if ($has_custom_key) { + if (!defined('BACKWPUP_ENC_KEY')) { + throw new Exception( + "Given string was encrypted using a custom key but 'BACKWPUP_ENC_KEY' constant is not defined anymore." + ); + } + + return [BACKWPUP_ENC_KEY, self::KEY_TYPE_CUSTOM]; + } + + return [$default_key, '']; + } } diff --git a/inc/class-factory-exception.php b/inc/class-factory-exception.php index 190fa858..7fbb7adc 100644 --- a/inc/class-factory-exception.php +++ b/inc/class-factory-exception.php @@ -1,17 +1,15 @@ isLink() ) { - $files_size += $file->getSize(); - } - } - -return $files_size; - } - - /** - * Get an absolute path if it is relative - * - * @param string $path - * - * @return string - */ - public static function get_absolute_path( $path = '/' ) { - - $path = str_replace( '\\', '/', $path ); - $content_path = trailingslashit( str_replace( '\\', '/', WP_CONTENT_DIR ) ); - - //use WP_CONTENT_DIR as root folder - if ( empty( $path ) || $path === '/' ) { - $path = $content_path; - } - - //make relative path to absolute - if ( substr( $path, 0, 1 ) !== '/' && ! preg_match( '#^[a-zA-Z]:/#', $path ) ) { - $path = $content_path . $path; - } - - $path = self::resolve_path( $path ); - - return $path; - } - - /** - * - * Check is folder readable and exists create it if not - * add .htaccess or index.html file in folder to prevent directory listing - * - * @param string $folder the folder to check - * @param bool $donotbackup Create a file that the folder will not backuped - * +class BackWPup_File +{ + /** + * Get the folder for blog uploads. + * + * @return string + */ + public static function get_upload_dir() + { + if (is_multisite()) { + if (defined('UPLOADBLOGSDIR')) { + return trailingslashit(str_replace('\\', '/', ABSPATH . UPLOADBLOGSDIR)); + } + if (is_dir(trailingslashit(WP_CONTENT_DIR) . 'uploads/sites')) { + return str_replace('\\', '/', trailingslashit(WP_CONTENT_DIR) . 'uploads/sites/'); + } + if (is_dir(trailingslashit(WP_CONTENT_DIR) . 'uploads')) { + return str_replace('\\', '/', trailingslashit(WP_CONTENT_DIR) . 'uploads/'); + } + + return trailingslashit(str_replace('\\', '/', WP_CONTENT_DIR)); + } + $upload_dir = wp_upload_dir(null, false, true); + + return trailingslashit(str_replace('\\', '/', $upload_dir['basedir'])); + } + + /** + * check if path in open basedir. + * + * @param string $file the file path to check + * + * @return bool is it in open basedir + */ + public static function is_in_open_basedir($file) + { + $ini_open_basedir = ini_get('open_basedir'); + + if (empty($ini_open_basedir)) { + return true; + } + + $open_base_dirs = explode(PATH_SEPARATOR, $ini_open_basedir); + $file = trailingslashit(strtolower(str_replace('\\', '/', $file))); + + foreach ($open_base_dirs as $open_base_dir) { + if (empty($open_base_dir) || !realpath($open_base_dir)) { + continue; + } + $open_base_dir = realpath($open_base_dir); + $open_base_dir = strtolower(str_replace('\\', '/', $open_base_dir)); + $part = substr($file, 0, strlen($open_base_dir)); + if ($part === $open_base_dir) { + return true; + } + } + + return false; + } + + /** + * get size of files in folder. + * + * @param string $folder the folder to calculate + * @param bool $deep went thrue suborders + * + * @return int folder size in byte + */ + public static function get_folder_size($folder) + { + $files_size = 0; + + if (!is_readable($folder)) { + return $files_size; + } + + $iterator = new RecursiveIteratorIterator(new BackWPup_Recursive_Directory($folder, FilesystemIterator::SKIP_DOTS)); + + foreach ($iterator as $file) { + if (!$file->isLink()) { + $files_size += $file->getSize(); + } + } + + return $files_size; + } + + /** + * Get an absolute path if it is relative. + * + * @param string $path + * + * @return string + */ + public static function get_absolute_path($path = '/') + { + $path = str_replace('\\', '/', $path); + $content_path = trailingslashit(str_replace('\\', '/', WP_CONTENT_DIR)); + + //use WP_CONTENT_DIR as root folder + if (empty($path) || $path === '/') { + $path = $content_path; + } + + //make relative path to absolute + if (substr($path, 0, 1) !== '/' && !preg_match('#^[a-zA-Z]:/#', $path)) { + $path = $content_path . $path; + } + + return self::resolve_path($path); + } + + /** + * Check is folder readable and exists create it if not + * add .htaccess or index.html file in folder to prevent directory listing. + * + * @param string $folder the folder to check + * @param bool $donotbackup Create a file that the folder will not backuped + * * @return string with error message if one - */ - public static function check_folder( $folder, $donotbackup = FALSE ) { - - $folder = self::get_absolute_path( $folder ); - $folder = untrailingslashit( $folder ); - - //check that is not home of WP - $uploads = self::get_upload_dir(); - if ( $folder === untrailingslashit( str_replace( '\\', '/', ABSPATH ) ) - || $folder === untrailingslashit( str_replace( '\\', '/', dirname( ABSPATH ) ) ) - || $folder === untrailingslashit( str_replace( '\\', '/', WP_PLUGIN_DIR ) ) - || $folder === untrailingslashit( str_replace( '\\', '/', WP_CONTENT_DIR ) ) - || $folder === untrailingslashit( $uploads ) - || $folder === '/' - ) { - return sprintf( __( 'Folder %1$s not allowed, please use another folder.', 'backwpup' ), $folder ); - } - - //open base dir check - if ( ! self::is_in_open_basedir( $folder ) ) { - return sprintf( __( 'Folder %1$s is not in open basedir, please use another folder.', 'backwpup' ), $folder ); - } - - //create folder if it not exists - if ( ! is_dir( $folder ) ) { - if ( ! wp_mkdir_p( $folder ) ) { - return sprintf( __( 'Cannot create folder: %1$s', 'backwpup' ), $folder ); - } - } - - //check is writable dir - if ( ! is_writable( $folder ) ) { - return sprintf( __( 'Folder "%1$s" is not writable', 'backwpup' ), $folder ); - } - - //create files for securing folder - if ( get_site_option( 'backwpup_cfg_protectfolders' ) ) { - $server_software = strtolower( $_SERVER[ 'SERVER_SOFTWARE' ] ); - //IIS - if ( strstr( $server_software, 'microsoft-iis' ) ) { - if ( ! file_exists( $folder . '/Web.config' ) ) { - file_put_contents( $folder . '/Web.config', - "" . PHP_EOL . - "\t" . PHP_EOL . - "\t\t" . PHP_EOL . - "\t\t\t" . PHP_EOL . - "\t\t" . PHP_EOL . - "\t" . PHP_EOL . - "" ); - } - } //Nginx - elseif ( strstr( $server_software, 'nginx' ) ) { - if ( ! file_exists( $folder . '/index.php' ) ) { - file_put_contents( $folder . '/index.php', "" . PHP_EOL . "" . PHP_EOL . "Deny from all" . PHP_EOL . "" . PHP_EOL . "" . PHP_EOL . "" . PHP_EOL . "Deny from all" . PHP_EOL . "" . PHP_EOL . "" . PHP_EOL . "" . PHP_EOL . "Deny from all" . PHP_EOL . "" . PHP_EOL . "" ); - } - if ( ! file_exists( $folder . '/index.php' ) ) { - file_put_contents( $folder . '/index.php', "' . PHP_EOL . + "\t" . PHP_EOL . + "\t\t" . PHP_EOL . + "\t\t\t" . PHP_EOL . + "\t\t" . PHP_EOL . + "\t" . PHP_EOL . + '' + ); + } + } //Nginx + elseif (strstr($server_software, 'nginx')) { + if (!file_exists($folder . '/index.php')) { + file_put_contents($folder . '/index.php', '' . PHP_EOL . '' . PHP_EOL . 'Deny from all' . PHP_EOL . '' . PHP_EOL . '' . PHP_EOL . '' . PHP_EOL . 'Deny from all' . PHP_EOL . '' . PHP_EOL . '' . PHP_EOL . '' . PHP_EOL . 'Deny from all' . PHP_EOL . '' . PHP_EOL . ''); + } + if (!file_exists($folder . '/index.php')) { + file_put_contents($folder . '/index.php', 'add_help_tab( array( - 'id' => 'plugininfo', - 'title' => __( 'Plugin Info', 'backwpup' ), - 'content' => - '

'.str_replace( '\"','"', sprintf( _x( '%1$s version %2$s. A project by Inpsyde GmbH.','Plugin name and link; Plugin Version','backwpup' ), '' . BackWPup::get_plugin_data( 'Name' ) . '' , BackWPup::get_plugin_data( 'Version' ) ) ) . '

' - . '

' . esc_html__( 'BackWPup comes with ABSOLUTELY NO WARRANTY. This is a free software, and you are welcome to redistribute it under certain conditions.', 'backwpup' ) . '

' - ) ); - - $text_help_sidebar = '

' . __( 'For more information:', 'backwpup' ) . '

'; - $text_help_sidebar .= '

' . BackWPup::get_plugin_data( 'Name' ) . '

'; - $text_help_sidebar .= '

' . esc_html__( 'Plugin on wordpress.org', 'backwpup' ) . '

'; - $text_help_sidebar .= '

' . esc_html__( 'Manual', 'backwpup' ) . '

'; - - get_current_screen()->set_help_sidebar( $text_help_sidebar ); - } - - } - - /** - * @static - * - * @param array $tab - */ - public static function add_tab( $tab = array() ) { - - if ( method_exists( get_current_screen(), 'add_help_tab' ) ) - get_current_screen()->add_help_tab( $tab ); - - } +class BackWPup_Help +{ + public static function help() + { + if (method_exists(get_current_screen(), 'add_help_tab')) { + get_current_screen()->add_help_tab([ + 'id' => 'plugininfo', + 'title' => __('Plugin Info', 'backwpup'), + 'content' => '

' . str_replace('\"', '"', sprintf(_x('%1$s version %2$s. A project by Inpsyde GmbH.', 'Plugin name and link; Plugin Version', 'backwpup'), '' . BackWPup::get_plugin_data('Name') . '', BackWPup::get_plugin_data('Version'))) . '

' + . '

' . esc_html__('BackWPup comes with ABSOLUTELY NO WARRANTY. This is a free software, and you are welcome to redistribute it under certain conditions.', 'backwpup') . '

', + ]); + + $text_help_sidebar = '

' . __('For more information:', 'backwpup') . '

'; + $text_help_sidebar .= '

' . BackWPup::get_plugin_data('Name') . '

'; + $text_help_sidebar .= '

' . esc_html__('Plugin on wordpress.org', 'backwpup') . '

'; + $text_help_sidebar .= '

' . esc_html__('Manual', 'backwpup') . '

'; + + get_current_screen()->set_help_sidebar($text_help_sidebar); + } + } + + /** + * @static + * + * @param array $tab + */ + public static function add_tab($tab = []) + { + if (method_exists(get_current_screen(), 'add_help_tab')) { + get_current_screen()->add_help_tab($tab); + } + } } diff --git a/inc/class-install.php b/inc/class-install.php index a53082a8..58460c61 100644 --- a/inc/class-install.php +++ b/inc/class-install.php @@ -1,333 +1,356 @@ ') && version_compare('3.0', $version_db, '<')) { + $upload_dir = wp_upload_dir(null, false, true); + $logfolder = get_site_option('backwpup_cfg_logfolder'); + if (empty($logfolder)) { + $old_log_folder = trailingslashit(str_replace('\\', '/', $upload_dir['basedir'])) . 'backwpup-' . substr(md5(md5(SECURE_AUTH_KEY)), 9, 5) . '-logs/'; + update_site_option('backwpup_cfg_logfolder', $old_log_folder); + } + } - //changes for version before 3.0.0 - if ( ! $version_db && get_option( 'backwpup' ) && get_option( 'backwpup_jobs' ) ) { - self::upgrade_from_version_two(); - } + //changes for 3.2 + $no_translation = get_site_option('backwpup_cfg_jobnotranslate'); + if ($no_translation) { + update_site_option('backwpup_cfg_loglevel', 'normal'); + delete_site_option('backwpup_cfg_jobnotranslate'); + } - //changes for version before 3.0.14 - if ( version_compare( '3.0.13', $version_db, '>' ) && version_compare( '3.0', $version_db, '<' ) ) { - $upload_dir = wp_upload_dir( null, false, true ); - $logfolder = get_site_option( 'backwpup_cfg_logfolder' ); - if ( empty( $logfolder ) ) { - $old_log_folder = trailingslashit( str_replace( '\\', '/',$upload_dir[ 'basedir' ] ) ) . 'backwpup-' . substr( md5( md5( SECURE_AUTH_KEY ) ), 9, 5 ) . '-logs/'; - update_site_option( 'backwpup_cfg_logfolder', $old_log_folder ); - } - } + delete_site_option('backwpup_cfg_jobziparchivemethod'); + //create new options + if (is_multisite()) { + add_site_option('backwpup_jobs', []); + } else { + add_option('backwpup_jobs', [], null, 'no'); + } - //changes for 3.2 - $no_translation = get_site_option( 'backwpup_cfg_jobnotranslate' ); - if ( $no_translation ) { - update_site_option( 'backwpup_cfg_loglevel', 'normal' ); - delete_site_option( 'backwpup_cfg_jobnotranslate' ); - } + //remove old schedule + wp_clear_scheduled_hook('backwpup_cron'); + //make new schedule + $activejobs = BackWPup_Option::get_job_ids('activetype', 'wpcron'); + if (!empty($activejobs)) { + foreach ($activejobs as $id) { + $cron_next = BackWPup_Cron::cron_next(BackWPup_Option::get($id, 'cron')); + wp_schedule_single_event($cron_next, 'backwpup_cron', ['arg' => $id]); + } + } + $activejobs = BackWPup_Option::get_job_ids('activetype', 'easycron'); + if (!empty($activejobs)) { + foreach ($activejobs as $id) { + BackWPup_EasyCron::update($id); + } + } - delete_site_option( 'backwpup_cfg_jobziparchivemethod' ); + //add Cleanup schedule + if (!wp_next_scheduled('backwpup_check_cleanup')) { + wp_schedule_event(time(), 'twicedaily', 'backwpup_check_cleanup'); + } - //create new options - if ( is_multisite() ) { - add_site_option( 'backwpup_jobs', array() ); - } else { - add_option( 'backwpup_jobs', array(), NULL, 'no' ); - } + //add capabilities to administrator role + $role = get_role('administrator'); + if (is_object($role) && method_exists($role, 'add_cap')) { + $role->add_cap('backwpup'); + $role->add_cap('backwpup_jobs'); + $role->add_cap('backwpup_jobs_edit'); + $role->add_cap('backwpup_jobs_start'); + $role->add_cap('backwpup_backups'); + $role->add_cap('backwpup_backups_download'); + $role->add_cap('backwpup_backups_delete'); + $role->add_cap('backwpup_logs'); + $role->add_cap('backwpup_logs_delete'); + $role->add_cap('backwpup_settings'); + $role->add_cap('backwpup_restore'); + } - //remove old schedule - wp_clear_scheduled_hook( 'backwpup_cron' ); - //make new schedule - $activejobs = BackWPup_Option::get_job_ids( 'activetype', 'wpcron' ); - if ( ! empty( $activejobs ) ) { - foreach ( $activejobs as $id ) { - $cron_next = BackWPup_Cron::cron_next( BackWPup_Option::get( $id, 'cron') ); - wp_schedule_single_event( $cron_next, 'backwpup_cron', array( 'arg' => $id ) ); - } - } - $activejobs = BackWPup_Option::get_job_ids( 'activetype', 'easycron' ); - if ( ! empty( $activejobs ) ) { - foreach ( $activejobs as $id ) { - BackWPup_EasyCron::update( $id ); - } - } + //add/overwrite roles + add_role('backwpup_admin', __('BackWPup Admin', 'backwpup'), [ + 'read' => true, // make it usable for single user + 'backwpup' => true, // BackWPup general accesses (like Dashboard) + 'backwpup_jobs' => true, // accesses for job page + 'backwpup_jobs_edit' => true, // user can edit/delete/copy/export jobs + 'backwpup_jobs_start' => true, // user can start jobs + 'backwpup_backups' => true, // accesses for backups page + 'backwpup_backups_download' => true, // user can download backup files + 'backwpup_backups_delete' => true, // user can delete backup files + 'backwpup_logs' => true, // accesses for logs page + 'backwpup_logs_delete' => true, // user can delete log files + 'backwpup_settings' => true, // accesses for settings page + 'backwpup_restore' => true, // accesses for restore page + ]); - //add Cleanup schedule - if ( ! wp_next_scheduled( 'backwpup_check_cleanup' ) ) { - wp_schedule_event( time(), 'twicedaily', 'backwpup_check_cleanup' ); - } + add_role('backwpup_check', __('BackWPup jobs checker', 'backwpup'), [ + 'read' => true, + 'backwpup' => true, + 'backwpup_jobs' => true, + 'backwpup_jobs_edit' => false, + 'backwpup_jobs_start' => false, + 'backwpup_backups' => true, + 'backwpup_backups_download' => false, + 'backwpup_backups_delete' => false, + 'backwpup_logs' => true, + 'backwpup_logs_delete' => false, + 'backwpup_settings' => false, + 'backwpup_restore' => false, + ]); - //add capabilities to administrator role - $role = get_role( 'administrator' ); - if ( is_object( $role ) && method_exists( $role, 'add_cap' ) ) { - $role->add_cap( 'backwpup' ); - $role->add_cap( 'backwpup_jobs' ); - $role->add_cap( 'backwpup_jobs_edit' ); - $role->add_cap( 'backwpup_jobs_start' ); - $role->add_cap( 'backwpup_backups' ); - $role->add_cap( 'backwpup_backups_download' ); - $role->add_cap( 'backwpup_backups_delete' ); - $role->add_cap( 'backwpup_logs' ); - $role->add_cap( 'backwpup_logs_delete' ); - $role->add_cap( 'backwpup_settings' ); - $role->add_cap( 'backwpup_restore' ); - } + add_role('backwpup_helper', __('BackWPup jobs functions', 'backwpup'), [ + 'read' => true, + 'backwpup' => true, + 'backwpup_jobs' => true, + 'backwpup_jobs_edit' => false, + 'backwpup_jobs_start' => true, + 'backwpup_backups' => true, + 'backwpup_backups_download' => true, + 'backwpup_backups_delete' => true, + 'backwpup_logs' => true, + 'backwpup_logs_delete' => true, + 'backwpup_settings' => false, + 'backwpup_restore' => false, + ]); - //add/overwrite roles - add_role( 'backwpup_admin', __( 'BackWPup Admin', 'backwpup' ), array( - 'read' => TRUE, // make it usable for single user - 'backwpup' => TRUE, // BackWPup general accesses (like Dashboard) - 'backwpup_jobs' => TRUE, // accesses for job page - 'backwpup_jobs_edit' => TRUE, // user can edit/delete/copy/export jobs - 'backwpup_jobs_start' => TRUE, // user can start jobs - 'backwpup_backups' => TRUE, // accesses for backups page - 'backwpup_backups_download' => TRUE, // user can download backup files - 'backwpup_backups_delete' => TRUE, // user can delete backup files - 'backwpup_logs' => TRUE, // accesses for logs page - 'backwpup_logs_delete' => TRUE, // user can delete log files - 'backwpup_settings' => TRUE, // accesses for settings page - 'backwpup_restore' => TRUE, // accesses for restore page - ) ); + //add default options + BackWPup_Option::default_site_options(); - add_role( 'backwpup_check', __( 'BackWPup jobs checker', 'backwpup' ), array( - 'read' => TRUE, - 'backwpup' => TRUE, - 'backwpup_jobs' => TRUE, - 'backwpup_jobs_edit' => FALSE, - 'backwpup_jobs_start' => FALSE, - 'backwpup_backups' => TRUE, - 'backwpup_backups_download' => FALSE, - 'backwpup_backups_delete' => FALSE, - 'backwpup_logs' => TRUE, - 'backwpup_logs_delete' => FALSE, - 'backwpup_settings' => FALSE, - 'backwpup_restore' => FALSE, - ) ); + //update version + update_site_option('backwpup_version', BackWPup::get_plugin_data('Version')); - add_role( 'backwpup_helper', __( 'BackWPup jobs functions', 'backwpup' ), array( - 'read' => TRUE, - 'backwpup' => TRUE, - 'backwpup_jobs' => TRUE, - 'backwpup_jobs_edit' => FALSE, - 'backwpup_jobs_start' => TRUE, - 'backwpup_backups' => TRUE, - 'backwpup_backups_download' => TRUE, - 'backwpup_backups_delete' => TRUE, - 'backwpup_logs' => TRUE, - 'backwpup_logs_delete' => TRUE, - 'backwpup_settings' => FALSE, - 'backwpup_restore' => FALSE, - ) ); + //only redirect if not in WP CLI environment + if (!$version_db && !(defined(\WP_CLI::class) && WP_CLI)) { + wp_redirect(network_admin_url('admin.php') . '?page=backwpupabout&welcome=1'); - //add default options - BackWPup_Option::default_site_options(); + exit(); + } + } - //update version - update_site_option( 'backwpup_version', BackWPup::get_plugin_data( 'Version' ) ); + private static function upgrade_from_version_two() + { + //load options + $cfg = get_option('backwpup'); //only exists in Version 2 + $jobs = get_option('backwpup_jobs'); - //only redirect if not in WP CLI environment - if ( ! $version_db && ! ( defined( 'WP_CLI' ) && WP_CLI ) ) { - wp_redirect( network_admin_url( 'admin.php' ) . '?page=backwpupabout&welcome=1' ); - die(); - } + //delete old options + delete_option('backwpup'); + delete_option('backwpup_jobs'); - } + //add new option default structure and without auto load cache + if (!is_multisite()) { + add_option('backwpup_jobs', [], null, 'no'); + } - private static function upgrade_from_version_two() { + //upgrade cfg + //if old value switch it to new + if (!empty($cfg['dirlogs'])) { + $cfg['logfolder'] = $cfg['dirlogs']; + } + if (!empty($cfg['httpauthpassword'])) { + if (preg_match('%^[a-zA-Z0-9/+]*={0,2}$%', $cfg['httpauthpassword'])) { + $cfg['httpauthpassword'] = base64_decode($cfg['httpauthpassword']); + } + $cfg['httpauthpassword'] = BackWPup_Encryption::encrypt($cfg['httpauthpassword']); + } + // delete old not needed vars + unset($cfg['dirtemp'], $cfg['dirlogs'], $cfg['logfilelist'], $cfg['jobscriptruntime'], $cfg['jobscriptruntimelong'], $cfg['last_activate'], $cfg['disablewpcron'], $cfg['phpzip'], $cfg['apicronservice'], $cfg['mailsndemail'], $cfg['mailsndname'], $cfg['mailmethod'], $cfg['mailsendmail'], $cfg['mailhost'], $cfg['mailpass'], $cfg['mailhostport'], $cfg['mailsecure'], $cfg['mailuser']); + //save in options + foreach ($cfg as $cfgname => $cfgvalue) { + update_site_option('backwpup_cfg_' . $cfgname, $cfgvalue); + } - //load options - $cfg = get_option( 'backwpup' ); //only exists in Version 2 - $jobs = get_option( 'backwpup_jobs' ); + //Put old jobs to new if exists + foreach ($jobs as $jobid => $jobvalue) { + //convert general settings + if (empty($jobvalue['jobid'])) { + $jobvalue['jobid'] = $jobid; + } + if (empty($jobvalue['activated'])) { + $jobvalue['activetype'] = ''; + } else { + $jobvalue['activetype'] = 'wpcron'; + } + if (!isset($jobvalue['cronselect']) && !isset($jobvalue['cron'])) { + $jobvalue['cronselect'] = 'basic'; + } elseif (!isset($jobvalue['cronselect']) && isset($jobvalue['cron'])) { + $jobvalue['cronselect'] = 'advanced'; + } + $jobvalue['backuptype'] = 'archive'; + $jobvalue['type'] = explode('+', $jobvalue['type']); //save as array - //delete old options - delete_option( 'backwpup' ); - delete_option( 'backwpup_jobs' ); + foreach ($jobvalue['type'] as $key => $type) { + if ($type == 'DB') { + $jobvalue['type'][$key] = 'DBDUMP'; + } + if ($type == 'OPTIMIZE') { + unset($jobvalue['type'][$key]); + } + if ($type == 'CHECK') { + $jobvalue['type'][$key] = 'DBCHECK'; + } + if ($type == 'MAIL') { + $jobvalue['type'][$key] = 'EMAIL'; + } + } + $jobvalue['archivename'] = $jobvalue['fileprefix'] . '%Y-%m-%d_%H-%i-%s'; + $jobvalue['archiveformat'] = $jobvalue['fileformart']; + //convert active destinations + $jobvalue['destinations'] = []; + if (!empty($jobvalue['backupdir']) && $jobvalue['backupdir'] != '/') { + $jobvalue['destinations'][] = 'FOLDER'; + } + if (!empty($jobvalue['mailaddress'])) { + $jobvalue['destinations'][] = 'MAIL'; + } + if (!empty($jobvalue['ftphost']) && !empty($jobvalue['ftpuser']) && !empty($jobvalue['ftppass'])) { + $jobvalue['destinations'][] = 'FTP'; + } + if (!empty($jobvalue['dropetoken']) && !empty($jobvalue['dropesecret'])) { + $jobvalue['destinations'][] = 'DROPBOX'; + } + if (!empty($jobvalue['sugarrefreshtoken']) && !empty($jobvalue['sugarroot'])) { + $jobvalue['destinations'][] = 'SUGARSYNC'; + } + if (!empty($jobvalue['awsAccessKey']) && !empty($jobvalue['awsSecretKey']) && !empty($jobvalue['awsBucket'])) { + $jobvalue['destinations'][] = 'S3'; + } + if (!empty($jobvalue['GStorageAccessKey']) and !empty($jobvalue['GStorageSecret']) && !empty($jobvalue['GStorageBucket']) && !in_array('S3', $jobvalue['destinations'], true)) { + $jobvalue['destinations'][] = 'S3'; + } + if (!empty($jobvalue['rscUsername']) && !empty($jobvalue['rscAPIKey']) && !empty($jobvalue['rscContainer'])) { + $jobvalue['destinations'][] = 'RSC'; + } + if (!empty($jobvalue['msazureHost']) && !empty($jobvalue['msazureAccName']) && !empty($jobvalue['msazureKey']) && !empty($jobvalue['msazureContainer'])) { + $jobvalue['destinations'][] = 'MSAZURE'; + } + //convert dropbox + $jobvalue['dropboxtoken'] = ''; //new app key are set must reauth + $jobvalue['dropboxsecret'] = ''; + $jobvalue['dropboxroot'] = 'dropbox'; + $jobvalue['dropboxmaxbackups'] = $jobvalue['dropemaxbackups']; + $jobvalue['dropboxdir'] = $jobvalue['dropedir']; + unset($jobvalue['dropetoken'], $jobvalue['dropesecret'], $jobvalue['droperoot'], $jobvalue['dropemaxbackups'], $jobvalue['dropedir']); + //convert amazon S3 + $jobvalue['s3accesskey'] = $jobvalue['awsAccessKey']; + $jobvalue['s3secretkey'] = BackWPup_Encryption::encrypt($jobvalue['awsSecretKey']); + $jobvalue['s3bucket'] = $jobvalue['awsBucket']; + //get aws region + $jobvalue['s3region'] = 'us-east-1'; + $jobvalue['s3storageclass'] = !empty($jobvalue['awsrrs']) ? 'REDUCED_REDUNDANCY' : ''; + $jobvalue['s3dir'] = $jobvalue['awsdir']; + $jobvalue['s3maxbackups'] = $jobvalue['awsmaxbackups']; + unset($jobvalue['awsAccessKey'], $jobvalue['awsSecretKey'], $jobvalue['awsBucket'], $jobvalue['awsrrs'], $jobvalue['awsdir'], $jobvalue['awsmaxbackups']); + //convert google storage + $jobvalue['s3accesskey'] = $jobvalue['GStorageAccessKey']; + $jobvalue['s3secretkey'] = BackWPup_Encryption::encrypt($jobvalue['GStorageSecret']); + $jobvalue['s3bucket'] = $jobvalue['GStorageBucket']; + $jobvalue['s3region'] = 'google-storage'; + $jobvalue['s3ssencrypt'] = ''; + $jobvalue['s3dir'] = $jobvalue['GStoragedir']; + $jobvalue['s3maxbackups'] = $jobvalue['GStoragemaxbackups']; + unset($jobvalue['GStorageAccessKey'], $jobvalue['GStorageSecret'], $jobvalue['GStorageBucket'], $jobvalue['GStoragedir'], $jobvalue['GStoragemaxbackups']); + //convert MS Azure storage + $jobvalue['msazureaccname'] = $jobvalue['msazureAccName']; + $jobvalue['msazurekey'] = BackWPup_Encryption::encrypt($jobvalue['msazureKey']); + $jobvalue['msazurecontainer'] = $jobvalue['msazureContainer']; + unset($jobvalue['msazureHost'], $jobvalue['msazureAccName'], $jobvalue['msazureKey'], $jobvalue['msazureContainer']); + //convert FTP + if (preg_match('%^[a-zA-Z0-9/+]*={0,2}$%', $jobvalue['ftppass'])) { + $jobvalue['ftppass'] = base64_decode($jobvalue['ftppass']); + } + $jobvalue['ftppass'] = BackWPup_Encryption::encrypt($jobvalue['ftppass']); + if (!empty($jobvalue['ftphost']) && strstr($jobvalue['ftphost'], ':')) { + [$jobvalue['ftphost'], $jobvalue['ftphostport']] = explode(':', $jobvalue['ftphost'], 2); + } + //convert Sugarsync + //convert Mail + $jobvalue['emailaddress'] = $jobvalue['mailaddress']; + $jobvalue['emailefilesize'] = $jobvalue['mailefilesize']; + unset($jobvalue['mailaddress'], $jobvalue['mailefilesize']); + //convert RSC + $jobvalue['rscusername'] = $jobvalue['rscUsername']; + $jobvalue['rscapikey'] = $jobvalue['rscAPIKey']; + $jobvalue['rsccontainer'] = $jobvalue['rscContainer']; + //convert jobtype DB Dump + $jobvalue['dbdumpexclude'] = $jobvalue['dbexclude']; + unset($jobvalue['dbexclude'], $jobvalue['dbshortinsert']); + //convert jobtype DBDUMP, DBCHECK + $jobvalue['dbcheckrepair'] = true; + unset($jobvalue['maintenance']); + //convert jobtype wpexport + //convert jobtype file + $excludes = []; - //add new option default structure and without auto load cache - if ( ! is_multisite() ) - add_option( 'backwpup_jobs', array(), NULL, 'no' ); + foreach ($jobvalue['backuprootexcludedirs'] as $folder) { + $excludes[] = basename($folder); + } + $jobvalue['backuprootexcludedirs'] = $excludes; + $excludes = []; - //upgrade cfg - //if old value switch it to new - if ( ! empty( $cfg[ 'dirlogs' ] ) ) - $cfg[ 'logfolder' ] = $cfg[ 'dirlogs' ]; - if ( ! empty( $cfg[ 'httpauthpassword' ] ) ) { - if ( preg_match('%^[a-zA-Z0-9/+]*={0,2}$%', $cfg[ 'httpauthpassword' ] ) ) - $cfg[ 'httpauthpassword' ] = base64_decode( $cfg[ 'httpauthpassword' ] ); - $cfg[ 'httpauthpassword' ] = BackWPup_Encryption::encrypt( $cfg[ 'httpauthpassword' ] ); - } - // delete old not needed vars - unset( $cfg[ 'dirtemp' ], $cfg[ 'dirlogs' ], $cfg[ 'logfilelist' ], $cfg[ 'jobscriptruntime' ], $cfg[ 'jobscriptruntimelong' ], $cfg[ 'last_activate' ], $cfg[ 'disablewpcron' ], $cfg[ 'phpzip' ], $cfg[ 'apicronservice' ], $cfg[ 'mailsndemail' ], $cfg[ 'mailsndname' ], $cfg[ 'mailmethod' ], $cfg[ 'mailsendmail' ], $cfg[ 'mailhost' ], $cfg[ 'mailpass' ], $cfg[ 'mailhostport' ], $cfg[ 'mailsecure' ], $cfg[ 'mailuser' ] ); - //save in options - foreach ( $cfg as $cfgname => $cfgvalue ) - update_site_option( 'backwpup_cfg_' . $cfgname, $cfgvalue ); + foreach ($jobvalue['backupcontentexcludedirs'] as $folder) { + $excludes[] = basename($folder); + } + $jobvalue['backupcontentexcludedirs'] = $excludes; + $excludes = []; - //Put old jobs to new if exists - foreach ( $jobs as $jobid => $jobvalue ) { - //convert general settings - if ( empty( $jobvalue[ 'jobid' ] ) ) - $jobvalue[ 'jobid' ] = $jobid; - if ( empty( $jobvalue[ 'activated' ] ) ) - $jobvalue[ 'activetype' ] = ''; - else - $jobvalue[ 'activetype' ] = 'wpcron'; - if ( ! isset( $jobvalue[ 'cronselect' ] ) && ! isset( $jobvalue[ 'cron' ] ) ) - $jobvalue[ 'cronselect' ] = 'basic'; - elseif ( ! isset( $jobvalue[ 'cronselect' ] ) && isset( $jobvalue[ 'cron' ] ) ) - $jobvalue[ 'cronselect' ] = 'advanced'; - $jobvalue[ 'backuptype' ] = 'archive'; - $jobvalue[ 'type' ] = explode( '+', $jobvalue[ 'type' ] ); //save as array - foreach ( $jobvalue[ 'type' ] as $key => $type ) { - if ( $type == 'DB' ) - $jobvalue[ 'type' ][ $key ] = 'DBDUMP'; - if ( $type == 'OPTIMIZE' ) - unset( $jobvalue[ 'type' ][ $key ] ); - if ( $type == 'CHECK' ) - $jobvalue[ 'type' ][ $key ] = 'DBCHECK'; - if ( $type == 'MAIL' ) - $jobvalue[ 'type' ][ $key ] = 'EMAIL'; - } - $jobvalue[ 'archivename' ] = $jobvalue[ 'fileprefix' ] . '%Y-%m-%d_%H-%i-%s'; - $jobvalue[ 'archiveformat' ] = $jobvalue[ 'fileformart' ]; - //convert active destinations - $jobvalue[ 'destinations' ] = array(); - if ( ! empty( $jobvalue[ 'backupdir' ] ) && $jobvalue[ 'backupdir' ] != '/' ) - $jobvalue[ 'destinations' ][ ] = 'FOLDER'; - if ( ! empty( $jobvalue[ 'mailaddress' ] ) ) - $jobvalue[ 'destinations' ][ ] = 'MAIL'; - if ( ! empty( $jobvalue[ 'ftphost' ] ) && ! empty( $jobvalue[ 'ftpuser' ] ) && ! empty( $jobvalue[ 'ftppass' ] ) ) - $jobvalue[ 'destinations' ][ ] = 'FTP'; - if ( ! empty( $jobvalue[ 'dropetoken' ] ) && ! empty( $jobvalue[ 'dropesecret' ] ) ) - $jobvalue[ 'destinations' ][ ] = 'DROPBOX'; - if ( ! empty( $jobvalue[ 'sugarrefreshtoken' ] ) && ! empty( $jobvalue[ 'sugarroot' ] ) ) - $jobvalue[ 'destinations' ][ ] = 'SUGARSYNC'; - if ( ! empty( $jobvalue[ 'awsAccessKey' ] ) && ! empty( $jobvalue[ 'awsSecretKey' ] ) && ! empty( $jobvalue[ 'awsBucket' ] ) ) - $jobvalue[ 'destinations' ][ ] = 'S3'; - if ( ! empty( $jobvalue[ 'GStorageAccessKey' ] ) and ! empty( $jobvalue[ 'GStorageSecret' ] ) && ! empty( $jobvalue[ 'GStorageBucket' ] ) && !in_array( 'S3', $jobvalue[ 'destinations' ], true ) ) - $jobvalue[ 'destinations' ][ ] = 'S3'; - if ( ! empty( $jobvalue[ 'rscUsername' ] ) && ! empty( $jobvalue[ 'rscAPIKey' ] ) && ! empty( $jobvalue[ 'rscContainer' ] ) ) - $jobvalue[ 'destinations' ][ ] = 'RSC'; - if ( ! empty( $jobvalue[ 'msazureHost' ] ) && ! empty( $jobvalue[ 'msazureAccName' ] ) && ! empty( $jobvalue[ 'msazureKey' ] ) && ! empty( $jobvalue[ 'msazureContainer' ] ) ) - $jobvalue[ 'destinations' ][ ] = 'MSAZURE'; - //convert dropbox - $jobvalue[ 'dropboxtoken' ] = ''; //new app key are set must reauth - $jobvalue[ 'dropboxsecret' ] = ''; - $jobvalue[ 'dropboxroot' ] = 'dropbox'; - $jobvalue[ 'dropboxmaxbackups' ] = $jobvalue[ 'dropemaxbackups' ]; - $jobvalue[ 'dropboxdir' ] = $jobvalue[ 'dropedir' ]; - unset( $jobvalue[ 'dropetoken' ], $jobvalue[ 'dropesecret' ], $jobvalue[ 'droperoot' ], $jobvalue[ 'dropemaxbackups' ], $jobvalue[ 'dropedir' ] ); - //convert amazon S3 - $jobvalue[ 's3accesskey' ] = $jobvalue[ 'awsAccessKey' ]; - $jobvalue[ 's3secretkey' ] = BackWPup_Encryption::encrypt( $jobvalue[ 'awsSecretKey' ] ); - $jobvalue[ 's3bucket' ] = $jobvalue[ 'awsBucket' ]; - //get aws region - $jobvalue[ 's3region' ] = 'us-east-1'; - $jobvalue[ 's3storageclass' ] = !empty( $jobvalue[ 'awsrrs' ] ) ? 'REDUCED_REDUNDANCY' : ''; - $jobvalue[ 's3dir' ] = $jobvalue[ 'awsdir' ]; - $jobvalue[ 's3maxbackups' ] = $jobvalue[ 'awsmaxbackups' ]; - unset( $jobvalue[ 'awsAccessKey' ], $jobvalue[ 'awsSecretKey' ], $jobvalue[ 'awsBucket' ], $jobvalue[ 'awsrrs' ], $jobvalue[ 'awsdir' ], $jobvalue[ 'awsmaxbackups' ] ); - //convert google storage - $jobvalue[ 's3accesskey' ] = $jobvalue[ 'GStorageAccessKey' ]; - $jobvalue[ 's3secretkey' ] = BackWPup_Encryption::encrypt( $jobvalue[ 'GStorageSecret' ] ); - $jobvalue[ 's3bucket' ] = $jobvalue[ 'GStorageBucket' ]; - $jobvalue[ 's3region' ] = 'google-storage'; - $jobvalue[ 's3ssencrypt' ] = ''; - $jobvalue[ 's3dir' ] = $jobvalue[ 'GStoragedir' ]; - $jobvalue[ 's3maxbackups' ] = $jobvalue[ 'GStoragemaxbackups' ]; - unset( $jobvalue[ 'GStorageAccessKey' ], $jobvalue[ 'GStorageSecret' ], $jobvalue[ 'GStorageBucket' ], $jobvalue[ 'GStoragedir' ], $jobvalue[ 'GStoragemaxbackups' ] ); - //convert MS Azure storage - $jobvalue[ 'msazureaccname' ] = $jobvalue[ 'msazureAccName' ]; - $jobvalue[ 'msazurekey' ] = BackWPup_Encryption::encrypt( $jobvalue[ 'msazureKey' ] ); - $jobvalue[ 'msazurecontainer' ] = $jobvalue[ 'msazureContainer' ]; - unset( $jobvalue[ 'msazureHost' ], $jobvalue[ 'msazureAccName' ], $jobvalue[ 'msazureKey' ], $jobvalue[ 'msazureContainer' ] ); - //convert FTP - if ( preg_match('%^[a-zA-Z0-9/+]*={0,2}$%', $jobvalue[ 'ftppass' ]) ) - $jobvalue[ 'ftppass' ] = base64_decode( $jobvalue[ 'ftppass' ] ); - $jobvalue[ 'ftppass' ] = BackWPup_Encryption::encrypt( $jobvalue[ 'ftppass' ] ); - if ( ! empty( $jobvalue[ 'ftphost' ] ) && strstr( $jobvalue[ 'ftphost' ], ':' ) ) - list( $jobvalue[ 'ftphost' ], $jobvalue[ 'ftphostport' ] ) = explode( ':', $jobvalue[ 'ftphost' ], 2 ); - //convert Sugarsync - //convert Mail - $jobvalue[ 'emailaddress' ] = $jobvalue[ 'mailaddress' ]; - $jobvalue[ 'emailefilesize' ] = $jobvalue[ 'mailefilesize' ]; - unset( $jobvalue[ 'mailaddress' ], $jobvalue[ 'mailefilesize' ] ); - //convert RSC - $jobvalue[ 'rscusername' ] = $jobvalue[ 'rscUsername' ]; - $jobvalue[ 'rscapikey' ] = $jobvalue[ 'rscAPIKey' ]; - $jobvalue[ 'rsccontainer' ] = $jobvalue[ 'rscContainer' ]; - //convert jobtype DB Dump - $jobvalue[ 'dbdumpexclude' ] = $jobvalue[ 'dbexclude' ]; - unset( $jobvalue[ 'dbexclude' ], $jobvalue['dbshortinsert'] ); - //convert jobtype DBDUMP, DBCHECK - $jobvalue[ 'dbcheckrepair' ] = TRUE; - unset( $jobvalue[ 'maintenance' ] ); - //convert jobtype wpexport - //convert jobtype file - $excludes = array(); - foreach ( $jobvalue[ 'backuprootexcludedirs' ] as $folder ) { - $excludes[] = basename( $folder ); - } - $jobvalue[ 'backuprootexcludedirs' ] = $excludes; - $excludes = array(); - foreach ( $jobvalue[ 'backupcontentexcludedirs' ] as $folder ) { - $excludes[] = basename( $folder ); - } - $jobvalue[ 'backupcontentexcludedirs' ] = $excludes; - $excludes = array(); - foreach ( $jobvalue[ 'backuppluginsexcludedirs' ] as $folder ) { - $excludes[] = basename( $folder ); - } - $jobvalue[ 'backuppluginsexcludedirs' ]= $excludes; - $excludes = array(); - foreach ( $jobvalue[ 'backupthemesexcludedirs' ] as $folder ) { - $excludes[] = basename( $folder ); - } - $jobvalue[ 'backupthemesexcludedirs' ] = $excludes; - $excludes = array(); - foreach ( $jobvalue[ 'backupuploadsexcludedirs' ] as $folder ) { - $excludes[] = basename( $folder ); - } - $jobvalue[ 'backupuploadsexcludedirs' ] = $excludes; - //delete not longer needed - unset( $jobvalue[ 'cronnextrun' ], $jobvalue[ 'fileprefix' ], $jobvalue[ 'fileformart' ], $jobvalue[ 'scheduleintervaltype' ], $jobvalue[ 'scheduleintervalteimes' ], $jobvalue[ 'scheduleinterval' ], $jobvalue[ 'dropemail' ], $jobvalue[ 'dropepass' ], $jobvalue[ 'dropesignmethod' ] ); - //save in options - foreach ( $jobvalue as $jobvaluename => $jobvaluevalue ) - BackWPup_Option::update( $jobvalue[ 'jobid' ], $jobvaluename, $jobvaluevalue ); - } + foreach ($jobvalue['backuppluginsexcludedirs'] as $folder) { + $excludes[] = basename($folder); + } + $jobvalue['backuppluginsexcludedirs'] = $excludes; + $excludes = []; - } + foreach ($jobvalue['backupthemesexcludedirs'] as $folder) { + $excludes[] = basename($folder); + } + $jobvalue['backupthemesexcludedirs'] = $excludes; + $excludes = []; - /** - * - * Cleanup on Plugin deactivation - * - * @return void - */ - public static function deactivate() { + foreach ($jobvalue['backupuploadsexcludedirs'] as $folder) { + $excludes[] = basename($folder); + } + $jobvalue['backupuploadsexcludedirs'] = $excludes; + //delete not longer needed + unset($jobvalue['cronnextrun'], $jobvalue['fileprefix'], $jobvalue['fileformart'], $jobvalue['scheduleintervaltype'], $jobvalue['scheduleintervalteimes'], $jobvalue['scheduleinterval'], $jobvalue['dropemail'], $jobvalue['dropepass'], $jobvalue['dropesignmethod']); + //save in options + foreach ($jobvalue as $jobvaluename => $jobvaluevalue) { + BackWPup_Option::update($jobvalue['jobid'], $jobvaluename, $jobvaluevalue); + } + } + } - wp_clear_scheduled_hook( 'backwpup_cron' ); - $activejobs = BackWPup_Option::get_job_ids( 'activetype', 'wpcron' ); - if ( ! empty( $activejobs ) ) { - foreach ( $activejobs as $id ) { - wp_clear_scheduled_hook( 'backwpup_cron', array( 'arg' => $id ) ); - } - } - wp_clear_scheduled_hook( 'backwpup_check_cleanup' ); + /** + * Cleanup on Plugin deactivation. + */ + public static function deactivate() + { + wp_clear_scheduled_hook('backwpup_cron'); + $activejobs = BackWPup_Option::get_job_ids('activetype', 'wpcron'); + if (!empty($activejobs)) { + foreach ($activejobs as $id) { + wp_clear_scheduled_hook('backwpup_cron', ['arg' => $id]); + } + } + wp_clear_scheduled_hook('backwpup_check_cleanup'); - $activejobs = BackWPup_Option::get_job_ids( 'activetype', 'easycron' ); - if ( ! empty( $activejobs ) ) { - foreach ( $activejobs as $id ) { - BackWPup_EasyCron::delete( $id ); - } - } - - } + $activejobs = BackWPup_Option::get_job_ids('activetype', 'easycron'); + if (!empty($activejobs)) { + foreach ($activejobs as $id) { + BackWPup_EasyCron::delete($id); + } + } + } } diff --git a/inc/class-job.php b/inc/class-job.php index 8680a2ec..7fb7bb21 100755 --- a/inc/class-job.php +++ b/inc/class-job.php @@ -1,2083 +1,2257 @@ 'backwpupjobs' ), network_admin_url( 'admin.php' ) ) ); - echo ' '; - flush(); - if ( $level = ob_get_level() ) { - for ( $i = 0; $i < $level; $i ++ ) { - ob_end_clean(); - } - } - } - - // Should be preventing doubled running job's on http requests - $random = mt_rand( 10, 90 ) * 10000; - usleep( $random ); - - // Check running job. - $backwpup_job_object = self::get_working_data(); - // Start class. - $starttype_exists = in_array( $starttype, array( 'runnow', 'runnowalt', 'runext', 'cronrun' ), true ); - if ( ! $backwpup_job_object && $starttype_exists && $jobid ) { - // Schedule restart event. - wp_schedule_single_event( time() + 60, 'backwpup_cron', array( 'arg' => 'restart' ) ); - // Sstart job. - $backwpup_job_object = new self(); - $backwpup_job_object->create( $starttype, $jobid ); - } - if ( $backwpup_job_object ) { - $backwpup_job_object->run(); - } - } - - /** - * - * Get data off a working job - * - * @return bool|object BackWPup_Job Object or Bool if file not exits - */ - public static function get_working_data() { - - clearstatcache( true, BackWPup::get_plugin_data( 'running_file' ) ); - - if ( ! file_exists( BackWPup::get_plugin_data( 'running_file' ) ) ) { - return false; - } - - $file_data = file_get_contents( BackWPup::get_plugin_data( 'running_file' ), false, null, 8 ); - if ( empty( $file_data ) ) { - return false; - } - - if ( $job_object = unserialize( $file_data ) ) { - if ( $job_object instanceof BackWPup_Job ) { - return $job_object; - } - } - - return false; - - } - - /** - * - * This starts or restarts the job working - * - * @param string $start_type Start types are 'runnow', 'runnowalt', 'cronrun', 'runext', 'runcli' - * @param array|int $job_id The id of job of a job to start - */ - private function create( $start_type, $job_id = 0 ) { - - global $wpdb; - /* @var wpdb $wpdb */ - - //check startype - if ( ! in_array( $start_type, array( 'runnow', 'runnowalt', 'cronrun', 'runext', 'runcli' ), true ) ) { - return; - } - - if ( $job_id ) { - $this->job = BackWPup_Option::get_job( $job_id ); - } else { - return; - } - - $this->start_time = current_time( 'timestamp' ); - $this->lastmsg = __( 'Starting job', 'backwpup' ); - //set Logfile - $log_folder = get_site_option( 'backwpup_cfg_logfolder' ); - $log_folder = BackWPup_File::get_absolute_path( $log_folder ); - $this->logfile = $log_folder . 'backwpup_log_' . BackWPup::get_generated_hash( 6 ) . '_' . date( 'Y-m-d_H-i-s', - current_time( 'timestamp' ) ) . '.html'; - //write settings to job - BackWPup_Option::update( $this->job['jobid'], 'lastrun', $this->start_time ); - BackWPup_Option::update( $this->job['jobid'], 'logfile', $this->logfile ); //Set current logfile - BackWPup_Option::update( $this->job['jobid'], 'lastbackupdownloadurl', '' ); - //Set needed job values - $this->timestamp_last_update = microtime( true ); - $this->exclude_from_backup = explode( ',', trim( $this->job['fileexclude'] ) ); - $this->exclude_from_backup = array_unique( $this->exclude_from_backup ); - //setup job steps - $this->steps_data['CREATE']['CALLBACK'] = ''; - $this->steps_data['CREATE']['NAME'] = __( 'Job Start', 'backwpup' ); - $this->steps_data['CREATE']['STEP_TRY'] = 0; - //ADD Job types file - /* @var $job_type_class BackWPup_JobTypes */ - $job_need_dest = false; - if ( $job_types = BackWPup::get_job_types() ) { - foreach ( $job_types as $id => $job_type_class ) { - if ( in_array( $id, $this->job['type'], true ) && $job_type_class->creates_file() ) { - $this->steps_todo[] = 'JOB_' . $id; - $this->steps_data[ 'JOB_' . $id ]['NAME'] = $job_type_class->info['description']; - $this->steps_data[ 'JOB_' . $id ]['STEP_TRY'] = 0; - $this->steps_data[ 'JOB_' . $id ]['SAVE_STEP_TRY'] = 0; - $job_need_dest = true; - } - } - } - //add destinations and create archive if a job where files to backup - if ( $job_need_dest ) { - //Create manifest file - $this->steps_todo[] = 'CREATE_MANIFEST'; - $this->steps_data['CREATE_MANIFEST']['NAME'] = __( 'Creates manifest file', 'backwpup' ); - $this->steps_data['CREATE_MANIFEST']['STEP_TRY'] = 0; - $this->steps_data['CREATE_MANIFEST']['SAVE_STEP_TRY'] = 0; - //Add archive creation and backup filename on backup type archive - if ( $this->job['backuptype'] == 'archive' ) { - //get Backup folder if destination folder set - if ( in_array( 'FOLDER', $this->job['destinations'], true ) ) { - $this->backup_folder = $this->job['backupdir']; - //check backup folder - if ( ! empty( $this->backup_folder ) ) { - $this->backup_folder = BackWPup_File::get_absolute_path( $this->backup_folder ); - $this->job['backupdir'] = $this->backup_folder; - } - } - //set temp folder to backup folder if not set because we need one - if ( ! $this->backup_folder || $this->backup_folder == '/' ) { - $this->backup_folder = BackWPup::get_plugin_data( 'TEMP' ); - } - //Create backup archive full file name - $this->backup_file = $this->generate_filename( $this->job['archivename'], $this->job['archiveformat'] ); - //add archive create - $this->steps_todo[] = 'CREATE_ARCHIVE'; - $this->steps_data['CREATE_ARCHIVE']['NAME'] = __( 'Creates archive', 'backwpup' ); - $this->steps_data['CREATE_ARCHIVE']['STEP_TRY'] = 0; - $this->steps_data['CREATE_ARCHIVE']['SAVE_STEP_TRY'] = 0; - // Encrypt archive - if ( BackWPup_Option::get( $this->job['jobid'], 'archiveencryption' ) ) { - $this->steps_todo[] = 'ENCRYPT_ARCHIVE'; - $this->steps_data['ENCRYPT_ARCHIVE']['NAME'] = __( 'Encrypts the archive', 'backwpup' ); - $this->steps_data['ENCRYPT_ARCHIVE']['STEP_TRY'] = 0; - $this->steps_data['ENCRYPT_ARCHIVE']['SAVE_STEP_TRY'] = 0; - } - } - //ADD Destinations - /* @var BackWPup_Destinations $dest_class */ - foreach ( BackWPup::get_registered_destinations() as $id => $dest ) { - if ( ! in_array( $id, $this->job['destinations'], true ) || empty( $dest['class'] ) ) { - continue; - } - $dest_class = BackWPup::get_destination( $id ); - if ( $dest_class->can_run( $this->job ) ) { - if ( $this->job['backuptype'] == 'sync' ) { - if ( $dest['can_sync'] ) { - $this->steps_todo[] = 'DEST_SYNC_' . $id; - $this->steps_data[ 'DEST_SYNC_' . $id ]['NAME'] = $dest['info']['description']; - $this->steps_data[ 'DEST_SYNC_' . $id ]['STEP_TRY'] = 0; - $this->steps_data[ 'DEST_SYNC_' . $id ]['SAVE_STEP_TRY'] = 0; - } - } else { - $this->steps_todo[] = 'DEST_' . $id; - $this->steps_data[ 'DEST_' . $id ]['NAME'] = $dest['info']['description']; - $this->steps_data[ 'DEST_' . $id ]['STEP_TRY'] = 0; - $this->steps_data[ 'DEST_' . $id ]['SAVE_STEP_TRY'] = 0; - } - } - } - } - //ADD Job type no file - if ( $job_types = BackWPup::get_job_types() ) { - foreach ( $job_types as $id => $job_type_class ) { - if ( in_array( $id, $this->job['type'], true ) && ! $job_type_class->creates_file() ) { - $this->steps_todo[] = 'JOB_' . $id; - $this->steps_data[ 'JOB_' . $id ]['NAME'] = $job_type_class->info['description']; - $this->steps_data[ 'JOB_' . $id ]['STEP_TRY'] = 0; - $this->steps_data[ 'JOB_' . $id ]['SAVE_STEP_TRY'] = 0; - } - } - } - $this->steps_todo[] = 'END'; - $this->steps_data['END']['NAME'] = __( 'End of Job', 'backwpup' ); - $this->steps_data['END']['STEP_TRY'] = 1; - //must write working data - $this->write_running_file(); - - //set log level - $this->log_level = get_site_option( 'backwpup_cfg_loglevel', 'normal_translated' ); - if ( ! in_array( $this->log_level, - array( - 'normal_translated', - 'normal', - 'debug_translated', - 'debug', - ), - true ) ) { - $this->log_level = 'normal_translated'; - } - //create log file - $head = ''; - $info = ''; - $head .= "" . PHP_EOL; - $head .= "" . PHP_EOL; - $head .= "" . PHP_EOL; - $head .= "" . PHP_EOL; - $head .= "" . sprintf( __( 'BackWPup log for %1$s from %2$s at %3$s', 'backwpup' ), - $this->job['name'], - date_i18n( get_option( 'date_format' ) ), - date_i18n( get_option( 'time_format' ) ) ) . "" . PHP_EOL; - $head .= "" . PHP_EOL; - $head .= "" . PHP_EOL; - $head .= "" . PHP_EOL; - $head .= "" . PHP_EOL; - $head .= "" . PHP_EOL; - $head .= "" . PHP_EOL; - $head .= "" . PHP_EOL; - $head .= str_pad( '', 100 ) . PHP_EOL; - $head .= str_pad( '', 100 ) . PHP_EOL; - $head .= "job['jobid'] . "\" />" . PHP_EOL; - $head .= "job['name'] ) . "\" />" . PHP_EOL; - $head .= "job['type'] ) . "\" />" . PHP_EOL; - $head .= str_pad( '', 100 ) . PHP_EOL; - $head .= str_pad( '', 100 ) . PHP_EOL; - $head .= '' . PHP_EOL; - $head .= '' . PHP_EOL; - $info .= sprintf( _x( '[INFO] %1$s %2$s; A project of Inpsyde GmbH', - 'Plugin name; Plugin Version; plugin url', - 'backwpup' ), - BackWPup::get_plugin_data( 'name' ), - BackWPup::get_plugin_data( 'Version' ), - __( 'http://backwpup.com', 'backwpup' ) ) . '
' . PHP_EOL; - $info .= sprintf( _x( '[INFO] WordPress %1$s on %2$s', 'WordPress Version; Blog url', 'backwpup' ), - BackWPup::get_plugin_data( 'wp_version' ), - esc_attr( site_url( '/' ) ) ) . '
' . PHP_EOL; - $level = __( 'Normal', 'backwpup' ); - $translated = ''; - if ( $this->is_debug() ) { - $level = __( 'Debug', 'backwpup' ); - } - if ( is_textdomain_loaded( 'backwpup' ) ) { - $translated = __( '(translated)', 'backwpup' ); - } - $info .= sprintf( __( '[INFO] Log Level: %1$s %2$s', 'backwpup' ), $level, $translated ) . '
' . PHP_EOL; - $job_name = esc_attr( $this->job['name'] ); - if ( $this->is_debug() ) { - $job_name .= '; ' . implode( '+', $this->job['type'] ); - } - $info .= sprintf( __( '[INFO] BackWPup job: %1$s', 'backwpup' ), $job_name ) . '
' . PHP_EOL; - if ( $this->is_debug() ) { - $current_user = wp_get_current_user(); - $info .= sprintf( __( '[INFO] Runs with user: %1$s (%2$d) ', 'backwpup' ), - $current_user->user_login, - $current_user->ID ) . '
' . PHP_EOL; - } - if ( $this->job['activetype'] === 'wpcron' ) { - //check next run - $cron_next = wp_next_scheduled( 'backwpup_cron', array( 'arg' => $this->job['jobid'] ) ); - if ( ! $cron_next || $cron_next < time() ) { - wp_unschedule_event( $cron_next, 'backwpup_cron', array( 'arg' => $this->job['jobid'] ) ); - $cron_next = BackWPup_Cron::cron_next( $this->job['cron'] ); - wp_schedule_single_event( $cron_next, 'backwpup_cron', array( 'arg' => $this->job['jobid'] ) ); - $cron_next = wp_next_scheduled( 'backwpup_cron', array( 'arg' => $this->job['jobid'] ) ); - } - //output scheduling - if ( $this->is_debug() ) { - if ( ! $cron_next ) { - $cron_next = __( 'Not scheduled!', 'backwpup' ); - } else { - $cron_next = date_i18n( 'D, j M Y @ H:i', - $cron_next + ( get_option( 'gmt_offset' ) * 3600 ), - true ); - } - $info .= sprintf( __( '[INFO] Cron: %s; Next: %s ', 'backwpup' ), - $this->job['cron'], - $cron_next ) . '
' . PHP_EOL; - } - } elseif ( $this->job['activetype'] == 'link' && $this->is_debug() ) { - $info .= __( '[INFO] BackWPup job start with link is active', 'backwpup' ) . '
' . PHP_EOL; - } elseif ( $this->job['activetype'] == 'easycron' && $this->is_debug() ) { - $info .= __( '[INFO] BackWPup job start with EasyCron.com', 'backwpup' ) . '
' . PHP_EOL; - //output scheduling - if ( $this->is_debug() ) { - $cron_next = BackWPup_Cron::cron_next( $this->job['cron'] ); - $cron_next = date_i18n( 'D, j M Y @ H:i', $cron_next + ( get_option( 'gmt_offset' ) * 3600 ), true ); - $info .= sprintf( __( '[INFO] Cron: %s; Next: %s ', 'backwpup' ), - $this->job['cron'], - $cron_next ) . '
' . PHP_EOL; - } - } elseif ( $this->is_debug() ) { - $info .= __( '[INFO] BackWPup no automatic job start configured', 'backwpup' ) . '
' . PHP_EOL; - } - if ( $this->is_debug() ) { - if ( $start_type == 'cronrun' ) { - $info .= __( '[INFO] BackWPup job started from wp-cron', 'backwpup' ) . '
' . PHP_EOL; - } elseif ( $start_type == 'runnow' || $start_type == 'runnowalt' ) { - $info .= __( '[INFO] BackWPup job started manually', 'backwpup' ) . '
' . PHP_EOL; - } elseif ( $start_type == 'runext' ) { - $info .= __( '[INFO] BackWPup job started from external url', 'backwpup' ) . '
' . PHP_EOL; - } elseif ( $start_type == 'runcli' ) { - $info .= __( '[INFO] BackWPup job started form commandline interface', - 'backwpup' ) . '
' . PHP_EOL; - } - $bit = ''; - if ( PHP_INT_SIZE === 4 ) { - $bit = ' (32bit)'; - } - if ( PHP_INT_SIZE === 8 ) { - $bit = ' (64bit)'; - } - $info .= __( '[INFO] PHP ver.:', - 'backwpup' ) . ' ' . PHP_VERSION . $bit . '; ' . PHP_SAPI . '; ' . PHP_OS . '
' . PHP_EOL; - $info .= sprintf( __( '[INFO] Maximum PHP script execution time is %1$d seconds', 'backwpup' ), - ini_get( 'max_execution_time' ) ) . '
' . PHP_EOL; - if ( php_sapi_name() != 'cli' ) { - $job_max_execution_time = get_site_option( 'backwpup_cfg_jobmaxexecutiontime' ); - if ( ! empty( $job_max_execution_time ) ) { - $info .= sprintf( __( '[INFO] Script restart time is configured to %1$d seconds', 'backwpup' ), - $job_max_execution_time ) . '
' . PHP_EOL; - } - } - $info .= sprintf( __( '[INFO] MySQL ver.: %s', 'backwpup' ), - $wpdb->get_var( "SELECT VERSION() AS version" ) ) . '
' . PHP_EOL; - if ( isset( $_SERVER['SERVER_SOFTWARE'] ) ) { - $info .= sprintf( __( '[INFO] Web Server: %s', 'backwpup' ), - $_SERVER['SERVER_SOFTWARE'] ) . '
' . PHP_EOL; - } - if ( function_exists( 'curl_init' ) ) { - $curlversion = curl_version(); - $info .= sprintf( __( '[INFO] curl ver.: %1$s; %2$s', 'backwpup' ), - $curlversion['version'], - $curlversion['ssl_version'] ) . '
' . PHP_EOL; - } - $info .= sprintf( __( '[INFO] Temp folder is: %s', 'backwpup' ), - BackWPup::get_plugin_data( 'TEMP' ) ) . '
' . PHP_EOL; - } - if ( $this->is_debug() ) { - $logfile = $this->logfile; - } else { - $logfile = basename( $this->logfile ); - } - $info .= sprintf( __( '[INFO] Logfile is: %s', 'backwpup' ), $logfile ) . '
' . PHP_EOL; - if ( ! empty( $this->backup_file ) && $this->job['backuptype'] === 'archive' ) { - if ( $this->is_debug() ) { - $backupfile = $this->backup_folder . $this->backup_file; - } else { - $backupfile = $this->backup_file; - } - $info .= sprintf( __( '[INFO] Backup file is: %s', 'backwpup' ), $backupfile ) . '
' . PHP_EOL; - } else { - $info .= sprintf( __( '[INFO] Backup type is: %s', 'backwpup' ), - $this->job['backuptype'] ) . '
' . PHP_EOL; - } - //output info on cli - if ( php_sapi_name() == 'cli' && defined( 'STDOUT' ) ) { - fwrite( STDOUT, strip_tags( $info ) ); - } - if ( ! file_put_contents( $this->logfile, $head . $info, FILE_APPEND ) ) { - $this->logfile = ''; - $this->log( __( 'Could not write log file', 'backwpup' ), E_USER_ERROR ); - } - //test for destinations - if ( $job_need_dest ) { - $desttest = false; - foreach ( $this->steps_todo as $deststeptest ) { - if ( substr( $deststeptest, 0, 5 ) == 'DEST_' ) { - $desttest = true; - break; - } - } - if ( ! $desttest ) { - $this->log( __( 'No destination correctly defined for backup! Please correct job settings.', - 'backwpup' ), - E_USER_ERROR ); - $this->steps_todo = array( 'END' ); - } - } - //test backup folder - if ( ! empty( $this->backup_folder ) ) { - $folder_message = BackWPup_File::check_folder( $this->backup_folder, true ); - if ( ! empty( $folder_message ) ) { - $this->log( $folder_message, E_USER_ERROR ); - $this->steps_todo = array( 'END' ); - } - } - - //Set start as done - $this->steps_done[] = 'CREATE'; - } - - /** - * @param $name - * @param string $suffix - * @param bool $delete_temp_file - * - * @return string - */ - public function generate_filename( $name, $suffix = '', $delete_temp_file = true ) { - - if ( $suffix ) { - $suffix = '.' . trim( $suffix, '. ' ); - } - - $name = BackWPup_Option::substitute_date_vars( $name ); - $name .= $suffix; - if ( $delete_temp_file && is_writeable( BackWPup::get_plugin_data( 'TEMP' ) . $name ) && ! is_dir( BackWPup::get_plugin_data( 'TEMP' ) . $name ) && ! is_link( BackWPup::get_plugin_data( 'TEMP' ) . $name ) ) { - unlink( BackWPup::get_plugin_data( 'TEMP' ) . $name ); - } - - return $name; - } - - /** - * Sanitizes a filename, replacing whitespace with underscores. - * - * @param $filename - * - * @return mixed - */ - public static function sanitize_file_name( $filename ) { - - $filename = trim( $filename ); - - $special_chars = array( - "?", - "[", - "]", - "/", - "\\", - "=", - "<", - ">", - ":", - ";", - ",", - "'", - "\"", - "&", - "$", - "#", - "*", - "(", - ")", - "|", - "~", - "`", - "!", - "{", - "}", - chr( 0 ), - ); - - $filename = str_replace( $special_chars, '', $filename ); - - $filename = str_replace( array( ' ', '%20', '+' ), '_', $filename ); - $filename = str_replace( array( "\n", "\t", "\r" ), '-', $filename ); - $filename = trim( $filename, '.-_' ); - - return $filename; - } - - private function write_running_file() { - - $clone = clone $this; - $data = 'log( __( 'Cannot write progress to working file. Job will be aborted.', 'backwpup' ), E_USER_ERROR ); - } - } - - /** - * Write messages to log file - * - * @param string $message the error message - * @param int $type the error number (E_USER_ERROR,E_USER_WARNING,E_USER_NOTICE, ...) - * @param string $file the full path of file with error (__FILE__) - * @param int $line the line in that is the error (__LINE__) - * - * @return bool true - */ - public function log( $message, $type = E_USER_NOTICE, $file = '', $line = 0 ) { - - // if error has been suppressed with an @ - if ( error_reporting() == 0 ) { - return true; - } - - //if first the type an second the message switch it on user errors - if ( ! is_int( $type ) && is_int( $message ) && in_array( $message, - array( - 1, - 2, - 4, - 8, - 16, - 32, - 64, - 128, - 256, - 512, - 1024, - 2048, - 4096, - 8192, - 16384, - ), - true ) - ) { - $temp = $message; - $message = $type; - $type = $temp; - } - - //json message if array or object - if ( is_array( $message ) || is_object( $message ) ) { - $message = json_encode( $message ); - } - - //if not set line and file get it - if ( $this->is_debug() ) { - if ( empty( $file ) || empty( $line ) ) { - $debug_info = debug_backtrace(); - $file = $debug_info[0]['file']; - $line = $debug_info[0]['line']; - } - } - - $error = false; - $warning = false; - - switch ( $type ) { - case E_NOTICE: - case E_USER_NOTICE: - break; - case E_WARNING: - case E_CORE_WARNING: - case E_COMPILE_WARNING: - case E_USER_WARNING: - $this->warnings ++; - $warning = true; - $message = __( 'WARNING:', 'backwpup' ) . ' ' . $message; - break; - case E_ERROR: - case E_PARSE: - case E_CORE_ERROR: - case E_COMPILE_ERROR: - case E_USER_ERROR: - $this->errors ++; - $error = true; - $message = __( 'ERROR:', 'backwpup' ) . ' ' . $message; - break; - case E_DEPRECATED: - case E_USER_DEPRECATED: - $message = __( 'DEPRECATED:', 'backwpup' ) . ' ' . $message; - break; - case E_STRICT: - $message = __( 'STRICT NOTICE:', 'backwpup' ) . ' ' . $message; - break; - case E_RECOVERABLE_ERROR: - $this->errors ++; - $error = true; - $message = __( 'RECOVERABLE ERROR:', 'backwpup' ) . ' ' . $message; - break; - default: - $message = $type . ': ' . $message; - break; - } - - //print message to cli - if ( defined( 'WP_CLI' ) && WP_CLI ) { - $output_message = str_replace( array( '…', ' ' ), array( '...', ' ' ), esc_html( $message ) ); - if ( ! call_user_func( array( '\cli\Shell', 'isPiped' ) ) ) { - if ( $error ) { - $output_message = '%r' . $output_message . '%n'; - } - if ( $warning ) { - $output_message = '%y' . $output_message . '%n'; - } - $output_message = call_user_func( array( '\cli\Colors', 'colorize' ), $output_message, true ); - } - WP_CLI::line( $output_message ); - } elseif ( php_sapi_name() == 'cli' && defined( 'STDOUT' ) ) { - $output_message = str_replace( array( '…', ' ' ), - array( - '...', - ' ', - ), - esc_html( $message ) ) . PHP_EOL; - fwrite( STDOUT, $output_message ); - } - - //timestamp for log file - $debug_info = ''; - if ( $this->is_debug() ) { - $debug_info = ' title="[Type: ' . $type . '|Line: ' . $line . '|File: ' . $this->get_destination_path_replacement( $file ) . '|Mem: ' . size_format( @memory_get_usage( true ), - 2 ) . '|Mem Max: ' . size_format( @memory_get_peak_usage( true ), - 2 ) . '|Mem Limit: ' . ini_get( 'memory_limit' ) . '|PID: ' . self::get_pid() . ' | UniqID: ' . $this->uniqid . '|Queries: ' . get_num_queries() . ']"'; - } - $timestamp = '[' . date( 'd-M-Y H:i:s', - current_time( 'timestamp' ) ) . '] '; - - //set last Message - if ( $error ) { - $output_message = '' . esc_html( $message ) . ''; - $this->lasterrormsg = $output_message; - } elseif ( $warning ) { - $output_message = '' . esc_html( $message ) . ''; - $this->lasterrormsg = $output_message; - } else { - $output_message = esc_html( $message ); - $this->lastmsg = $output_message; - } - //write log file - if ( $this->logfile ) { - if ( ! file_put_contents( $this->logfile, - $timestamp . $output_message . '
' . PHP_EOL, - FILE_APPEND ) ) { - $this->logfile = ''; - restore_error_handler(); - trigger_error( esc_html( $message ), $type ); - } - - //write new log header - if ( ( $error || $warning ) && $this->logfile ) { - if ( $fd = fopen( $this->logfile, 'r+' ) ) { - $file_pos = ftell( $fd ); - while ( ! feof( $fd ) ) { - $line = fgets( $fd ); - if ( $error && stripos( $line, '', - 100 ) . PHP_EOL ); - break; - } - if ( $warning && stripos( $line, '', - 100 ) . PHP_EOL ); - break; - } - $file_pos = ftell( $fd ); - } - fclose( $fd ); - } - } - } - - //write working data - $this->update_working_data( $error || $warning ); - - //true for no more php error handling. - return true; - } - - /** - * Is debug log active - * - * @return bool - */ - public function is_debug() { - - return strstr( $this->log_level, 'debug' ) ? true : false; - } - - /** - * Change path of a given path - * for better storing in archives or on sync destinations - * - * @param $path string path to change to wp default path - * - * @return string - */ - public function get_destination_path_replacement( $path ) { - - $abs_path = realpath( BackWPup_Path_Fixer::fix_path( ABSPATH ) ); - if ( $this->job['backupabsfolderup'] ) { - $abs_path = dirname( $abs_path ); - } - $abs_path = trailingslashit( str_replace( '\\', '/', $abs_path ) ); - - $path = str_replace( array( '\\', $abs_path ), '/', $path ); - - //replace the colon from windows drive letters with so they will not be problems with them in archives or on copying to directory - if ( 0 === stripos( PHP_OS, 'WIN' ) && 1 === strpos( $path, ':/' ) ) { - $path = '/' . substr_replace( $path, '', 1,1 ); - } - - return $path; - } - - /** - * Get the Process id of working script - * - * @return int - */ - private static function get_pid() { - - if ( function_exists( 'posix_getpid' ) ) { - - return posix_getpid(); - } elseif ( function_exists( 'getmypid' ) ) { - - return getmypid(); - } - - return - 1; - } - - /** - * - * Write the Working data to display the process or that i can executes again - * The write will only done every second - * - * @param bool $must - */ - public function update_working_data( $must = false ) { - - global $wpdb; - - //to reduce server load - if ( get_site_option( 'backwpup_cfg_jobwaittimems' ) > 0 && get_site_option( 'backwpup_cfg_jobwaittimems' ) <= 500000 ) { - usleep( get_site_option( 'backwpup_cfg_jobwaittimems' ) ); - } - - //check free memory - $this->need_free_memory( '10M' ); - - //only run every 1 sec. - $time_to_update = microtime( true ) - $this->timestamp_last_update; - if ( $time_to_update < 1 && ! $must ) { - return; - } - - //FCGI must have a permanent output so that it not broke - if ( get_site_option( 'backwpup_cfg_jobdooutput' ) && ! defined( 'STDOUT' ) ) { - echo str_repeat( ' ', 12 ); - flush(); - } - - // check WPDB connection. WP will do it after a query that will cause messages. - $wpdb->check_connection( false ); - - //set execution time again for 5 min - @set_time_limit( 300 ); - - //calc sub step percent - if ( $this->substeps_todo > 0 && $this->substeps_done > 0 ) { - $this->substep_percent = min( round( $this->substeps_done / $this->substeps_todo * 100 ), 100 ); - } else { - $this->substep_percent = 1; - } - - //check if job aborted - if ( ! file_exists( BackWPup::get_plugin_data( 'running_file' ) ) ) { - if ( $this->step_working !== 'END' ) { - $this->end(); - } - } else { - $this->timestamp_last_update = microtime( true ); //last update of working file - $this->write_running_file(); - } - - if ( $this->signal !== 0 ) { - $this->do_restart(); - } - } - - /** - * - * Increase automatically the memory that is needed - * - * @param int|string $memneed of the needed memory - */ - public function need_free_memory( $memneed ) { - - //need memory - $needmemory = @memory_get_usage( true ) + self::convert_hr_to_bytes( $memneed ); - // increase Memory - if ( $needmemory > self::convert_hr_to_bytes( ini_get( 'memory_limit' ) ) ) { - $newmemory = round( $needmemory / 1024 / 1024 ) + 1 . 'M'; - if ( $needmemory >= 1073741824 ) { - $newmemory = round( $needmemory / 1024 / 1024 / 1024 ) . 'G'; - } - @ini_set( 'memory_limit', $newmemory ); - } - } - - /** - * - * Converts hr to bytes - * - * @param $size - * - * @return int - */ - public static function convert_hr_to_bytes( $size ) { - - $size = strtolower( $size ); - $bytes = (int) $size; - if ( strpos( $size, 'k' ) !== false ) { - $bytes = intval( $size ) * 1024; - } elseif ( strpos( $size, 'm' ) !== false ) { - $bytes = intval( $size ) * 1024 * 1024; - } elseif ( strpos( $size, 'g' ) !== false ) { - $bytes = intval( $size ) * 1024 * 1024 * 1024; - } - - return $bytes; - } - - /** - * - * Called on job stop makes cleanup and terminates the script - * - */ - private function end() { - - $this->step_working = 'END'; - $this->substeps_todo = 1; - - if ( ! file_exists( BackWPup::get_plugin_data( 'running_file' ) ) ) { - $this->log( __( 'Aborted by user!', 'backwpup' ), E_USER_ERROR ); - } - - //delete old logs - if ( get_site_option( 'backwpup_cfg_maxlogs' ) ) { - $log_file_list = array(); - $log_folder = trailingslashit( dirname( $this->logfile ) ); - if ( is_readable( $log_folder ) ) { //make file list - try { - $dir = new BackWPup_Directory( $log_folder ); - - foreach ( $dir as $file ) { - if ( ! $file->isDot() && strpos( $file->getFilename(), - 'backwpup_log_' ) === 0 && strpos( $file->getFilename(), '.html' ) !== false ) { - $log_file_list[ $file->getMTime() ] = clone $file; - } - } - } catch ( UnexpectedValueException $e ) { - $this->log( sprintf( __( "Could not open path: %s" ), $e->getMessage() ), E_USER_WARNING ); - } - } - if ( count( $log_file_list ) > 0 ) { - krsort( $log_file_list, SORT_NUMERIC ); - $num_delete_files = 0; - $i = - 1; - foreach ( $log_file_list as $log_file ) { - $i ++; - if ( $i < get_site_option( 'backwpup_cfg_maxlogs' ) ) { - continue; - } - unlink( $log_file->getPathname() ); - $num_delete_files ++; - } - if ( $num_delete_files > 0 ) { - $this->log( sprintf( _n( 'One old log deleted', - '%d old logs deleted', - $num_delete_files, - 'backwpup' ), - $num_delete_files ) ); - } - } - } - - //Display job working time - if ( $this->errors > 0 ) { - $this->log( sprintf( __( 'Job has ended with errors in %s seconds. You must resolve the errors for correct execution.', - 'backwpup' ), - current_time( 'timestamp' ) - $this->start_time ), - E_USER_ERROR ); - } elseif ( $this->warnings > 0 ) { - $this->log( sprintf( __( 'Job finished with warnings in %s seconds. Please resolve them for correct execution.', - 'backwpup' ), - current_time( 'timestamp' ) - $this->start_time ), - E_USER_WARNING ); - } else { - $this->log( sprintf( __( 'Job done in %s seconds.', 'backwpup' ), - current_time( 'timestamp' ) - $this->start_time ) ); - } - - //Update job options - $this->job['lastruntime'] = current_time( 'timestamp' ) - $this->start_time; - BackWPup_Option::update( $this->job['jobid'], 'lastruntime', $this->job['lastruntime'] ); - - - //write header info - if ( ! empty( $this->logfile ) ) { - - if ( $fd = fopen( $this->logfile, 'r+' ) ) { - $filepos = ftell( $fd ); - $found = 0; - while ( ! feof( $fd ) ) { - $line = fgets( $fd ); - if ( stripos( $line, '', - 100 ) . PHP_EOL ); - $found ++; - } - if ( stripos( $line, '', - 100 ) . PHP_EOL ); - $found ++; - } - if ( $found >= 2 ) { - break; - } - $filepos = ftell( $fd ); - } - fclose( $fd ); - } - - //Send mail with log - $sendmail = false; - if ( $this->job['mailaddresslog'] ) { - $sendmail = true; - } - if ( $this->errors === 0 && $this->job['mailerroronly'] ) { - $sendmail = false; - } - if ( $sendmail ) { - //special subject - $status = __( 'SUCCESSFUL', 'backwpup' ); - if ( $this->warnings > 0 ) { - $status = __( 'WARNING', 'backwpup' ); - } - if ( $this->errors > 0 ) { - $status = __( 'ERROR', 'backwpup' ); - } - - $subject = sprintf( __( '[%3$s] BackWPup log %1$s: %2$s', 'backwpup' ), - date_i18n( 'd-M-Y H:i', $this->start_time, true ), - esc_attr( $this->job['name'] ), - $status ); - $headers = array(); - $headers[] = 'Content-Type: text/html; charset=' . get_bloginfo( 'charset' ); - if ( $this->job['mailaddresssenderlog'] ) { - $this->job['mailaddresssenderlog'] = str_replace( array( '<', '>' ), - array( - '<', - '>', - ), - $this->job['mailaddresssenderlog'] ); - - $bracket_pos = strpos( $this->job['mailaddresssenderlog'], '<' ); - $at_pos = strpos( $this->job['mailaddresssenderlog'], '@' ); - if ( $bracket_pos === false || $at_pos === false ) { - $this->job['mailaddresssenderlog'] = str_replace( array( - '<', - '>', - ), - '', - $this->job['mailaddresssenderlog'] ) . ' <' . get_bloginfo( 'admin_email' ) . '>'; - } - - $headers[] = 'From: ' . $this->job['mailaddresssenderlog']; - } - wp_mail( $this->job['mailaddresslog'], $subject, file_get_contents( $this->logfile ), $headers ); - } - } - - //set done - $this->substeps_done = 1; - $this->steps_done[] = 'END'; - - //clean up temp - self::clean_temp_folder(); - - //remove shutdown action - remove_action( 'shutdown', array( $this, 'shutdown' ) ); - restore_exception_handler(); - restore_error_handler(); - - //logfile end - file_put_contents( $this->logfile, "" . PHP_EOL . "", FILE_APPEND ); - - BackWPup_Cron::check_cleanup(); - - exit(); - } - - /** - * Cleanup Temp Folder - */ - public static function clean_temp_folder() { - - $instance = new self(); - $temp_dir = BackWPup::get_plugin_data( 'TEMP' ); - $do_not_delete_files = array( '.htaccess', 'nginx.conf', 'index.php', '.', '..', '.donotbackup' ); - - if ( is_writable( $temp_dir ) ) { - try { - $dir = new BackWPup_Directory( $temp_dir ); - foreach ( $dir as $file ) { - if ( in_array( $file->getFilename(), - $do_not_delete_files, - true ) || $file->isDir() || $file->isLink() ) { - continue; - } - if ( $file->isWritable() ) { - unlink( $file->getPathname() ); - } - } - } catch ( UnexpectedValueException $e ) { - $instance->log( sprintf( __( "Could not open path: %s" ), $e->getMessage() ), E_USER_WARNING ); - } - } - } - - /** - * Do a job restart - * - * @param bool $must Restart must done - */ - public function do_restart( $must = false ) { - - //restart must done if signal - if ( $this->signal !== 0 ) { - $must = true; - } - - //no restart if in end step - if ( $this->step_working === 'END' || ( count( $this->steps_done ) + 1 ) >= count( $this->steps_todo ) ) { - return; - } - - //no restart on cli usage - if ( php_sapi_name() == 'cli' ) { - return; - } - - //no restart if no restart time configured - $job_max_execution_time = get_site_option( 'backwpup_cfg_jobmaxexecutiontime' ); - if ( ! $must && empty( $job_max_execution_time ) ) { - return; - } - - //no restart when restart was 3 Seconds before - $execution_time = microtime( true ) - $this->timestamp_script_start; - if ( ! $must && $execution_time < 3 ) { - return; - } - - //no restart if no working job - if ( ! file_exists( BackWPup::get_plugin_data( 'running_file' ) ) ) { - return; - } - - //print message - if ( $this->is_debug() ) { - if ( $execution_time !== 0 ) { - $this->log( sprintf( __( 'Restart after %1$d seconds.', 'backwpup' ), ceil( $execution_time ) ) ); - } elseif ( $this->signal !== 0 ) { - $this->log( __( 'Restart after getting signal.', 'backwpup' ) ); - } - } - - //do things for a clean restart - $this->pid = 0; - $this->uniqid = ''; - $this->write_running_file(); - remove_action( 'shutdown', array( $this, 'shutdown' ) ); - //do restart - wp_clear_scheduled_hook( 'backwpup_cron', array( 'arg' => 'restart' ) ); - wp_schedule_single_event( time() + 5, 'backwpup_cron', array( 'arg' => 'restart' ) ); - self::get_jobrun_url( 'restart' ); - - exit(); - } - - /** - * - * Get a url to run a job of BackWPup - * - * @param string $starttype Start types are 'runnow', 'runnowlink', 'cronrun', 'runext', 'restart', 'restartalt', - * 'test' - * @param int $jobid The id of job to start else 0 - * - * @return array|object [url] is the job url [header] for auth header or object form wp_remote_get() - */ - public static function get_jobrun_url( $starttype, $jobid = 0 ) { - - $authentication = get_site_option( 'backwpup_cfg_authentication', - array( - 'method' => '', - 'basic_user' => '', - 'basic_password' => '', - 'user_id' => 0, - 'query_arg' => '', - ) ); - $url = site_url( 'wp-cron.php' ); - $header = array( 'Cache-Control' => 'no-cache' ); - $authurl = ''; - $query_args = array( - '_nonce' => substr( wp_hash( wp_nonce_tick() . 'backwpup_job_run-' . $starttype, 'nonce' ), - 12, 10 ), - 'doing_wp_cron' => sprintf( '%.22F', microtime( true ) ), - ); - - if ( in_array( $starttype, array( 'restart', 'runnow', 'cronrun', 'runext', 'test' ), true ) ) { - $query_args['backwpup_run'] = $starttype; - } - - if ( in_array( $starttype, array( 'runnowlink', 'runnow', 'cronrun', 'runext' ), true ) && ! empty( $jobid ) ) { - $query_args['jobid'] = $jobid; - } - - if ( ! empty( $authentication['basic_user'] ) && ! empty( $authentication['basic_password'] ) && $authentication['method'] == 'basic' ) { - $header['Authorization'] = 'Basic ' . base64_encode( $authentication['basic_user'] . ':' . BackWPup_Encryption::decrypt( $authentication['basic_password'] ) ); - $authurl = urlencode( $authentication['basic_user'] ) . ':' . urlencode( BackWPup_Encryption::decrypt( $authentication['basic_password'] ) ) . '@'; - } - - if ( ! empty( $authentication['query_arg'] ) && $authentication['method'] == 'query_arg' ) { - $url .= '?' . $authentication['query_arg']; - } - - if ( $starttype === 'runext' ) { - $query_args['_nonce'] = get_site_option( 'backwpup_cfg_jobrunauthkey' ); - $query_args['doing_wp_cron'] = null; - if ( ! empty( $authurl ) ) { - $url = str_replace( 'https://', 'https://' . $authurl, $url ); - $url = str_replace( 'http://', 'http://' . $authurl, $url ); - } - } - - if ( $starttype === 'runnowlink' && ( ! defined( 'ALTERNATE_WP_CRON' ) || ! ALTERNATE_WP_CRON ) ) { - $url = wp_nonce_url( network_admin_url( 'admin.php' ), 'backwpup_job_run-' . $starttype ); - $query_args['page'] = 'backwpupjobs'; - $query_args['action'] = 'runnow'; - $query_args['doing_wp_cron'] = null; - unset( $query_args['_nonce'] ); - } - - if ( $starttype === 'runnowlink' && defined( 'ALTERNATE_WP_CRON' ) && ALTERNATE_WP_CRON ) { - $query_args['backwpup_run'] = 'runnowalt'; - $query_args['_nonce'] = substr( wp_hash( wp_nonce_tick() . 'backwpup_job_run-runnowalt', 'nonce' ), - - 12, - 10 ); - $query_args['doing_wp_cron'] = null; - } - - if ( $starttype === 'restartalt' && defined( 'ALTERNATE_WP_CRON' ) && ALTERNATE_WP_CRON ) { - $query_args['backwpup_run'] = 'restart'; - $query_args['_nonce'] = null; - } - - if ( $starttype === 'restart' || $starttype === 'test' ) { - $query_args['_nonce'] = null; - } - - if ( ! empty( $authentication['user_id'] ) && $authentication['method'] === 'user' ) { - //cache cookies for auth some - $cookies = get_site_transient( 'backwpup_cookies' ); - if ( empty( $cookies ) ) { - $wp_admin_user = get_users( array( 'role' => 'administrator', 'number' => 1 ) ); - if ( empty( $wp_admin_user ) ) { - $wp_admin_user = get_users( array( 'role' => 'backwpup_admin', 'number' => 1 ) ); - } - if ( ! empty( $wp_admin_user[0]->ID ) ) { - $expiration = time() + ( 2 * DAY_IN_SECONDS ); - $manager = WP_Session_Tokens::get_instance( $wp_admin_user[0]->ID ); - $token = $manager->create( $expiration ); - $cookies[ LOGGED_IN_COOKIE ] = wp_generate_auth_cookie( $wp_admin_user[0]->ID, - $expiration, - 'logged_in', - $token ); - } - set_site_transient( 'backwpup_cookies', $cookies, 2 * DAY_IN_SECONDS ); - } - } else { - $cookies = ''; - } - - $cron_request = array( - 'url' => add_query_arg( $query_args, $url ), - 'key' => $query_args['doing_wp_cron'], - 'args' => array( - 'blocking' => false, - 'sslverify' => false, - 'timeout' => 0.01, - 'headers' => $header, - 'user-agent' => BackWPup::get_plugin_data( 'User-Agent' ), - ), - ); - - if ( ! empty( $cookies ) ) { - foreach ( $cookies as $name => $value ) { - $cron_request['args']['cookies'][] = new WP_Http_Cookie( array( 'name' => $name, 'value' => $value ) ); - } - } - - $cron_request = apply_filters( 'cron_request', $cron_request ); - - if ( $starttype === 'test' ) { - $cron_request['args']['timeout'] = 15; - $cron_request['args']['blocking'] = true; - } - - if ( ! in_array( $starttype, array( 'runnowlink', 'runext', 'restartalt' ), true ) ) { - delete_transient( 'doing_cron' ); - - return wp_remote_post( $cron_request['url'], $cron_request['args'] ); - } - - return $cron_request; - } - - /** - * Run baby run - */ - public function run() { - - global $wpdb; - /* @var wpdb $wpdb */ - - //disable output buffering - if ( $level = ob_get_level() ) { - for ( $i = 0; $i < $level; $i ++ ) { - ob_end_clean(); - } - } - - // Job can't run it is not created - if ( empty( $this->steps_todo ) || empty( $this->logfile ) ) { - $running_file = BackWPup::get_plugin_data( 'running_file' ); - if ( file_exists( $running_file ) ) { - unlink( $running_file ); - } - - return; - } - - //Check double running and inactivity - $last_update = microtime( true ) - $this->timestamp_last_update; - if ( ! empty( $this->pid ) && $last_update > 300 ) { - $this->log( __( 'Job restarts due to inactivity for more than 5 minutes.', 'backwpup' ), E_USER_WARNING ); - } elseif ( ! empty( $this->pid ) ) { - return; - } - // set timestamp of script start - $this->timestamp_script_start = microtime( true ); - //set Pid - $this->pid = self::get_pid(); - $this->uniqid = uniqid( '', true ); - //Early write new working file - $this->write_running_file(); - if ( $this->is_debug() ) { - @ini_set( 'error_log', $this->logfile ); - error_reporting( - 1 ); - } - @ini_set( 'display_errors', '0' ); - @ini_set( 'log_errors', '1' ); - @ini_set( 'html_errors', '0' ); - @ini_set( 'report_memleaks', '1' ); - @ini_set( 'zlib.output_compression', '0' ); - @ini_set( 'implicit_flush', '0' ); - @putenv( 'TMPDIR=' . BackWPup::get_plugin_data( 'TEMP' ) ); - //Write Wordpress DB errors to log - $wpdb->suppress_errors( false ); - $wpdb->hide_errors(); - //set wp max memory limit - @ini_set( 'memory_limit', apply_filters( 'admin_memory_limit', WP_MAX_MEMORY_LIMIT ) ); - //set error handler - if ( ! empty( $this->logfile ) ) { - if ( $this->is_debug() ) { - set_error_handler( array( $this, 'log' ) ); - } else { - set_error_handler( array( $this, 'log' ), E_ALL ^ E_NOTICE ); - } - } - set_exception_handler( array( $this, 'exception_handler' ) ); - // execute function on job shutdown register_shutdown_function( array( $this, 'shutdown' ) ); - add_action( 'shutdown', array( $this, 'shutdown' ) ); - - if ( function_exists( 'pcntl_signal' ) ) { - $signals = array( - 'SIGHUP', //Term - 'SIGINT', //Term - 'SIGQUIT', //Core - 'SIGILL', //Core - //'SIGTRAP', //Core - 'SIGABRT', //Core - 'SIGBUS', //Core - 'SIGFPE', //Core - //'SIGKILL', //Term - 'SIGSEGV', //Core - //'SIGPIPE', Term - //'SIGALRM', Term - 'SIGTERM', //Term - 'SIGSTKFLT', //Term - 'SIGUSR1',//Term - 'SIGUSR2', //Term - //'SIGCHLD', //Ign - //'SIGCONT', //Cont - //'SIGSTOP', //Stop - //'SIGTSTP', //Stop - //'SIGTTIN', //Stop - //'SIGTTOU', //Stop - //'SIGURG', //Ign - 'SIGXCPU', //Core - 'SIGXFSZ', //Core - //'SIGVTALRM', //Term - //'SIGPROF', //Term - //'SIGWINCH', //Ign - //'SIGIO', //Term - 'SIGPWR', //Term - 'SIGSYS' //Core - ); - $signals = apply_filters( 'backwpup_job_signals_to_handel', $signals ); - declare( ticks=1 ); - $this->signal = 0; - foreach ( $signals as $signal ) { - if ( defined( $signal ) ) { - pcntl_signal( constant( $signal ), array( $this, 'signal_handler' ), false ); - } - } - } - $job_types = BackWPup::get_job_types(); - //go step by step - foreach ( $this->steps_todo as $this->step_working ) { - //Check if step already done - if ( in_array( $this->step_working, $this->steps_done, true ) ) { - continue; - } - //calc step percent - if ( count( $this->steps_done ) > 0 ) { - $this->step_percent = min( round( count( $this->steps_done ) / count( $this->steps_todo ) * 100 ), - 100 ); - } else { - $this->step_percent = 1; - } - // do step tries - while ( true ) { - if ( $this->steps_data[ $this->step_working ]['STEP_TRY'] >= get_site_option( 'backwpup_cfg_jobstepretry' ) ) { - $this->log( __( 'Step aborted: too many attempts!', 'backwpup' ), E_USER_ERROR ); - $this->temp = array(); - $this->steps_done[] = $this->step_working; - $this->substeps_done = 0; - $this->substeps_todo = 0; - $this->do_restart(); - break; - } - - $this->steps_data[ $this->step_working ]['STEP_TRY'] ++; - $done = false; - - //executes the methods of job process - if ( $this->step_working == 'CREATE_ARCHIVE' ) { - $done = $this->create_archive(); - } elseif ( $this->step_working == 'ENCRYPT_ARCHIVE' ) { - $done = $this->encrypt_archive(); - } elseif ( $this->step_working == 'CREATE_MANIFEST' ) { - $done = $this->create_manifest(); - } elseif ( $this->step_working == 'END' ) { - $this->end(); - break 2; - } elseif ( strstr( $this->step_working, 'JOB_' ) ) { - $done = $job_types[ str_replace( 'JOB_', '', $this->step_working ) ]->job_run( $this ); - } elseif ( strstr( $this->step_working, 'DEST_SYNC_' ) ) { - $done = BackWPup::get_destination( str_replace( 'DEST_SYNC_', '', $this->step_working ) ) - ->job_run_sync( $this ); - } elseif ( strstr( $this->step_working, 'DEST_' ) ) { - $done = BackWPup::get_destination( str_replace( 'DEST_', '', $this->step_working ) ) - ->job_run_archive( $this ); - } elseif ( ! empty( $this->steps_data[ $this->step_working ]['CALLBACK'] ) ) { - $done = $this->steps_data[ $this->step_working ]['CALLBACK']( $this ); - } - - // set step as done - if ( $done === true ) { - $this->temp = array(); - $this->steps_done[] = $this->step_working; - $this->substeps_done = 0; - $this->substeps_todo = 0; - $this->update_working_data( true ); - } - if ( count( $this->steps_done ) < count( $this->steps_todo ) - 1 ) { - $this->do_restart(); - } - if ( $done === true ) { - break; - } - } - } - } - - /** - * Creates the backup archive - */ - private function create_archive() { - - //load folders to backup - $folders_to_backup = $this->get_folders_to_backup(); - - $this->substeps_todo = $this->count_folder + 1; - - //initial settings for restarts in archiving - if ( ! isset( $this->steps_data[ $this->step_working ]['on_file'] ) ) { - $this->steps_data[ $this->step_working ]['on_file'] = ''; - } - if ( ! isset( $this->steps_data[ $this->step_working ]['on_folder'] ) ) { - $this->steps_data[ $this->step_working ]['on_folder'] = ''; - } - - if ( $this->steps_data[ $this->step_working ]['on_folder'] == '' && $this->steps_data[ $this->step_working ]['on_file'] == '' && is_file( $this->backup_folder . $this->backup_file ) ) { - unlink( $this->backup_folder . $this->backup_file ); - } - - if ( $this->steps_data[ $this->step_working ]['SAVE_STEP_TRY'] != $this->steps_data[ $this->step_working ]['STEP_TRY'] ) { - $this->log( sprintf( __( '%d. Trying to create backup archive …', 'backwpup' ), - $this->steps_data[ $this->step_working ]['STEP_TRY'] ), - E_USER_NOTICE ); - } - - try { - $backup_archive = new BackWPup_Create_Archive( $this->backup_folder . $this->backup_file ); - - //show method for creation - if ( $this->substeps_done == 0 ) { - $this->log( sprintf( _x( 'Compressing files as %s. Please be patient, this may take a moment.', - 'Archive compression method', - 'backwpup' ), - $backup_archive->get_method() ) ); - } - - //add extra files - if ( $this->substeps_done == 0 ) { - if ( ! empty( $this->additional_files_to_backup ) && $this->substeps_done == 0 ) { - if ( $this->is_debug() ) { - $this->log( __( 'Adding Extra files to Archive', 'backwpup' ) ); - } - foreach ( $this->additional_files_to_backup as $file ) { - if ( $backup_archive->add_file( $file, basename( $file ) ) ) { - $this->count_files ++; - $this->count_files_size = $this->count_files_size + filesize( $file ); - $this->update_working_data(); - } else { - $backup_archive->close(); - $this->steps_data[ $this->step_working ]['on_file'] = ''; - $this->steps_data[ $this->step_working ]['on_folder'] = ''; - $this->log( __( 'Cannot create backup archive correctly. Aborting creation.', 'backwpup' ), - E_USER_ERROR ); - - return false; - } - } - } - $this->substeps_done ++; - } - - //add normal files - while ( $folder = array_shift( $folders_to_backup ) ) { - //jump over already done folders - if ( in_array( $this->steps_data[ $this->step_working ]['on_folder'], $folders_to_backup, true ) ) { - continue; - } - if ( $this->is_debug() ) { - $this->log( sprintf( __( 'Archiving Folder: %s', 'backwpup' ), $folder ) ); - } - $this->steps_data[ $this->step_working ]['on_folder'] = $folder; - $files_in_folder = $this->get_files_in_folder( $folder ); - //add empty folders - if ( empty( $files_in_folder ) ) { - $folder_name_in_archive = trim( ltrim( $this->get_destination_path_replacement( $folder ), '/' ) ); - if ( ! empty ( $folder_name_in_archive ) ) { - $backup_archive->add_empty_folder( $folder, $folder_name_in_archive ); - } - continue; - } - //add files - while ( $file = array_shift( $files_in_folder ) ) { - //jump over already done files - if ( in_array( $this->steps_data[ $this->step_working ]['on_file'], $files_in_folder, true ) ) { - continue; - } - if ( $this->maybe_sql_dump( $file ) ) { - continue; - } - - $this->steps_data[ $this->step_working ]['on_file'] = $file; - //restart if needed - $restart_time = $this->get_restart_time(); - if ( $restart_time <= 0 ) { - unset( $backup_archive ); - $this->do_restart_time( true ); - - return false; - } - //generate filename in archive - $in_archive_filename = ltrim( $this->get_destination_path_replacement( $file ), '/' ); - //add file to archive - if ( $backup_archive->add_file( $file, $in_archive_filename ) ) { - $this->count_files ++; - $this->count_files_size = $this->count_files_size + filesize( $file ); - $this->update_working_data(); - } else { - $backup_archive->close(); - unset( $backup_archive ); - $this->steps_data[ $this->step_working ]['on_file'] = ''; - $this->steps_data[ $this->step_working ]['on_folder'] = ''; - $this->substeps_done = 0; - $this->backup_filesize = filesize( $this->backup_folder . $this->backup_file ); - if ( $this->backup_filesize === false ) { - $this->backup_filesize = PHP_INT_MAX; - } - $this->log( __( 'Cannot create backup archive correctly. Aborting creation.', 'backwpup' ), - E_USER_ERROR ); - - return false; - } - } - $this->steps_data[ $this->step_working ]['on_file'] = ''; - $this->substeps_done ++; - } - $backup_archive->close(); - unset( $backup_archive ); - $this->log( __( 'Backup archive created.', 'backwpup' ), E_USER_NOTICE ); - } catch ( Exception $e ) { - $this->log( $e->getMessage(), E_USER_ERROR, $e->getFile(), $e->getLine() ); - unset( $backup_archive ); - - return false; - } - - $this->backup_filesize = filesize( $this->backup_folder . $this->backup_file ); - if ( $this->backup_filesize === false ) { - $this->backup_filesize = PHP_INT_MAX; - } - - if ( $this->backup_filesize >= PHP_INT_MAX ) { - $this->log( __( 'The Backup archive will be too large for file operations with this PHP Version. You might want to consider splitting the backup job in multiple jobs with less files each.', - 'backwpup' ), - E_USER_ERROR ); - $this->end(); - } else { - $this->log( sprintf( __( 'Archive size is %s.', 'backwpup' ), size_format( $this->backup_filesize, 2 ) ), - E_USER_NOTICE ); - } - - $this->log( sprintf( __( '%1$d Files with %2$s in Archive.', 'backwpup' ), - $this->count_files, - size_format( $this->count_files_size, 2 ) ), - E_USER_NOTICE ); - - return true; - } - - /** - * Encrypt Archive - * - * Encrypt the backup archive. - * - * @return bool true when done, false otherwise - */ - private function encrypt_archive() { - - $encryptionType = get_site_option( 'backwpup_cfg_encryption' ); - // Substeps is number of 128 KB chunks - $block_size = 128 * 1024; - $this->substeps_todo = ceil( $this->backup_filesize / $block_size ); - - if ( ! isset( $this->steps_data[ $this->step_working ]['encrypted_filename'] ) ) { - $this->steps_data[ $this->step_working ]['encrypted_filename'] = $this->backup_folder . $this->backup_file . '.encrypted'; - if ( is_file( $this->steps_data[ $this->step_working ]['encrypted_filename'] ) ) { - unlink( $this->steps_data[ $this->step_working ]['encrypted_filename'] ); - } - } - - if ( ! isset( $this->steps_data[ $this->step_working ]['key'] ) ) { - switch ( $encryptionType ) { - case self::ENCRYPTION_SYMMETRIC: - $key = get_site_option( 'backwpup_cfg_encryptionkey' ); - $this->steps_data[ $this->step_working ]['key'] = pack( 'H*', $key ); - break; - case self::ENCRYPTION_ASYMMETRIC: - $this->steps_data[ $this->step_working ]['key'] = \phpseclib\Crypt\Random::string( 32 ); - break; - } - - if ( empty( $this->steps_data[ $this->step_working ]['key'] ) ) { - $this->log( __( 'No encryption key was provided. Aborting encryption.', 'backwpup' ), E_USER_WARNING ); - - return false; - } - } - - if ( $this->steps_data[ $this->step_working ]['SAVE_STEP_TRY'] != $this->steps_data[ $this->step_working ]['STEP_TRY'] ) { - // Show initial log message - $this->log( sprintf( __( '%d. Trying to encrypt archive …', 'backwpup' ), - $this->steps_data[ $this->step_working ]['STEP_TRY'] ), - E_USER_NOTICE ); - } - - $aes = new \phpseclib\Crypt\AES( \phpseclib\Crypt\AES::MODE_CBC ); - $aes->setKey( $this->steps_data[ $this->step_working ]['key'] ); - $aes->enableContinuousBuffer(); - $aes->disablePadding(); - - $file_in = fopen( $this->backup_folder . $this->backup_file, 'rb' ); - if ( ! $file_in ) { - $this->log( __( 'Cannot open the archive for reading. Aborting encryption.', 'backwpup' ), E_USER_ERROR ); - - return false; - } - - $file_out = fopen( $this->steps_data[ $this->step_working ]['encrypted_filename'], 'a+b' ); - if ( ! $file_out ) { - $this->log( __( 'Cannot write the encrypted archive. Aborting encryption.', 'backwpup' ), E_USER_ERROR ); - - return false; - } - - if ( $this->substeps_done === 0 ) { - switch ( $encryptionType ) { - case self::ENCRYPTION_SYMMETRIC: - fwrite( $file_out, "\x00" ); - break; - case self::ENCRYPTION_ASYMMETRIC: - fwrite( $file_out, "\x01" ); - - $rsa = new \phpseclib\Crypt\RSA(); - $rsa->loadKey( get_site_option( 'backwpup_cfg_publickey' ) ); - - $key = $rsa->encrypt( $this->steps_data[ $this->step_working ]['key'] ); - $length = strlen( $key ); - - fwrite( $file_out, pack( 'H*', dechex( $length ) ) ); - fwrite( $file_out, $key ); - break; - } - } - - $bytes_read = $this->substeps_done * $block_size; - fseek( $file_in, $bytes_read ); - - while ( $bytes_read <= $this->backup_filesize ) { - $data = fread( $file_in, $block_size ); - // Check if final block - $length = strlen( $data ); - if ( $this->substeps_done == $this->substeps_todo - 1 ) { - // Pad as necessary - $pad = 16 - ( $length % 16 ); - $data = str_pad( $data, $length + $pad, chr( $pad ) ); - } - fwrite( $file_out, $aes->encrypt( $data ) ); - - $this->substeps_done ++; - $bytes_read += strlen( $data ); - $this->update_working_data(); - - // Should we restart? - $restart_time = $this->get_restart_time(); - if ( $restart_time <= 0 ) { - $this->do_restart_time( true ); - fclose( $file_in ); - fclose( $file_out ); - - return false; - } - } - - fclose( $file_in ); - fclose( $file_out ); - - $this->log( sprintf( __( 'Encrypted %s of data.', 'backwpup' ), size_format( $bytes_read, 2 ) ), - E_USER_NOTICE ); - - // Remove the original file then rename the encrypted file - if ( ! unlink( $this->backup_folder . $this->backup_file ) ) { - $this->log( __( 'Unable to delete unencrypted archive.', 'backwpup' ) ); - - return false; - } - if ( ! rename( $this->steps_data[ $this->step_working ]['encrypted_filename'], - $this->backup_folder . $this->backup_file ) ) { - $this->log( __( 'Unable to rename encrypted archive.', 'backwpup' ) ); - - return false; - } - - $this->backup_filesize = filesize( $this->backup_folder . $this->backup_file ); - $this->log( __( 'Archive has been successfully encrypted.', 'backwpup' ) ); - - return true; - } - - /** - * Get list of Folder for backup - * - * @return array folder list - */ - public function get_folders_to_backup() { - - $file = BackWPup::get_plugin_data( 'temp' ) . 'backwpup-' . BackWPup::get_plugin_data( 'hash' ) . '-folder.php'; - - if ( ! file_exists( $file ) ) { - return array(); - } - - $folders = array(); - - $file_data = file( $file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES ); - - foreach ( $file_data as $folder ) { - $folder = trim( str_replace( array( 'count_folder = count( $folders ); - - return $folders; - } - - /** - * - * Get back a array of files to backup in the selected folder - * - * @param string $folder the folder to get the files from - * - * @return array files to backup - */ - public function get_files_in_folder( $folder ) { - - $files = array(); - $folder = trailingslashit( $folder ); - - if ( ! is_dir( $folder ) ) { - $this->log( sprintf( _x( 'Folder %s does not exist', 'Folder name', 'backwpup' ), $folder ), - E_USER_WARNING ); - - return $files; - } - - if ( ! is_readable( $folder ) ) { - $this->log( sprintf( _x( 'Folder %s is not readable', 'Folder name', 'backwpup' ), $folder ), - E_USER_WARNING ); - - return $files; - } - - try { - $dir = new BackWPup_Directory( $folder ); - - foreach ( $dir as $file ) { - if ( $file->isDot() || $file->isDir() ) { - continue; - } - $path = BackWPup_Path_Fixer::slashify( $file->getPathname() ); - foreach ( $this->exclude_from_backup as $exclusion ) { //exclude files - $exclusion = trim( $exclusion ); - if ( stripos( $path, $exclusion ) !== false && ! empty( $exclusion ) ) { - continue 2; - } - } - if ( $this->job['backupexcludethumbs'] && strpos( $folder, - BackWPup_File::get_upload_dir() ) !== false && preg_match( "/\-[0-9]{1,4}x[0-9]{1,4}.+\.(jpg|png|gif)$/i", - $file->getFilename() ) ) { - continue; - } - if ( $file->isLink() ) { - $this->log( sprintf( __( 'Link "%s" not following.', 'backwpup' ), $file->getPathname() ), - E_USER_WARNING ); - } elseif ( ! $file->isReadable() ) { - $this->log( sprintf( __( 'File "%s" is not readable!', 'backwpup' ), $file->getPathname() ), - E_USER_WARNING ); - } else { - $file_size = $file->getSize(); - if ( ! is_int( $file_size ) || $file_size < 0 || $file_size > 2147483647 ) { - $this->log( sprintf( __( 'File size of “%s” cannot be retrieved. File might be too large and will not be added to queue.', - 'backwpup' ), - $file->getPathname() . ' ' . $file_size ), - E_USER_WARNING ); - continue; - } - $files[] = BackWPup_Path_Fixer::slashify( realpath( $path ) ); - } - } - - } catch ( UnexpectedValueException $e ) { - $this->log( sprintf( __( "Could not open path: %s" ), $e->getMessage() ), E_USER_WARNING ); - } - - return $files; - } - - /** - * Get job restart time - * - * @return int remaining time - */ - public function get_restart_time() { - - if ( php_sapi_name() == 'cli' ) { - return 300; - } - - $job_max_execution_time = get_site_option( 'backwpup_cfg_jobmaxexecutiontime' ); - - if ( empty( $job_max_execution_time ) ) { - return 300; - } - - $execution_time = microtime( true ) - $this->timestamp_script_start; - - return $job_max_execution_time - $execution_time - 3; - } - - /** - * Do a job restart - * - * @param bool $do_restart_now should time restart now be done - * - * @return int remaining time - */ - public function do_restart_time( $do_restart_now = false ) { - - if ( php_sapi_name() == 'cli' ) { - return 300; - } - - //do restart after signal is send - if ( $this->signal !== 0 ) { - $this->steps_data[ $this->step_working ]['SAVE_STEP_TRY'] = $this->steps_data[ $this->step_working ]['STEP_TRY']; - $this->steps_data[ $this->step_working ]['STEP_TRY'] -= 1; - $this->do_restart( true ); - } - - $job_max_execution_time = get_site_option( 'backwpup_cfg_jobmaxexecutiontime' ); - - if ( empty( $job_max_execution_time ) ) { - return 300; - } - - $execution_time = microtime( true ) - $this->timestamp_script_start; - - // do restart 3 sec. before max. execution time - if ( $do_restart_now || $execution_time >= ( $job_max_execution_time - 3 ) ) { - $this->steps_data[ $this->step_working ]['SAVE_STEP_TRY'] = $this->steps_data[ $this->step_working ]['STEP_TRY']; - $this->steps_data[ $this->step_working ]['STEP_TRY'] -= 1; - $this->do_restart( true ); - } - - return $job_max_execution_time - $execution_time; - } - - /** - * create manifest file - * - * @return bool - */ - public function create_manifest() { - - $this->substeps_todo = 3; - - $this->log( sprintf( __( '%d. Trying to generate a manifest file …', 'backwpup' ), - $this->steps_data[ $this->step_working ]['STEP_TRY'] ) ); - - //build manifest - $manifest = array(); - // add blog information - $manifest['blog_info']['url'] = home_url(); - $manifest['blog_info']['wpurl'] = site_url(); - $manifest['blog_info']['prefix'] = $GLOBALS['wpdb']->prefix; - $manifest['blog_info']['description'] = get_option( 'blogdescription' ); - $manifest['blog_info']['stylesheet_directory'] = get_template_directory_uri(); - $manifest['blog_info']['activate_plugins'] = wp_get_active_and_valid_plugins(); - $manifest['blog_info']['activate_theme'] = wp_get_theme()->get( 'Name' ); - $manifest['blog_info']['admin_email'] = get_option( 'admin_email' ); - $manifest['blog_info']['charset'] = get_bloginfo( 'charset' ); - $manifest['blog_info']['version'] = BackWPup::get_plugin_data( 'wp_version' ); - $manifest['blog_info']['backwpup_version'] = BackWPup::get_plugin_data( 'version' ); - $manifest['blog_info']['language'] = get_bloginfo( 'language' ); - $manifest['blog_info']['name'] = get_bloginfo( 'name' ); - $manifest['blog_info']['abspath'] = ABSPATH; - $manifest['blog_info']['uploads'] = wp_upload_dir( null, false, true ); - $manifest['blog_info']['contents']['basedir'] = WP_CONTENT_DIR; - $manifest['blog_info']['contents']['baseurl'] = WP_CONTENT_URL; - $manifest['blog_info']['plugins']['basedir'] = WP_PLUGIN_DIR; - $manifest['blog_info']['plugins']['baseurl'] = WP_PLUGIN_URL; - $manifest['blog_info']['themes']['basedir'] = get_theme_root(); - $manifest['blog_info']['themes']['baseurl'] = get_theme_root_uri(); +class BackWPup_Job +{ + public const ENCRYPTION_SYMMETRIC = 'symmetric'; + public const ENCRYPTION_ASYMMETRIC = 'asymmetric'; + + /** + * @var array of the job settings + */ + public $job = []; + + /** + * @var int The timestamp when the job starts + */ + public $start_time = 0; + + /** + * @var string the logfile + */ + public $logfile = ''; + /** + * @var array for temp values + */ + public $temp = []; + /** + * @var string Folder where is Backup files in + */ + public $backup_folder = ''; + /** + * @var string the name of the Backup archive file + */ + public $backup_file = ''; + /** + * @var int The size of the Backup archive file + */ + public $backup_filesize = 0; + /** + * @var int PID of script + */ + public $pid = 0; + /** + * @var float Timestamp of last update off .running file + */ + public $timestamp_last_update = 0; + /** + * @var int Number of warnings + */ + public $warnings = 0; + /** + * @var int Number of errors + */ + public $errors = 0; + /** + * @var string the last log notice message + */ + public $lastmsg = ''; + /** + * @var string the last log error/waring message + */ + public $lasterrormsg = ''; + /** + * @var array of steps to do + */ + public $steps_todo = ['CREATE']; + /** + * @var array of done steps + */ + public $steps_done = []; + /** + * @var array of steps data + */ + public $steps_data = []; + /** + * @var string working on step + */ + public $step_working = 'CREATE'; + /** + * @var int Number of sub steps must do in step + */ + public $substeps_todo = 0; + /** + * @var int Number of sub steps done in step + */ + public $substeps_done = 0; + /** + * @var int Percent of steps done + */ + public $step_percent = 1; + /** + * @var int Percent of sub steps done + */ + public $substep_percent = 1; + /** + * @var string[] Array of additional files to backup + */ + public $additional_files_to_backup = []; + /** + * @var array of files/folder to exclude from backup + */ + public $exclude_from_backup = []; + /** + * @var int count of affected files + */ + public $count_files = 0; + /** + * @var int count of affected file sizes + */ + public $count_files_size = 0; + /** + * @var int count of affected folders + */ + public $count_folder = 0; + /** + * If job aborted from user. + * + * @var bool + */ + public $user_abort = false; + /** + * A uniqid ID uniqid('', true); to identify process. + * + * @var string + */ + public $uniqid = ''; + /** + * @var float Timestamp of script start + */ + private $timestamp_script_start = 0; + /** + * Stores data that will only used in a single run. + * + * @var array + */ + private $run = []; + /** + * @var string logging level (normal|normal_untranslated|debug|debug_untranslated) + */ + private $log_level = 'normal'; + + /** + * @var int Signal of signal handler + */ + private $signal = 0; + + public static function start_http($starttype, $jobid = 0) + { + //load text domain + $log_level = get_site_option('backwpup_cfg_loglevel', 'normal_translated'); + if (strstr($log_level, 'translated')) { + BackWPup::load_text_domain(); + } else { + add_filter('override_load_textdomain', '__return_true'); + $GLOBALS['l10n'] = []; + } + if ($starttype !== 'restart') { + //check job id exists + if ((int) $jobid !== (int) BackWPup_Option::get($jobid, 'jobid')) { + return false; + } + + //check folders + $log_folder = get_site_option('backwpup_cfg_logfolder'); + $folder_message_log = BackWPup_File::check_folder(BackWPup_File::get_absolute_path($log_folder)); + $folder_message_temp = BackWPup_File::check_folder(BackWPup::get_plugin_data('TEMP'), true); + if (!empty($folder_message_log) || !empty($folder_message_temp)) { + BackWPup_Admin::message($folder_message_log, true); + BackWPup_Admin::message($folder_message_temp, true); + + return false; + } + } + + // redirect + if ($starttype === 'runnowalt') { + ob_start(); + wp_redirect(add_query_arg(['page' => 'backwpupjobs'], network_admin_url('admin.php'))); + echo ' '; + flush(); + if ($level = ob_get_level()) { + for ($i = 0; $i < $level; ++$i) { + ob_end_clean(); + } + } + } + + // Should be preventing doubled running job's on http requests + $random = random_int(10, 90) * 10000; + usleep($random); + + // Check running job. + $backwpup_job_object = self::get_working_data(); + // Start class. + $starttype_exists = in_array($starttype, ['runnow', 'runnowalt', 'runext', 'cronrun'], true); + if (!$backwpup_job_object && $starttype_exists && $jobid) { + // Schedule restart event. + wp_schedule_single_event(time() + 60, 'backwpup_cron', ['arg' => 'restart']); + // Sstart job. + $backwpup_job_object = new self(); + $backwpup_job_object->create($starttype, $jobid); + } + if ($backwpup_job_object) { + $backwpup_job_object->run(); + } + } + + /** + * Get data off a working job. + * + * @return bool|object BackWPup_Job Object or Bool if file not exits + */ + public static function get_working_data() + { + clearstatcache(true, BackWPup::get_plugin_data('running_file')); + + if (!file_exists(BackWPup::get_plugin_data('running_file'))) { + return false; + } + + $file_data = file_get_contents(BackWPup::get_plugin_data('running_file'), false, null, 8); + if (empty($file_data)) { + return false; + } + + if ($job_object = unserialize($file_data)) { + if ($job_object instanceof BackWPup_Job) { + return $job_object; + } + } + + return false; + } + + /** + * @return string|int|null + */ + public function getStepData(string $key) + { + return $this->steps_data[$this->step_working][$key] ?? null; + } + + /** + * @param int|string $value + */ + public function setStepData(string $key, $value): void + { + $this->steps_data[$this->step_working][$key] = $value; + } + + /** + * This starts or restarts the job working. + * + * @param string $start_type Start types are 'runnow', 'runnowalt', 'cronrun', 'runext', 'runcli' + * @param array|int $job_id The id of job of a job to start + */ + private function create($start_type, $job_id = 0) + { + /** @var wpdb $wpdb */ + global $wpdb; + + //check startype + if (!in_array($start_type, ['runnow', 'runnowalt', 'cronrun', 'runext', 'runcli'], true)) { + return; + } + + if ($job_id) { + $this->job = BackWPup_Option::get_job($job_id); + } else { + return; + } + + $this->start_time = current_time('timestamp'); + $this->lastmsg = __('Starting job', 'backwpup'); + //set Logfile + $log_folder = get_site_option('backwpup_cfg_logfolder'); + $log_folder = BackWPup_File::get_absolute_path($log_folder); + $this->logfile = $log_folder . 'backwpup_log_' . BackWPup::get_generated_hash(6) . '_' . date( + 'Y-m-d_H-i-s', + current_time('timestamp') + ) . '.html'; + //write settings to job + BackWPup_Option::update($this->job['jobid'], 'lastrun', $this->start_time); + BackWPup_Option::update($this->job['jobid'], 'logfile', $this->logfile); //Set current logfile + BackWPup_Option::update($this->job['jobid'], 'lastbackupdownloadurl', ''); + //Set needed job values + $this->timestamp_last_update = microtime(true); + $this->exclude_from_backup = explode(',', trim($this->job['fileexclude'])); + $this->exclude_from_backup = array_unique($this->exclude_from_backup); + //setup job steps + $this->steps_data['CREATE']['CALLBACK'] = ''; + $this->steps_data['CREATE']['NAME'] = __('Job Start', 'backwpup'); + $this->steps_data['CREATE']['STEP_TRY'] = 0; + //ADD Job types file + $job_need_dest = false; + if ($job_types = BackWPup::get_job_types()) { + /** @var BackWPup_JobTypes $job_type_class */ + foreach ($job_types as $id => $job_type_class) { + if (in_array($id, $this->job['type'], true) && $job_type_class->creates_file()) { + $this->steps_todo[] = 'JOB_' . $id; + $this->steps_data['JOB_' . $id]['NAME'] = $job_type_class->info['description']; + $this->steps_data['JOB_' . $id]['STEP_TRY'] = 0; + $this->steps_data['JOB_' . $id]['SAVE_STEP_TRY'] = 0; + $job_need_dest = true; + } + } + } + //add destinations and create archive if a job where files to backup + if ($job_need_dest) { + //Create manifest file + $this->steps_todo[] = 'CREATE_MANIFEST'; + $this->steps_data['CREATE_MANIFEST']['NAME'] = __('Creates manifest file', 'backwpup'); + $this->steps_data['CREATE_MANIFEST']['STEP_TRY'] = 0; + $this->steps_data['CREATE_MANIFEST']['SAVE_STEP_TRY'] = 0; + //Add archive creation and backup filename on backup type archive + if ($this->job['backuptype'] == 'archive') { + //get Backup folder if destination folder set + if (in_array('FOLDER', $this->job['destinations'], true)) { + $this->backup_folder = $this->job['backupdir']; + //check backup folder + if (!empty($this->backup_folder)) { + $this->backup_folder = BackWPup_File::get_absolute_path($this->backup_folder); + $this->job['backupdir'] = $this->backup_folder; + } + } + //set temp folder to backup folder if not set because we need one + if (!$this->backup_folder || $this->backup_folder == '/') { + $this->backup_folder = BackWPup::get_plugin_data('TEMP'); + } + //Create backup archive full file name + $this->backup_file = $this->generate_filename($this->job['archivename'], $this->job['archiveformat']); + //add archive create + $this->steps_todo[] = 'CREATE_ARCHIVE'; + $this->steps_data['CREATE_ARCHIVE']['NAME'] = __('Creates archive', 'backwpup'); + $this->steps_data['CREATE_ARCHIVE']['STEP_TRY'] = 0; + $this->steps_data['CREATE_ARCHIVE']['SAVE_STEP_TRY'] = 0; + // Encrypt archive + if (BackWPup_Option::get($this->job['jobid'], 'archiveencryption')) { + $this->steps_todo[] = 'ENCRYPT_ARCHIVE'; + $this->steps_data['ENCRYPT_ARCHIVE']['NAME'] = __('Encrypts the archive', 'backwpup'); + $this->steps_data['ENCRYPT_ARCHIVE']['STEP_TRY'] = 0; + $this->steps_data['ENCRYPT_ARCHIVE']['SAVE_STEP_TRY'] = 0; + } + } + //ADD Destinations + foreach (BackWPup::get_registered_destinations() as $id => $dest) { + if (!in_array($id, $this->job['destinations'], true) || empty($dest['class'])) { + continue; + } + /** @var BackWPup_Destinations $dest_class */ + $dest_class = BackWPup::get_destination($id); + if ($dest_class->can_run($this->job)) { + if ($this->job['backuptype'] == 'sync') { + if ($dest['can_sync']) { + $this->steps_todo[] = 'DEST_SYNC_' . $id; + $this->steps_data['DEST_SYNC_' . $id]['NAME'] = $dest['info']['description']; + $this->steps_data['DEST_SYNC_' . $id]['STEP_TRY'] = 0; + $this->steps_data['DEST_SYNC_' . $id]['SAVE_STEP_TRY'] = 0; + } + } else { + $this->steps_todo[] = 'DEST_' . $id; + $this->steps_data['DEST_' . $id]['NAME'] = $dest['info']['description']; + $this->steps_data['DEST_' . $id]['STEP_TRY'] = 0; + $this->steps_data['DEST_' . $id]['SAVE_STEP_TRY'] = 0; + } + } + } + } + //ADD Job type no file + if ($job_types = BackWPup::get_job_types()) { + foreach ($job_types as $id => $job_type_class) { + if (in_array($id, $this->job['type'], true) && !$job_type_class->creates_file()) { + $this->steps_todo[] = 'JOB_' . $id; + $this->steps_data['JOB_' . $id]['NAME'] = $job_type_class->info['description']; + $this->steps_data['JOB_' . $id]['STEP_TRY'] = 0; + $this->steps_data['JOB_' . $id]['SAVE_STEP_TRY'] = 0; + } + } + } + $this->steps_todo[] = 'END'; + $this->steps_data['END']['NAME'] = __('End of Job', 'backwpup'); + $this->steps_data['END']['STEP_TRY'] = 1; + //must write working data + $this->write_running_file(); + + //set log level + $this->log_level = get_site_option('backwpup_cfg_loglevel', 'normal_translated'); + if (!in_array( + $this->log_level, + [ + 'normal_translated', + 'normal', + 'debug_translated', + 'debug', + ], + true + )) { + $this->log_level = 'normal_translated'; + } + //create log file + $head = ''; + $info = ''; + $head .= '' . PHP_EOL; + $head .= '' . PHP_EOL; + $head .= '' . PHP_EOL; + $head .= '' . PHP_EOL; + $head .= '' . sprintf( + __('BackWPup log for %1$s from %2$s at %3$s', 'backwpup'), + $this->job['name'], + date_i18n(get_option('date_format')), + date_i18n(get_option('time_format')) + ) . '' . PHP_EOL; + $head .= '' . PHP_EOL; + $head .= '' . PHP_EOL; + $head .= '' . PHP_EOL; + $head .= '' . PHP_EOL; + $head .= '' . PHP_EOL; + $head .= '' . PHP_EOL; + $head .= '' . PHP_EOL; + $head .= str_pad('', 100) . PHP_EOL; + $head .= str_pad('', 100) . PHP_EOL; + $head .= '' . PHP_EOL; + $head .= '' . PHP_EOL; + $head .= '' . PHP_EOL; + $head .= str_pad('', 100) . PHP_EOL; + $head .= str_pad('', 100) . PHP_EOL; + $head .= '' . PHP_EOL; + $head .= '' . PHP_EOL; + $info .= sprintf( + _x( + '[INFO] %1$s %2$s; A project of Inpsyde GmbH', + 'Plugin name; Plugin Version; plugin url', + 'backwpup' + ), + BackWPup::get_plugin_data('name'), + BackWPup::get_plugin_data('Version'), + __('http://backwpup.com', 'backwpup') + ) . '
' . PHP_EOL; + $info .= sprintf( + _x('[INFO] WordPress %1$s on %2$s', 'WordPress Version; Blog url', 'backwpup'), + BackWPup::get_plugin_data('wp_version'), + esc_attr(site_url('/')) + ) . '
' . PHP_EOL; + $level = __('Normal', 'backwpup'); + $translated = ''; + if ($this->is_debug()) { + $level = __('Debug', 'backwpup'); + } + if (is_textdomain_loaded('backwpup')) { + $translated = __('(translated)', 'backwpup'); + } + $info .= sprintf(__('[INFO] Log Level: %1$s %2$s', 'backwpup'), $level, $translated) . '
' . PHP_EOL; + $job_name = esc_attr($this->job['name']); + if ($this->is_debug()) { + $job_name .= '; ' . implode('+', $this->job['type']); + } + $info .= sprintf(__('[INFO] BackWPup job: %1$s', 'backwpup'), $job_name) . '
' . PHP_EOL; + if ($this->is_debug()) { + $current_user = wp_get_current_user(); + $info .= sprintf( + __('[INFO] Runs with user: %1$s (%2$d) ', 'backwpup'), + $current_user->user_login, + $current_user->ID + ) . '
' . PHP_EOL; + } + if ($this->job['activetype'] === 'wpcron') { + //check next run + $cron_next = wp_next_scheduled('backwpup_cron', ['arg' => $this->job['jobid']]); + if (!$cron_next || $cron_next < time()) { + wp_unschedule_event($cron_next, 'backwpup_cron', ['arg' => $this->job['jobid']]); + $cron_next = BackWPup_Cron::cron_next($this->job['cron']); + wp_schedule_single_event($cron_next, 'backwpup_cron', ['arg' => $this->job['jobid']]); + $cron_next = wp_next_scheduled('backwpup_cron', ['arg' => $this->job['jobid']]); + } + //output scheduling + if ($this->is_debug()) { + if (!$cron_next) { + $cron_next = __('Not scheduled!', 'backwpup'); + } else { + $cron_next = date_i18n( + 'D, j M Y @ H:i', + $cron_next + (get_option('gmt_offset') * 3600), + true + ); + } + $info .= sprintf( + __('[INFO] Cron: %s; Next: %s ', 'backwpup'), + $this->job['cron'], + $cron_next + ) . '
' . PHP_EOL; + } + } elseif ($this->job['activetype'] == 'link' && $this->is_debug()) { + $info .= __('[INFO] BackWPup job start with link is active', 'backwpup') . '
' . PHP_EOL; + } elseif ($this->job['activetype'] == 'easycron' && $this->is_debug()) { + $info .= __('[INFO] BackWPup job start with EasyCron.com', 'backwpup') . '
' . PHP_EOL; + //output scheduling + if ($this->is_debug()) { + $cron_next = BackWPup_Cron::cron_next($this->job['cron']); + $cron_next = date_i18n('D, j M Y @ H:i', $cron_next + (get_option('gmt_offset') * 3600), true); + $info .= sprintf( + __('[INFO] Cron: %s; Next: %s ', 'backwpup'), + $this->job['cron'], + $cron_next + ) . '
' . PHP_EOL; + } + } elseif ($this->is_debug()) { + $info .= __('[INFO] BackWPup no automatic job start configured', 'backwpup') . '
' . PHP_EOL; + } + if ($this->is_debug()) { + if ($start_type == 'cronrun') { + $info .= __('[INFO] BackWPup job started from wp-cron', 'backwpup') . '
' . PHP_EOL; + } elseif ($start_type == 'runnow' || $start_type == 'runnowalt') { + $info .= __('[INFO] BackWPup job started manually', 'backwpup') . '
' . PHP_EOL; + } elseif ($start_type == 'runext') { + $info .= __('[INFO] BackWPup job started from external url', 'backwpup') . '
' . PHP_EOL; + } elseif ($start_type == 'runcli') { + $info .= __( + '[INFO] BackWPup job started form commandline interface', + 'backwpup' + ) . '
' . PHP_EOL; + } + $bit = ''; + if (PHP_INT_SIZE === 4) { + $bit = ' (32bit)'; + } + if (PHP_INT_SIZE === 8) { + $bit = ' (64bit)'; + } + $info .= __( + '[INFO] PHP ver.:', + 'backwpup' + ) . ' ' . PHP_VERSION . $bit . '; ' . PHP_SAPI . '; ' . PHP_OS . '
' . PHP_EOL; + $info .= sprintf( + __('[INFO] Maximum PHP script execution time is %1$d seconds', 'backwpup'), + ini_get('max_execution_time') + ) . '
' . PHP_EOL; + if (php_sapi_name() != 'cli') { + $job_max_execution_time = get_site_option('backwpup_cfg_jobmaxexecutiontime'); + if (!empty($job_max_execution_time)) { + $info .= sprintf( + __('[INFO] Script restart time is configured to %1$d seconds', 'backwpup'), + $job_max_execution_time + ) . '
' . PHP_EOL; + } + } + $info .= sprintf( + __('[INFO] MySQL ver.: %s', 'backwpup'), + $wpdb->get_var('SELECT VERSION() AS version') + ) . '
' . PHP_EOL; + if (isset($_SERVER['SERVER_SOFTWARE'])) { + $info .= sprintf( + __('[INFO] Web Server: %s', 'backwpup'), + $_SERVER['SERVER_SOFTWARE'] + ) . '
' . PHP_EOL; + } + if (function_exists('curl_init')) { + $curlversion = curl_version(); + $info .= sprintf( + __('[INFO] curl ver.: %1$s; %2$s', 'backwpup'), + $curlversion['version'], + $curlversion['ssl_version'] + ) . '
' . PHP_EOL; + } + $info .= sprintf( + __('[INFO] Temp folder is: %s', 'backwpup'), + BackWPup::get_plugin_data('TEMP') + ) . '
' . PHP_EOL; + } + if ($this->is_debug()) { + $logfile = $this->logfile; + } else { + $logfile = basename($this->logfile); + } + $info .= sprintf(__('[INFO] Logfile is: %s', 'backwpup'), $logfile) . '
' . PHP_EOL; + if (!empty($this->backup_file) && $this->job['backuptype'] === 'archive') { + if ($this->is_debug()) { + $backupfile = $this->backup_folder . $this->backup_file; + } else { + $backupfile = $this->backup_file; + } + $info .= sprintf(__('[INFO] Backup file is: %s', 'backwpup'), $backupfile) . '
' . PHP_EOL; + } else { + $info .= sprintf( + __('[INFO] Backup type is: %s', 'backwpup'), + $this->job['backuptype'] + ) . '
' . PHP_EOL; + } + //output info on cli + if (php_sapi_name() == 'cli' && defined('STDOUT')) { + fwrite(STDOUT, strip_tags($info)); + } + if (!file_put_contents($this->logfile, $head . $info, FILE_APPEND)) { + $this->logfile = ''; + $this->log(__('Could not write log file', 'backwpup'), E_USER_ERROR); + } + //test for destinations + if ($job_need_dest) { + $desttest = false; + + foreach ($this->steps_todo as $deststeptest) { + if (substr($deststeptest, 0, 5) == 'DEST_') { + $desttest = true; + break; + } + } + if (!$desttest) { + $this->log( + __( + 'No destination correctly defined for backup! Please correct job settings.', + 'backwpup' + ), + E_USER_ERROR + ); + $this->steps_todo = ['END']; + } + } + //test backup folder + if (!empty($this->backup_folder)) { + $folder_message = BackWPup_File::check_folder($this->backup_folder, true); + if (!empty($folder_message)) { + $this->log($folder_message, E_USER_ERROR); + $this->steps_todo = ['END']; + } + } + + //Set start as done + $this->steps_done[] = 'CREATE'; + } + + /** + * @param $name + * @param string $suffix + * @param bool $delete_temp_file + * + * @return string + */ + public function generate_filename($name, $suffix = '', $delete_temp_file = true) + { + if ($suffix) { + $suffix = '.' . trim($suffix, '. '); + } + + $name = BackWPup_Option::substitute_date_vars($name); + $name .= $suffix; + if ($delete_temp_file && is_writeable(BackWPup::get_plugin_data('TEMP') . $name) && !is_dir(BackWPup::get_plugin_data('TEMP') . $name) && !is_link(BackWPup::get_plugin_data('TEMP') . $name)) { + unlink(BackWPup::get_plugin_data('TEMP') . $name); + } + + return $name; + } + + /** + * Sanitizes a filename, replacing whitespace with underscores. + * + * @param $filename + * + * @return mixed + */ + public static function sanitize_file_name($filename) + { + $filename = trim($filename); + + $special_chars = [ + '?', + '[', + ']', + '/', + '\\', + '=', + '<', + '>', + ':', + ';', + ',', + "'", + '"', + '&', + '$', + '#', + '*', + '(', + ')', + '|', + '~', + '`', + '!', + '{', + '}', + chr(0), + ]; + + $filename = str_replace($special_chars, '', $filename); + + $filename = str_replace([' ', '%20', '+'], '_', $filename); + $filename = str_replace(["\n", "\t", "\r"], '-', $filename); + + return trim($filename, '.-_'); + } + + private function write_running_file() + { + $clone = clone $this; + $data = 'log(__('Cannot write progress to working file. Job will be aborted.', 'backwpup'), E_USER_ERROR); + } + } + + /** + * Write messages to log file. + * + * @param string $message the error message + * @param int $type the error number (E_USER_ERROR,E_USER_WARNING,E_USER_NOTICE, ...) + * @param string $file the full path of file with error (__FILE__) + * @param int $line the line in that is the error (__LINE__) + * + * @return bool true + */ + public function log($message, $type = E_USER_NOTICE, $file = '', $line = 0) + { + // if error has been suppressed with an @ + if (error_reporting() == 0) { + return true; + } + + //if first the type an second the message switch it on user errors + if (!is_int($type) && is_int($message) && in_array( + $message, + [ + 1, + 2, + 4, + 8, + 16, + 32, + 64, + 128, + 256, + 512, + 1024, + 2048, + 4096, + 8192, + 16384, + ], + true + ) + ) { + $temp = $message; + $message = $type; + $type = $temp; + } + + //json message if array or object + if (is_array($message) || is_object($message)) { + $message = json_encode($message); + } + + //if not set line and file get it + if ($this->is_debug()) { + if (empty($file) || empty($line)) { + $debug_info = debug_backtrace(); + $file = $debug_info[0]['file']; + $line = $debug_info[0]['line']; + } + } + + $error = false; + $warning = false; + + switch ($type) { + case E_NOTICE: + case E_USER_NOTICE: + break; + + case E_WARNING: + case E_CORE_WARNING: + case E_COMPILE_WARNING: + case E_USER_WARNING: + $this->warnings++; + $warning = true; + $message = __('WARNING:', 'backwpup') . ' ' . $message; + break; + + case E_ERROR: + case E_PARSE: + case E_CORE_ERROR: + case E_COMPILE_ERROR: + case E_USER_ERROR: + $this->errors++; + $error = true; + $message = __('ERROR:', 'backwpup') . ' ' . $message; + break; + + case E_DEPRECATED: + case E_USER_DEPRECATED: + $message = __('DEPRECATED:', 'backwpup') . ' ' . $message; + break; + + case E_STRICT: + $message = __('STRICT NOTICE:', 'backwpup') . ' ' . $message; + break; + + case E_RECOVERABLE_ERROR: + $this->errors++; + $error = true; + $message = __('RECOVERABLE ERROR:', 'backwpup') . ' ' . $message; + break; + + default: + $message = $type . ': ' . $message; + break; + } + + //print message to cli + if (defined(\WP_CLI::class) && WP_CLI) { + $output_message = str_replace(['…', ' '], ['...', ' '], esc_html($message)); + if (!call_user_func(['\cli\Shell', 'isPiped'])) { + if ($error) { + $output_message = '%r' . $output_message . '%n'; + } + if ($warning) { + $output_message = '%y' . $output_message . '%n'; + } + $output_message = call_user_func(['\cli\Colors', 'colorize'], $output_message, true); + } + WP_CLI::line($output_message); + } elseif (php_sapi_name() == 'cli' && defined('STDOUT')) { + $output_message = str_replace( + ['…', ' '], + [ + '...', + ' ', + ], + esc_html($message) + ) . PHP_EOL; + fwrite(STDOUT, $output_message); + } + + //timestamp for log file + $debug_info = ''; + if ($this->is_debug()) { + $debug_info = ' title="[Type: ' . $type . '|Line: ' . $line . '|File: ' . $this->get_destination_path_replacement($file) . '|Mem: ' . size_format( + @memory_get_usage(true), + 2 + ) . '|Mem Max: ' . size_format( + @memory_get_peak_usage(true), + 2 + ) . '|Mem Limit: ' . ini_get('memory_limit') . '|PID: ' . self::get_pid() . ' | UniqID: ' . $this->uniqid . '|Queries: ' . get_num_queries() . ']"'; + } + $timestamp = '[' . date( + 'd-M-Y H:i:s', + current_time('timestamp') + ) . '] '; + + //set last Message + if ($error) { + $output_message = '' . esc_html($message) . ''; + $this->lasterrormsg = $output_message; + } elseif ($warning) { + $output_message = '' . esc_html($message) . ''; + $this->lasterrormsg = $output_message; + } else { + $output_message = esc_html($message); + $this->lastmsg = $output_message; + } + //write log file + if ($this->logfile) { + if (!file_put_contents( + $this->logfile, + $timestamp . $output_message . '
' . PHP_EOL, + FILE_APPEND + )) { + $this->logfile = ''; + restore_error_handler(); + trigger_error(esc_html($message), $type); + } + + //write new log header + if (($error || $warning) && $this->logfile) { + if ($fd = fopen($this->logfile, 'r+')) { + $file_pos = ftell($fd); + + while (!feof($fd)) { + $line = fgets($fd); + if ($error && stripos($line, '', + 100 + ) . PHP_EOL + ); + break; + } + if ($warning && stripos($line, '', + 100 + ) . PHP_EOL + ); + break; + } + $file_pos = ftell($fd); + } + fclose($fd); + } + } + } + + //write working data + $this->update_working_data($error || $warning); + + //true for no more php error handling. + return true; + } + + /** + * Is debug log active. + * + * @return bool + */ + public function is_debug() + { + return strstr($this->log_level, 'debug') ? true : false; + } + + /** + * Change path of a given path + * for better storing in archives or on sync destinations. + * + * @param $path string path to change to wp default path + * + * @return string + */ + public function get_destination_path_replacement($path) + { + $abs_path = realpath(BackWPup_Path_Fixer::fix_path(ABSPATH)); + if ($this->job['backupabsfolderup']) { + $abs_path = dirname($abs_path); + } + $abs_path = trailingslashit(str_replace('\\', '/', $abs_path)); + + $path = str_replace(['\\', $abs_path], '/', $path); + + //replace the colon from windows drive letters with so they will not be problems with them in archives or on copying to directory + if (0 === stripos(PHP_OS, 'WIN') && 1 === strpos($path, ':/')) { + $path = '/' . substr_replace($path, '', 1, 1); + } + + return $path; + } + + /** + * Get the Process id of working script. + * + * @return int + */ + private static function get_pid() + { + if (function_exists('posix_getpid')) { + return posix_getpid(); + } + if (function_exists('getmypid')) { + return getmypid(); + } + + return -1; + } + + /** + * Write the Working data to display the process or that i can executes again + * The write will only done every second. + * + * @param bool $must + */ + public function update_working_data($must = false) + { + global $wpdb; + + //to reduce server load + if (get_site_option('backwpup_cfg_jobwaittimems') > 0 && get_site_option('backwpup_cfg_jobwaittimems') <= 500000) { + usleep(get_site_option('backwpup_cfg_jobwaittimems')); + } + + //check free memory + $this->need_free_memory('10M'); + + //only run every 1 sec. + $time_to_update = microtime(true) - $this->timestamp_last_update; + if ($time_to_update < 1 && !$must) { + return; + } + + //FCGI must have a permanent output so that it not broke + if (get_site_option('backwpup_cfg_jobdooutput') && !defined('STDOUT')) { + echo str_repeat(' ', 12); + flush(); + } + + // check WPDB connection. WP will do it after a query that will cause messages. + $wpdb->check_connection(false); + + //set execution time again for 5 min + @set_time_limit(300); + + //calc sub step percent + if ($this->substeps_todo > 0 && $this->substeps_done > 0) { + $this->substep_percent = min(round($this->substeps_done / $this->substeps_todo * 100), 100); + } else { + $this->substep_percent = 1; + } + + //check if job aborted + if (!file_exists(BackWPup::get_plugin_data('running_file'))) { + if ($this->step_working !== 'END') { + $this->end(); + } + } else { + $this->timestamp_last_update = microtime(true); //last update of working file + $this->write_running_file(); + } + + if ($this->signal !== 0) { + $this->do_restart(); + } + } + + /** + * Increase automatically the memory that is needed. + * + * @param int|string $memneed of the needed memory + */ + public function need_free_memory($memneed) + { + //need memory + $needmemory = @memory_get_usage(true) + self::convert_hr_to_bytes($memneed); + // increase Memory + if ($needmemory > self::convert_hr_to_bytes(ini_get('memory_limit'))) { + $newmemory = round($needmemory / 1024 / 1024) + 1 . 'M'; + if ($needmemory >= 1073741824) { + $newmemory = round($needmemory / 1024 / 1024 / 1024) . 'G'; + } + @ini_set('memory_limit', $newmemory); + } + } + + /** + * Converts hr to bytes. + * + * @param $size + * + * @return int + */ + public static function convert_hr_to_bytes($size) + { + $size = strtolower($size); + $bytes = (int) $size; + if (strpos($size, 'k') !== false) { + $bytes = intval($size) * 1024; + } elseif (strpos($size, 'm') !== false) { + $bytes = intval($size) * 1024 * 1024; + } elseif (strpos($size, 'g') !== false) { + $bytes = intval($size) * 1024 * 1024 * 1024; + } + + return $bytes; + } + + /** + * Called on job stop makes cleanup and terminates the script. + */ + private function end() + { + $this->step_working = 'END'; + $this->substeps_todo = 1; + + if (!file_exists(BackWPup::get_plugin_data('running_file'))) { + $this->log(__('Aborted by user!', 'backwpup'), E_USER_ERROR); + } + + //delete old logs + if (get_site_option('backwpup_cfg_maxlogs')) { + $log_file_list = []; + $log_folder = trailingslashit(dirname($this->logfile)); + if (is_readable($log_folder)) { //make file list + try { + $dir = new BackWPup_Directory($log_folder); + + foreach ($dir as $file) { + if (!$file->isDot() && strpos( + $file->getFilename(), + 'backwpup_log_' + ) === 0 && strpos($file->getFilename(), '.html') !== false) { + $log_file_list[$file->getMTime()] = clone $file; + } + } + } catch (UnexpectedValueException $e) { + $this->log(sprintf(__('Could not open path: %s'), $e->getMessage()), E_USER_WARNING); + } + } + if (count($log_file_list) > 0) { + krsort($log_file_list, SORT_NUMERIC); + $num_delete_files = 0; + $i = -1; + + foreach ($log_file_list as $log_file) { + ++$i; + if ($i < get_site_option('backwpup_cfg_maxlogs')) { + continue; + } + unlink($log_file->getPathname()); + ++$num_delete_files; + } + if ($num_delete_files > 0) { + $this->log(sprintf( + _n( + 'One old log deleted', + '%d old logs deleted', + $num_delete_files, + 'backwpup' + ), + $num_delete_files + )); + } + } + } + + //Display job working time + if ($this->errors > 0) { + $this->log( + sprintf( + __( + 'Job has ended with errors in %s seconds. You must resolve the errors for correct execution.', + 'backwpup' + ), + current_time('timestamp') - $this->start_time + ), + E_USER_ERROR + ); + } elseif ($this->warnings > 0) { + $this->log( + sprintf( + __( + 'Job finished with warnings in %s seconds. Please resolve them for correct execution.', + 'backwpup' + ), + current_time('timestamp') - $this->start_time + ), + E_USER_WARNING + ); + } else { + $this->log(sprintf( + __('Job done in %s seconds.', 'backwpup'), + current_time('timestamp') - $this->start_time + )); + } + + //Update job options + $this->job['lastruntime'] = current_time('timestamp') - $this->start_time; + BackWPup_Option::update($this->job['jobid'], 'lastruntime', $this->job['lastruntime']); + + //write header info + if (!empty($this->logfile)) { + if ($fd = fopen($this->logfile, 'r+')) { + $filepos = ftell($fd); + $found = 0; + + while (!feof($fd)) { + $line = fgets($fd); + if (stripos($line, '', + 100 + ) . PHP_EOL + ); + ++$found; + } + if (stripos($line, '', + 100 + ) . PHP_EOL + ); + ++$found; + } + if ($found >= 2) { + break; + } + $filepos = ftell($fd); + } + fclose($fd); + } + + //Send mail with log + $sendmail = false; + if ($this->job['mailaddresslog']) { + $sendmail = true; + } + if ($this->errors === 0 && $this->job['mailerroronly']) { + $sendmail = false; + } + if ($sendmail) { + //special subject + $status = __('SUCCESSFUL', 'backwpup'); + if ($this->warnings > 0) { + $status = __('WARNING', 'backwpup'); + } + if ($this->errors > 0) { + $status = __('ERROR', 'backwpup'); + } + + $subject = sprintf( + __('[%3$s] BackWPup log %1$s: %2$s', 'backwpup'), + date_i18n('d-M-Y H:i', $this->start_time, true), + esc_attr($this->job['name']), + $status + ); + $headers = []; + $headers[] = 'Content-Type: text/html; charset=' . get_bloginfo('charset'); + if ($this->job['mailaddresssenderlog']) { + $this->job['mailaddresssenderlog'] = str_replace( + ['<', '>'], + [ + '<', + '>', + ], + $this->job['mailaddresssenderlog'] + ); + + $bracket_pos = strpos($this->job['mailaddresssenderlog'], '<'); + $at_pos = strpos($this->job['mailaddresssenderlog'], '@'); + if ($bracket_pos === false || $at_pos === false) { + $this->job['mailaddresssenderlog'] = str_replace( + [ + '<', + '>', + ], + '', + $this->job['mailaddresssenderlog'] + ) . ' <' . get_bloginfo('admin_email') . '>'; + } + + $headers[] = 'From: ' . $this->job['mailaddresssenderlog']; + } + wp_mail($this->job['mailaddresslog'], $subject, file_get_contents($this->logfile), $headers); + } + } + + //set done + $this->substeps_done = 1; + $this->steps_done[] = 'END'; + + //clean up temp + self::clean_temp_folder(); + + //remove shutdown action + remove_action('shutdown', [$this, 'shutdown']); + restore_exception_handler(); + restore_error_handler(); + + //logfile end + file_put_contents($this->logfile, '' . PHP_EOL . '', FILE_APPEND); + + BackWPup_Cron::check_cleanup(); + + exit(); + } + + /** + * Cleanup Temp Folder. + */ + public static function clean_temp_folder() + { + $instance = new self(); + $temp_dir = BackWPup::get_plugin_data('TEMP'); + $do_not_delete_files = ['.htaccess', 'nginx.conf', 'index.php', '.', '..', '.donotbackup']; + + if (is_writable($temp_dir)) { + try { + $dir = new BackWPup_Directory($temp_dir); + + foreach ($dir as $file) { + if (in_array( + $file->getFilename(), + $do_not_delete_files, + true + ) || $file->isDir() || $file->isLink()) { + continue; + } + if ($file->isWritable()) { + unlink($file->getPathname()); + } + } + } catch (UnexpectedValueException $e) { + $instance->log(sprintf(__('Could not open path: %s'), $e->getMessage()), E_USER_WARNING); + } + } + } + + /** + * Do a job restart. + * + * @param bool $must Restart must done + */ + public function do_restart($must = false) + { + //restart must done if signal + if ($this->signal !== 0) { + $must = true; + } + + //no restart if in end step + if ($this->step_working === 'END' || (count($this->steps_done) + 1) >= count($this->steps_todo)) { + return; + } + + //no restart on cli usage + if (php_sapi_name() == 'cli') { + return; + } + + //no restart if no restart time configured + $job_max_execution_time = get_site_option('backwpup_cfg_jobmaxexecutiontime'); + if (!$must && empty($job_max_execution_time)) { + return; + } + + //no restart when restart was 3 Seconds before + $execution_time = microtime(true) - $this->timestamp_script_start; + if (!$must && $execution_time < 3) { + return; + } + + //no restart if no working job + if (!file_exists(BackWPup::get_plugin_data('running_file'))) { + return; + } + + //print message + if ($this->is_debug()) { + if ($execution_time !== 0) { + $this->log(sprintf(__('Restart after %1$d seconds.', 'backwpup'), ceil($execution_time))); + } elseif ($this->signal !== 0) { + $this->log(__('Restart after getting signal.', 'backwpup')); + } + } + + //do things for a clean restart + $this->pid = 0; + $this->uniqid = ''; + $this->write_running_file(); + remove_action('shutdown', [$this, 'shutdown']); + //do restart + wp_clear_scheduled_hook('backwpup_cron', ['arg' => 'restart']); + wp_schedule_single_event(time() + 5, 'backwpup_cron', ['arg' => 'restart']); + self::get_jobrun_url('restart'); + + exit(); + } + + /** + * Get a url to run a job of BackWPup. + * + * @param string $starttype Start types are 'runnow', 'runnowlink', 'cronrun', 'runext', 'restart', 'restartalt', + * 'test' + * @param int $jobid The id of job to start else 0 + * + * @return array|object [url] is the job url [header] for auth header or object form wp_remote_get() + */ + public static function get_jobrun_url($starttype, $jobid = 0) + { + $authentication = get_site_option( + 'backwpup_cfg_authentication', + [ + 'method' => '', + 'basic_user' => '', + 'basic_password' => '', + 'user_id' => 0, + 'query_arg' => '', + ] + ); + $url = site_url('wp-cron.php'); + $header = ['Cache-Control' => 'no-cache']; + $authurl = ''; + $query_args = [ + '_nonce' => substr(wp_hash(wp_nonce_tick() . 'backwpup_job_run-' . $starttype, 'nonce'), -12, 10), + 'doing_wp_cron' => sprintf('%.22F', microtime(true)), + ]; + + if (in_array($starttype, ['restart', 'runnow', 'cronrun', 'runext', 'test'], true)) { + $query_args['backwpup_run'] = $starttype; + } + + if (in_array($starttype, ['runnowlink', 'runnow', 'cronrun', 'runext'], true) && !empty($jobid)) { + $query_args['jobid'] = $jobid; + } + + if (!empty($authentication['basic_user']) && !empty($authentication['basic_password']) && $authentication['method'] == 'basic') { + $header['Authorization'] = 'Basic ' . base64_encode($authentication['basic_user'] . ':' . BackWPup_Encryption::decrypt($authentication['basic_password'])); + $authurl = urlencode($authentication['basic_user']) . ':' . urlencode(BackWPup_Encryption::decrypt($authentication['basic_password'])) . '@'; + } + + if (!empty($authentication['query_arg']) && $authentication['method'] == 'query_arg') { + $url .= '?' . $authentication['query_arg']; + } + + if ($starttype === 'runext') { + $query_args['_nonce'] = get_site_option('backwpup_cfg_jobrunauthkey'); + $query_args['doing_wp_cron'] = null; + if (!empty($authurl)) { + $url = str_replace('https://', 'https://' . $authurl, $url); + $url = str_replace('http://', 'http://' . $authurl, $url); + } + } + + if ($starttype === 'runnowlink' && (!defined('ALTERNATE_WP_CRON') || !ALTERNATE_WP_CRON)) { + $url = wp_nonce_url(network_admin_url('admin.php'), 'backwpup_job_run-' . $starttype); + $query_args['page'] = 'backwpupjobs'; + $query_args['action'] = 'runnow'; + $query_args['doing_wp_cron'] = null; + unset($query_args['_nonce']); + } + + if ($starttype === 'runnowlink' && defined('ALTERNATE_WP_CRON') && ALTERNATE_WP_CRON) { + $query_args['backwpup_run'] = 'runnowalt'; + $query_args['_nonce'] = substr( + wp_hash(wp_nonce_tick() . 'backwpup_job_run-runnowalt', 'nonce'), + -12, + 10 + ); + $query_args['doing_wp_cron'] = null; + } + + if ($starttype === 'restartalt' && defined('ALTERNATE_WP_CRON') && ALTERNATE_WP_CRON) { + $query_args['backwpup_run'] = 'restart'; + $query_args['_nonce'] = null; + } + + if ($starttype === 'restart' || $starttype === 'test') { + $query_args['_nonce'] = null; + } + + if (!empty($authentication['user_id']) && $authentication['method'] === 'user') { + //cache cookies for auth some + $cookies = get_site_transient('backwpup_cookies'); + if (empty($cookies)) { + $wp_admin_user = get_users(['role' => 'administrator', 'number' => 1]); + if (empty($wp_admin_user)) { + $wp_admin_user = get_users(['role' => 'backwpup_admin', 'number' => 1]); + } + if (!empty($wp_admin_user[0]->ID)) { + $expiration = time() + (2 * DAY_IN_SECONDS); + $manager = WP_Session_Tokens::get_instance($wp_admin_user[0]->ID); + $token = $manager->create($expiration); + $cookies[LOGGED_IN_COOKIE] = wp_generate_auth_cookie( + $wp_admin_user[0]->ID, + $expiration, + 'logged_in', + $token + ); + } + set_site_transient('backwpup_cookies', $cookies, 2 * DAY_IN_SECONDS); + } + } else { + $cookies = ''; + } + + $cron_request = [ + 'url' => add_query_arg($query_args, $url), + 'key' => $query_args['doing_wp_cron'], + 'args' => [ + 'blocking' => false, + 'sslverify' => false, + 'timeout' => 0.01, + 'headers' => $header, + 'user-agent' => BackWPup::get_plugin_data('User-Agent'), + ], + ]; + + if (!empty($cookies)) { + foreach ($cookies as $name => $value) { + $cron_request['args']['cookies'][] = new WP_Http_Cookie(['name' => $name, 'value' => $value]); + } + } + + $cron_request = apply_filters('cron_request', $cron_request); + + if ($starttype === 'test') { + $cron_request['args']['timeout'] = 15; + $cron_request['args']['blocking'] = true; + } + + if (!in_array($starttype, ['runnowlink', 'runext', 'restartalt'], true)) { + delete_transient('doing_cron'); + + return wp_remote_post($cron_request['url'], $cron_request['args']); + } + + return $cron_request; + } + + /** + * Run baby run. + */ + public function run() + { + /** @var wpdb $wpdb */ + global $wpdb; + + //disable output buffering + if ($level = ob_get_level()) { + for ($i = 0; $i < $level; ++$i) { + ob_end_clean(); + } + } + + // Job can't run it is not created + if (empty($this->steps_todo) || empty($this->logfile)) { + $running_file = BackWPup::get_plugin_data('running_file'); + if (file_exists($running_file)) { + unlink($running_file); + } + + return; + } + + //Check double running and inactivity + $last_update = microtime(true) - $this->timestamp_last_update; + if (!empty($this->pid) && $last_update > 300) { + $this->log(__('Job restarts due to inactivity for more than 5 minutes.', 'backwpup'), E_USER_WARNING); + } elseif (!empty($this->pid)) { + return; + } + // set timestamp of script start + $this->timestamp_script_start = microtime(true); + //set Pid + $this->pid = self::get_pid(); + $this->uniqid = uniqid('', true); + //Early write new working file + $this->write_running_file(); + if ($this->is_debug()) { + @ini_set('error_log', $this->logfile); + error_reporting(-1); + } + @ini_set('display_errors', '0'); + @ini_set('log_errors', '1'); + @ini_set('html_errors', '0'); + @ini_set('report_memleaks', '1'); + @ini_set('zlib.output_compression', '0'); + @ini_set('implicit_flush', '0'); + @putenv('TMPDIR=' . BackWPup::get_plugin_data('TEMP')); + //Write Wordpress DB errors to log + $wpdb->suppress_errors(false); + $wpdb->hide_errors(); + //set wp max memory limit + @ini_set('memory_limit', apply_filters('admin_memory_limit', WP_MAX_MEMORY_LIMIT)); + //set error handler + if (!empty($this->logfile)) { + if ($this->is_debug()) { + set_error_handler([$this, 'log']); + } else { + set_error_handler([$this, 'log'], E_ALL ^ E_NOTICE); + } + } + set_exception_handler([$this, 'exception_handler']); + // execute function on job shutdown register_shutdown_function( array( $this, 'shutdown' ) ); + add_action('shutdown', [$this, 'shutdown']); + + if (function_exists('pcntl_signal')) { + $signals = [ + 'SIGHUP', //Term + 'SIGINT', //Term + 'SIGQUIT', //Core + 'SIGILL', //Core + //'SIGTRAP', //Core + 'SIGABRT', //Core + 'SIGBUS', //Core + 'SIGFPE', //Core + //'SIGKILL', //Term + 'SIGSEGV', //Core + //'SIGPIPE', Term + //'SIGALRM', Term + 'SIGTERM', //Term + 'SIGSTKFLT', //Term + 'SIGUSR1', //Term + 'SIGUSR2', //Term + //'SIGCHLD', //Ign + //'SIGCONT', //Cont + //'SIGSTOP', //Stop + //'SIGTSTP', //Stop + //'SIGTTIN', //Stop + //'SIGTTOU', //Stop + //'SIGURG', //Ign + 'SIGXCPU', //Core + 'SIGXFSZ', //Core + //'SIGVTALRM', //Term + //'SIGPROF', //Term + //'SIGWINCH', //Ign + //'SIGIO', //Term + 'SIGPWR', //Term + 'SIGSYS', //Core + ]; + $signals = apply_filters('backwpup_job_signals_to_handel', $signals); + + declare(ticks=1); + $this->signal = 0; + + foreach ($signals as $signal) { + if (defined($signal)) { + pcntl_signal(constant($signal), [$this, 'signal_handler'], false); + } + } + } + $job_types = BackWPup::get_job_types(); + //go step by step + foreach ($this->steps_todo as $this->step_working) { + //Check if step already done + if (in_array($this->step_working, $this->steps_done, true)) { + continue; + } + //calc step percent + if (count($this->steps_done) > 0) { + $this->step_percent = min( + round(count($this->steps_done) / count($this->steps_todo) * 100), + 100 + ); + } else { + $this->step_percent = 1; + } + // do step tries + while (true) { + if ($this->steps_data[$this->step_working]['STEP_TRY'] >= get_site_option('backwpup_cfg_jobstepretry')) { + $this->log(__('Step aborted: too many attempts!', 'backwpup'), E_USER_ERROR); + $this->temp = []; + $this->steps_done[] = $this->step_working; + $this->substeps_done = 0; + $this->substeps_todo = 0; + $this->do_restart(); + break; + } + + ++$this->steps_data[$this->step_working]['STEP_TRY']; + $done = false; + + //executes the methods of job process + if ($this->step_working == 'CREATE_ARCHIVE') { + $done = $this->create_archive(); + } elseif ($this->step_working == 'ENCRYPT_ARCHIVE') { + $done = $this->encrypt_archive(); + } elseif ($this->step_working == 'CREATE_MANIFEST') { + $done = $this->create_manifest(); + } elseif ($this->step_working == 'END') { + $this->end(); + break 2; + } elseif (strstr($this->step_working, 'JOB_')) { + $done = $job_types[str_replace('JOB_', '', $this->step_working)]->job_run($this); + } elseif (strstr($this->step_working, 'DEST_SYNC_')) { + $done = BackWPup::get_destination(str_replace('DEST_SYNC_', '', $this->step_working)) + ->job_run_sync($this) + ; + } elseif (strstr($this->step_working, 'DEST_')) { + $done = BackWPup::get_destination(str_replace('DEST_', '', $this->step_working)) + ->job_run_archive($this) + ; + } elseif (!empty($this->steps_data[$this->step_working]['CALLBACK'])) { + $done = $this->steps_data[$this->step_working]['CALLBACK']($this); + } + + // set step as done + if ($done === true) { + $this->temp = []; + $this->steps_done[] = $this->step_working; + $this->substeps_done = 0; + $this->substeps_todo = 0; + $this->update_working_data(true); + } + if (count($this->steps_done) < count($this->steps_todo) - 1) { + $this->do_restart(); + } + if ($done === true) { + break; + } + } + } + } + + /** + * Creates the backup archive. + */ + private function create_archive() + { + //load folders to backup + $folders_to_backup = $this->get_folders_to_backup(); + + $this->substeps_todo = $this->count_folder + 1; + + //initial settings for restarts in archiving + if (!isset($this->steps_data[$this->step_working]['on_file'])) { + $this->steps_data[$this->step_working]['on_file'] = ''; + } + if (!isset($this->steps_data[$this->step_working]['on_folder'])) { + $this->steps_data[$this->step_working]['on_folder'] = ''; + } + + if ($this->steps_data[$this->step_working]['on_folder'] == '' && $this->steps_data[$this->step_working]['on_file'] == '' && is_file($this->backup_folder . $this->backup_file)) { + unlink($this->backup_folder . $this->backup_file); + } + + if ($this->steps_data[$this->step_working]['SAVE_STEP_TRY'] != $this->steps_data[$this->step_working]['STEP_TRY']) { + $this->log( + sprintf( + __('%d. Trying to create backup archive …', 'backwpup'), + $this->steps_data[$this->step_working]['STEP_TRY'] + ), + E_USER_NOTICE + ); + } + + try { + $backup_archive = new BackWPup_Create_Archive($this->backup_folder . $this->backup_file); + + //show method for creation + if ($this->substeps_done == 0) { + $this->log(sprintf( + _x( + 'Compressing files as %s. Please be patient, this may take a moment.', + 'Archive compression method', + 'backwpup' + ), + $backup_archive->get_method() + )); + } + + //add extra files + if ($this->substeps_done == 0) { + if (!empty($this->additional_files_to_backup) && $this->substeps_done == 0) { + if ($this->is_debug()) { + $this->log(__('Adding Extra files to Archive', 'backwpup')); + } + + foreach ($this->additional_files_to_backup as $file) { + // Generate top-level filename + // Requires special handling in case of "use one folder above" + $archiveFilename = ltrim($this->get_destination_path_replacement(ABSPATH . basename($file)), '/'); + if ($backup_archive->add_file($file, $archiveFilename)) { + ++$this->count_files; + $this->count_files_size = $this->count_files_size + filesize($file); + $this->update_working_data(); + } else { + $backup_archive->close(); + $this->steps_data[$this->step_working]['on_file'] = ''; + $this->steps_data[$this->step_working]['on_folder'] = ''; + $this->log( + __('Cannot create backup archive correctly. Aborting creation.', 'backwpup'), + E_USER_ERROR + ); + + return false; + } + } + } + ++$this->substeps_done; + } + + //add normal files + while ($folder = array_shift($folders_to_backup)) { + //jump over already done folders + if (in_array($this->steps_data[$this->step_working]['on_folder'], $folders_to_backup, true)) { + continue; + } + if ($this->is_debug()) { + $this->log(sprintf(__('Archiving Folder: %s', 'backwpup'), $folder)); + } + $this->steps_data[$this->step_working]['on_folder'] = $folder; + $files_in_folder = $this->get_files_in_folder($folder); + //add empty folders + if (empty($files_in_folder)) { + $folder_name_in_archive = trim(ltrim($this->get_destination_path_replacement($folder), '/')); + if (!empty($folder_name_in_archive)) { + $backup_archive->add_empty_folder($folder, $folder_name_in_archive); + } + + continue; + } + //add files + while ($file = array_shift($files_in_folder)) { + //jump over already done files + if (in_array($this->steps_data[$this->step_working]['on_file'], $files_in_folder, true)) { + continue; + } + if ($this->maybe_sql_dump($file)) { + continue; + } + + $this->steps_data[$this->step_working]['on_file'] = $file; + //restart if needed + $restart_time = $this->get_restart_time(); + if ($restart_time <= 0) { + unset($backup_archive); + $this->do_restart_time(true); + + return false; + } + //generate filename in archive + $in_archive_filename = ltrim($this->get_destination_path_replacement($file), '/'); + //add file to archive + if ($backup_archive->add_file($file, $in_archive_filename)) { + ++$this->count_files; + $this->count_files_size = $this->count_files_size + filesize($file); + $this->update_working_data(); + } else { + $backup_archive->close(); + unset($backup_archive); + $this->steps_data[$this->step_working]['on_file'] = ''; + $this->steps_data[$this->step_working]['on_folder'] = ''; + $this->substeps_done = 0; + $this->backup_filesize = filesize($this->backup_folder . $this->backup_file); + if ($this->backup_filesize === false) { + $this->backup_filesize = PHP_INT_MAX; + } + $this->log( + __('Cannot create backup archive correctly. Aborting creation.', 'backwpup'), + E_USER_ERROR + ); + + return false; + } + } + $this->steps_data[$this->step_working]['on_file'] = ''; + ++$this->substeps_done; + } + $backup_archive->close(); + unset($backup_archive); + $this->log(__('Backup archive created.', 'backwpup'), E_USER_NOTICE); + } catch (Exception $e) { + $this->log($e->getMessage(), E_USER_ERROR, $e->getFile(), $e->getLine()); + + return false; + } + + $this->backup_filesize = filesize($this->backup_folder . $this->backup_file); + if ($this->backup_filesize === false) { + $this->backup_filesize = PHP_INT_MAX; + } + + if ($this->backup_filesize >= PHP_INT_MAX) { + $this->log( + __( + 'The Backup archive will be too large for file operations with this PHP Version. You might want to consider splitting the backup job in multiple jobs with less files each.', + 'backwpup' + ), + E_USER_ERROR + ); + $this->end(); + } else { + $this->log( + sprintf(__('Archive size is %s.', 'backwpup'), size_format($this->backup_filesize, 2)), + E_USER_NOTICE + ); + } + + $this->log( + sprintf( + __('%1$d Files with %2$s in Archive.', 'backwpup'), + $this->count_files, + size_format($this->count_files_size, 2) + ), + E_USER_NOTICE + ); + + return true; + } + + /** + * Encrypt Archive. + * + * Encrypt the backup archive. + * + * @return bool true when done, false otherwise + */ + private function encrypt_archive() + { + $encryptionType = get_site_option('backwpup_cfg_encryption'); + // Substeps is number of 128 KB chunks + $blockSize = 128 * 1024; + $this->substeps_todo = ceil($this->backup_filesize / $blockSize); + + if (!isset($this->steps_data[$this->step_working]['encrypted_filename'])) { + $this->steps_data[$this->step_working]['encrypted_filename'] = $this->backup_folder . $this->backup_file . '.encrypted'; + if (is_file($this->steps_data[$this->step_working]['encrypted_filename'])) { + unlink($this->steps_data[$this->step_working]['encrypted_filename']); + } + } + + if (!isset($this->steps_data[$this->step_working]['key'])) { + switch ($encryptionType) { + case self::ENCRYPTION_SYMMETRIC: + $this->steps_data[$this->step_working]['key'] = pack('H*', get_site_option('backwpup_cfg_encryptionkey')); + break; + + case self::ENCRYPTION_ASYMMETRIC: + $this->steps_data[$this->step_working]['key'] = get_site_option('backwpup_cfg_publickey'); + break; + } + + if (empty($this->steps_data[$this->step_working]['key'])) { + $this->log(__('No encryption key was provided. Aborting encryption.', 'backwpup'), E_USER_WARNING); + + return false; + } + } + + $key = $this->steps_data[$this->step_working]['key']; + + if ($this->steps_data[$this->step_working]['SAVE_STEP_TRY'] != $this->steps_data[$this->step_working]['STEP_TRY']) { + // Show initial log message + $this->log( + sprintf( + __('%d. Trying to encrypt archive …', 'backwpup'), + $this->steps_data[$this->step_working]['STEP_TRY'] + ), + E_USER_NOTICE + ); + } + + try { + $fileIn = Utils::streamFor(Utils::tryFopen($this->backup_folder . $this->backup_file, 'r')); + } catch (\RuntimeException $e) { + $this->log(__('Cannot open the archive for reading. Aborting encryption.', 'backwpup'), E_USER_ERROR); + + return false; + } + + try { + $fileOut = Utils::tryFopen($this->steps_data[$this->step_working]['encrypted_filename'], 'a+'); + } catch (\RuntimeException $e) { + $this->log(__('Cannot write the encrypted archive. Aborting encryption.', 'backwpup'), E_USER_ERROR); + + return false; + } + + $encryptor = null; + + switch ($encryptionType) { + case self::ENCRYPTION_SYMMETRIC: + $encryptor = EncryptionStream::fromSymmetric($key, Utils::streamFor($fileOut)); + break; + + case self::ENCRYPTION_ASYMMETRIC: + $encryptor = EncryptionStream::fromAsymmetric($key, Utils::streamFor($fileOut)); + break; + } + + if ($encryptor === null) { + $this->log(__('Could not initialize encryptor.', 'backwpup'), E_USER_ERROR); + + return false; + } + + $bytesRead = $this->substeps_done * $blockSize; + $fileIn->seek($bytesRead); + + while ($bytesRead < $this->backup_filesize) { + $data = $fileIn->read($blockSize); + $encryptor->write($data); + + ++$this->substeps_done; + $bytesRead += strlen($data); + $this->update_working_data(); + + // Should we restart? + $restartTime = $this->get_restart_time(); + if ($restartTime <= 0) { + $this->do_restart_time(true); + $fileIn->close(); + $encryptor->close(); + + return false; + } + } + + $fileIn->close(); + $encryptor->close(); + + $this->log( + sprintf(__('Encrypted %s of data.', 'backwpup'), size_format($bytesRead, 2)), + E_USER_NOTICE + ); + + // Remove the original file then rename the encrypted file + if (!unlink($this->backup_folder . $this->backup_file)) { + $this->log(__('Unable to delete unencrypted archive.', 'backwpup')); + + return false; + } + if (!rename( + $this->steps_data[$this->step_working]['encrypted_filename'], + $this->backup_folder . $this->backup_file + )) { + $this->log(__('Unable to rename encrypted archive.', 'backwpup')); + + return false; + } + + $this->backup_filesize = filesize($this->backup_folder . $this->backup_file); + $this->log(__('Archive has been successfully encrypted.', 'backwpup')); + + return true; + } + + /** + * Get list of Folders for backup. + * + * @return string[] Folder list + */ + public function get_folders_to_backup() + { + $file = BackWPup::get_plugin_data('temp') . 'backwpup-' . BackWPup::get_plugin_data('hash') . '-folder.php'; + + if (!file_exists($file)) { + return []; + } + + $folders = []; + + $file_data = file($file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); + + foreach ($file_data as $folder) { + $folder = trim(str_replace(['count_folder = count($folders); + + return $folders; + } + + /** + * Get back a array of files to backup in the selected folder. + * + * @param string $folder The folder to get the files from + * + * @return string[] Files to backup + */ + public function get_files_in_folder($folder) + { + $files = []; + $folder = trailingslashit($folder); + + if (!is_dir($folder)) { + $this->log( + sprintf(_x('Folder %s does not exist', 'Folder name', 'backwpup'), $folder), + E_USER_WARNING + ); + + return $files; + } + + if (!is_readable($folder)) { + $this->log( + sprintf(_x('Folder %s is not readable', 'Folder name', 'backwpup'), $folder), + E_USER_WARNING + ); + + return $files; + } + + try { + $dir = new BackWPup_Directory($folder); + + foreach ($dir as $file) { + if ($file->isDot() || $file->isDir()) { + continue; + } + + $path = BackWPup_Path_Fixer::slashify($file->getPathname()); + + foreach ($this->exclude_from_backup as $exclusion) { //exclude files + $exclusion = trim($exclusion); + if (stripos($path, $exclusion) !== false && !empty($exclusion)) { + continue 2; + } + } + + if ($this->job['backupexcludethumbs'] && strpos( + $folder, + BackWPup_File::get_upload_dir() + ) !== false && preg_match( + '/\\-[0-9]{1,4}x[0-9]{1,4}.+\\.(jpg|png|gif)$/i', + $file->getFilename() + )) { + continue; + } + + if ($file->isLink()) { + $this->log( + sprintf(__('Link "%s" not following.', 'backwpup'), $file->getPathname()), + E_USER_WARNING + ); + } elseif (!$file->isReadable()) { + $this->log( + sprintf(__('File "%s" is not readable!', 'backwpup'), $file->getPathname()), + E_USER_WARNING + ); + } else { + $file_size = $file->getSize(); + if (!is_int($file_size) || $file_size < 0 || $file_size > 2147483647) { + $this->log( + sprintf( + __( + 'File size of “%s” cannot be retrieved. File might be too large and will not be added to queue.', + 'backwpup' + ), + $file->getPathname() . ' ' . $file_size + ), + E_USER_WARNING + ); + + continue; + } + + $files[] = BackWPup_Path_Fixer::slashify(realpath($path)); + } + } + } catch (UnexpectedValueException $e) { + $this->log(sprintf(__('Could not open path: %s'), $e->getMessage()), E_USER_WARNING); + } + + return $files; + } + + /** + * Get job restart time. + * + * @return int remaining time + */ + public function get_restart_time() + { + if (php_sapi_name() == 'cli') { + return 300; + } + + $job_max_execution_time = get_site_option('backwpup_cfg_jobmaxexecutiontime'); + + if (empty($job_max_execution_time)) { + return 300; + } + + $execution_time = microtime(true) - $this->timestamp_script_start; + + return $job_max_execution_time - $execution_time - 3; + } + + /** + * Do a job restart. + * + * @param bool $do_restart_now should time restart now be done + * + * @return int remaining time + */ + public function do_restart_time($do_restart_now = false) + { + if (php_sapi_name() == 'cli') { + return 300; + } + + //do restart after signal is send + if ($this->signal !== 0) { + $this->steps_data[$this->step_working]['SAVE_STEP_TRY'] = $this->steps_data[$this->step_working]['STEP_TRY']; + --$this->steps_data[$this->step_working]['STEP_TRY']; + $this->do_restart(true); + } + + $job_max_execution_time = get_site_option('backwpup_cfg_jobmaxexecutiontime'); + + if (empty($job_max_execution_time)) { + return 300; + } + + $execution_time = microtime(true) - $this->timestamp_script_start; + + // do restart 3 sec. before max. execution time + if ($do_restart_now || $execution_time >= ($job_max_execution_time - 3)) { + $this->steps_data[$this->step_working]['SAVE_STEP_TRY'] = $this->steps_data[$this->step_working]['STEP_TRY']; + --$this->steps_data[$this->step_working]['STEP_TRY']; + $this->do_restart(true); + } + + return $job_max_execution_time - $execution_time; + } + + /** + * create manifest file. + * + * @return bool + */ + public function create_manifest() + { + $this->substeps_todo = 3; + + $this->log(sprintf( + __('%d. Trying to generate a manifest file …', 'backwpup'), + $this->steps_data[$this->step_working]['STEP_TRY'] + )); + + //build manifest + $manifest = []; + // add blog information + $manifest['blog_info']['url'] = home_url(); + $manifest['blog_info']['wpurl'] = site_url(); + $manifest['blog_info']['prefix'] = $GLOBALS[\wpdb::class]->prefix; + $manifest['blog_info']['description'] = get_option('blogdescription'); + $manifest['blog_info']['stylesheet_directory'] = get_template_directory_uri(); + $manifest['blog_info']['activate_plugins'] = wp_get_active_and_valid_plugins(); + $manifest['blog_info']['activate_theme'] = wp_get_theme()->get('Name'); + $manifest['blog_info']['admin_email'] = get_option('admin_email'); + $manifest['blog_info']['charset'] = get_bloginfo('charset'); + $manifest['blog_info']['version'] = BackWPup::get_plugin_data('wp_version'); + $manifest['blog_info']['backwpup_version'] = BackWPup::get_plugin_data('version'); + $manifest['blog_info']['language'] = get_bloginfo('language'); + $manifest['blog_info']['name'] = get_bloginfo('name'); + $manifest['blog_info']['abspath'] = ABSPATH; + $manifest['blog_info']['uploads'] = wp_upload_dir(null, false, true); + $manifest['blog_info']['contents']['basedir'] = WP_CONTENT_DIR; + $manifest['blog_info']['contents']['baseurl'] = WP_CONTENT_URL; + $manifest['blog_info']['plugins']['basedir'] = WP_PLUGIN_DIR; + $manifest['blog_info']['plugins']['baseurl'] = WP_PLUGIN_URL; + $manifest['blog_info']['themes']['basedir'] = get_theme_root(); + $manifest['blog_info']['themes']['baseurl'] = get_theme_root_uri(); // Add job settings - $manifest['job_settings'] = array( + $manifest['job_settings'] = [ 'dbdumptype' => $this->job['dbdumptype'], 'dbdumpfile' => $this->job['dbdumpfile'], 'dbdumpfilecompression' => $this->job['dbdumpfilecompression'], @@ -2087,580 +2261,548 @@ public function create_manifest() { 'backuptype' => $this->job['backuptype'], 'archiveformat' => $this->job['archiveformat'], 'dbdumpexclude' => $this->job['dbdumpexclude'], + ]; + + // add archive info + foreach ($this->additional_files_to_backup as $file) { + $manifest['archive']['extra_files'][] = basename($file); + } + if (isset($this->steps_data['JOB_FILE'])) { + if ($this->job['backuproot']) { + $manifest['archive']['abspath'] = trailingslashit($this->get_destination_path_replacement(ABSPATH)); + } + if ($this->job['backupuploads']) { + $manifest['archive']['uploads'] = trailingslashit($this->get_destination_path_replacement(BackWPup_File::get_upload_dir())); + } + if ($this->job['backupcontent']) { + $manifest['archive']['contents'] = trailingslashit($this->get_destination_path_replacement(WP_CONTENT_DIR)); + } + if ($this->job['backupplugins']) { + $manifest['archive']['plugins'] = trailingslashit($this->get_destination_path_replacement(WP_PLUGIN_DIR)); + } + if ($this->job['backupthemes']) { + $manifest['archive']['themes'] = trailingslashit($this->get_destination_path_replacement(get_theme_root())); + } + } + + if (!file_put_contents(BackWPup::get_plugin_data('TEMP') . 'manifest.json', json_encode($manifest))) { + return false; + } + $this->substeps_done = 1; + + //Create backwpup_readme.txt + $readme_text = __('You may have noticed the manifest.json file in this archive.', 'backwpup') . PHP_EOL; + $readme_text .= __( + 'manifest.json might be needed for later restoring a backup from this archive.', + 'backwpup' + ) . PHP_EOL; + $readme_text .= __( + 'Please leave manifest.json untouched and in place. Otherwise it is safe to be ignored.', + 'backwpup' + ) . PHP_EOL; + if (!file_put_contents(BackWPup::get_plugin_data('TEMP') . 'backwpup_readme.txt', $readme_text)) { + return false; + } + $this->substeps_done = 2; + + //add file to backup files + if (is_readable(BackWPup::get_plugin_data('TEMP') . 'manifest.json')) { + $this->additional_files_to_backup[] = BackWPup::get_plugin_data('TEMP') . 'manifest.json'; + $this->additional_files_to_backup[] = BackWPup::get_plugin_data('TEMP') . 'backwpup_readme.txt'; + $this->log(sprintf( + __('Added manifest.json file with %1$s to backup file list.', 'backwpup'), + size_format(filesize(BackWPup::get_plugin_data('TEMP') . 'manifest.json'), 2) + )); + } + $this->substeps_done = 3; + + return true; + } + + /** + * @param $jobid + */ + public static function start_cli($jobid) + { + if (php_sapi_name() != 'cli') { + return; + } + + //define DOING_CRON to prevent caching + if (!defined('DOING_CRON')) { + define('DOING_CRON', true); + } + + //load text domain + $log_level = get_site_option('backwpup_cfg_loglevel', 'normal_translated'); + if (strstr($log_level, 'translated')) { + BackWPup::load_text_domain(); + } else { + add_filter('override_load_textdomain', '__return_true'); + $GLOBALS['l10n'] = []; + } + + $jobid = absint($jobid); + + //Logs Folder + $log_folder = get_site_option('backwpup_cfg_logfolder'); + $log_folder = BackWPup_File::get_absolute_path($log_folder); + + //check job id exists + $jobids = BackWPup_Option::get_job_ids(); + if (!in_array($jobid, $jobids, true)) { + exit(__('Wrong BackWPup JobID', 'backwpup')); + } + //check folders + $log_folder_message = BackWPup_File::check_folder($log_folder); + if (!empty($log_folder_message)) { + exit($log_folder_message); + } + $log_folder_message = BackWPup_File::check_folder(BackWPup::get_plugin_data('TEMP'), true); + if (!empty($log_folder_message)) { + exit($log_folder_message); + } + //check running job + if (file_exists(BackWPup::get_plugin_data('running_file'))) { + exit(__('A BackWPup job is already running', 'backwpup')); + } + //start class + $backwpup_job_object = new self(); + $backwpup_job_object->create('runcli', (int) $jobid); + $backwpup_job_object->run(); + } + + /** + * disable caches. + */ + public static function disable_caches() + { + //Special settings + @putenv('nokeepalive=1'); + @ini_set('zlib.output_compression', 'Off'); + + // deactivate caches + if (!defined('DONOTCACHEDB')) { + define('DONOTCACHEDB', true); + } + if (!defined('DONOTCACHEPAGE')) { + define('DONOTCACHEPAGE', true); + } + } + + /** + * Reads a BackWPup logfile header and gives back a array of information. + * + * @param string $logfile full logfile path + * + * @return array|bool + */ + public static function read_logheader($logfile) + { + $usedmetas = [ + 'date' => 'logtime', + 'backwpup_logtime' => 'logtime', //old value of date + 'backwpup_errors' => 'errors', + 'backwpup_warnings' => 'warnings', + 'backwpup_jobid' => 'jobid', + 'backwpup_jobname' => 'name', + 'backwpup_jobtype' => 'type', + 'backwpup_jobruntime' => 'runtime', + 'backwpup_backupfilesize' => 'backupfilesize', + ]; + + //get metadata of logfile + $metas = []; + if (is_readable($logfile)) { + if ('.gz' == substr($logfile, -3)) { + $metas = (array) get_meta_tags('compress.zlib://' . $logfile); + } else { + $metas = (array) get_meta_tags($logfile); + } + } + + //only output needed data + foreach ($usedmetas as $keyword => $field) { + if (isset($metas[$keyword])) { + $joddata[$field] = $metas[$keyword]; + } else { + $joddata[$field] = ''; + } + } + + //convert date + if (isset($metas['date'])) { + $joddata['logtime'] = strtotime($metas['date']) + (get_option('gmt_offset') * 3600); + } + + //use file create date if none + if (empty($joddata['logtime'])) { + $joddata['logtime'] = filectime($logfile); + } + + return $joddata; + } + + public static function user_abort() + { + /** @var BackWPup_Job $job_object */ + $job_object = BackWPup_Job::get_working_data(); + + unlink(BackWPup::get_plugin_data('running_file')); + + //if job not working currently abort it this way for message + $not_worked_time = microtime(true) - $job_object->timestamp_last_update; + $restart_time = get_site_option('backwpup_cfg_jobmaxexecutiontime'); + if (empty($restart_time)) { + $restart_time = 60; + } + if (empty($job_object->pid) || $not_worked_time > $restart_time) { + $job_object->user_abort = true; + $job_object->update_working_data(); + } + } + + /** + * Delete some data on cloned objects. + */ + public function __clone() + { + $this->temp = []; + $this->run = []; + } + + /** + * Signal handler. + * + * @param $signal_send + */ + public function signal_handler($signal_send) + { + //known signals + $signals = [ + 'SIGHUP' => [ + 'description' => _x( + 'Hangup detected on controlling terminal or death of controlling process', + 'SIGHUP: Please see http://man7.org/linux/man-pages/man7/signal.7.html for details', + 'backwpup' + ), + 'error' => E_USER_ERROR, + ], + 'SIGINT' => [ + 'description' => _x( + 'Interrupt from keyboard', + 'SIGINT: Please see http://man7.org/linux/man-pages/man7/signal.7.html for details', + 'backwpup' + ), + 'error' => E_USER_ERROR, + ], + 'SIGQUIT' => [ + 'description' => _x( + 'Quit from keyboard', + 'SIGQUIT: Please see http://man7.org/linux/man-pages/man7/signal.7.html for details', + 'backwpup' + ), + 'error' => E_USER_ERROR, + ], + 'SIGILL' => [ + 'description' => _x( + 'Illegal Instruction', + 'SIGILL: Please see http://man7.org/linux/man-pages/man7/signal.7.html for details', + 'backwpup' + ), + 'error' => E_USER_ERROR, + ], + 'SIGABRT' => [ + 'description' => _x( + 'Abort signal from abort(3)', + 'SIGABRT: Please see http://man7.org/linux/man-pages/man7/signal.7.html for details', + 'backwpup' + ), + 'error' => E_USER_NOTICE, + ], + 'SIGBUS' => [ + 'description' => _x( + 'Bus error (bad memory access)', + 'SIGBUS: Please see http://man7.org/linux/man-pages/man7/signal.7.html for details', + 'backwpup' + ), + 'error' => E_USER_ERROR, + ], + 'SIGFPE' => [ + 'description' => _x( + 'Floating point exception', + 'SIGFPE: Please see http://man7.org/linux/man-pages/man7/signal.7.html for details', + 'backwpup' + ), + 'error' => E_USER_ERROR, + ], + 'SIGSEGV' => [ + 'description' => _x( + 'Invalid memory reference', + 'SIGSEGV: Please see http://man7.org/linux/man-pages/man7/signal.7.html for details', + 'backwpup' + ), + 'error' => E_USER_ERROR, + ], + 'SIGTERM' => [ + 'description' => _x( + 'Termination signal', + 'SIGTERM: Please see http://man7.org/linux/man-pages/man7/signal.7.html for details', + 'backwpup' + ), + 'error' => E_USER_WARNING, + ], + 'SIGSTKFLT' => [ + 'description' => _x( + 'Stack fault on coprocessor', + 'SIGSTKFLT: Please see http://man7.org/linux/man-pages/man7/signal.7.html for details', + 'backwpup' + ), + 'error' => E_USER_ERROR, + ], + 'SIGUSR1' => [ + 'description' => _x( + 'User-defined signal 1', + 'SIGUSR1: Please see http://man7.org/linux/man-pages/man7/signal.7.html for details', + 'backwpup' + ), + 'error' => E_USER_NOTICE, + ], + 'SIGUSR2' => [ + 'description' => _x( + 'User-defined signal 2', + 'SIGUSR2: Please see http://man7.org/linux/man-pages/man7/signal.7.html for details', + 'backwpup' + ), + 'error' => E_USER_NOTICE, + ], + 'SIGURG' => [ + 'description' => _x( + 'Urgent condition on socket', + 'SIGURG: Please see http://man7.org/linux/man-pages/man7/signal.7.html for details', + 'backwpup' + ), + 'error' => E_USER_NOTICE, + ], + 'SIGXCPU' => [ + 'description' => _x( + 'CPU time limit exceeded', + 'SIGXCPU: Please see http://man7.org/linux/man-pages/man7/signal.7.html for details', + 'backwpup' + ), + 'error' => E_USER_ERROR, + ], + 'SIGXFSZ' => [ + 'description' => _x( + 'File size limit exceeded', + 'SIGXFSZ: Please see http://man7.org/linux/man-pages/man7/signal.7.html for details', + 'backwpup' + ), + 'error' => E_USER_ERROR, + ], + 'SIGPWR' => [ + 'description' => _x( + 'Power failure', + 'SIGPWR: Please see http://man7.org/linux/man-pages/man7/signal.7.html for details', + 'backwpup' + ), + 'error' => E_USER_ERROR, + ], + 'SIGSYS' => [ + 'description' => _x( + 'Bad argument to routine', + 'SIGSYS: Please see http://man7.org/linux/man-pages/man7/signal.7.html for details', + 'backwpup' + ), + 'error' => E_USER_ERROR, + ], + ]; + + foreach ($signals as $signal => $config) { + if (defined($signal) && $signal_send === constant($signal)) { + $this->log( + sprintf( + __('Signal "%1$s" (%2$s) is sent to script!', 'backwpup'), + $signal, + $config['description'] + ), + $config['error'] + ); + $this->signal = $signal_send; + break; + } + } + } + + /** + * Shutdown function is call if script terminates try to make a restart if needed. + * + * Prepare the job for start + * + * @internal param int the signal that terminates the job + */ + public function shutdown() + { + //Put last error to log if one + $lasterror = error_get_last(); + if ($lasterror['type'] === E_ERROR || $lasterror['type'] === E_PARSE || $lasterror['type'] === E_CORE_ERROR || $lasterror['type'] === E_CORE_WARNING || $lasterror['type'] === E_COMPILE_ERROR || $lasterror['type'] === E_COMPILE_WARNING) { + $this->log($lasterror['type'], $lasterror['message'], $lasterror['file'], $lasterror['line']); + } + + $error = false; + if (function_exists('pcntl_get_last_error')) { + $error = pcntl_get_last_error(); + if (!empty($error)) { + $error_msg = pcntl_strerror($error); + if (!empty($error_msg)) { + $error = '(' . $error . ') ' . $error_msg; + } + } + if (!empty($error)) { + $this->log(sprintf(__('System: %s', 'backwpup'), $error), E_USER_ERROR); + } + } + + if (function_exists('posix_get_last_error') && !$error) { + $error = posix_get_last_error(); + if (!empty($error)) { + $error_msg = posix_strerror($error); + if (!empty($error_msg)) { + $error = '(' . $error . ') ' . $error_msg; + } + } + if (!empty($error)) { + $this->log(sprintf(__('System: %s', 'backwpup'), $error), E_USER_ERROR); + } + } + + $this->do_restart(true); + } + + /** + * The uncouth exception handler. + * + * @param object $exception + */ + public function exception_handler($exception) + { + $this->log( + sprintf( + __('Exception caught in %1$s: %2$s', 'backwpup'), + get_class($exception), + $exception->getMessage() + ), + E_USER_ERROR, + $exception->getFile(), + $exception->getLine() ); + } + + /** + * Callback for the CURLOPT_READFUNCTION that submit the transferred bytes + * to build the process bar. + * + * @param $curl_handle + * @param $file_handle + * @param $read_count + * + * @return string + * + * @internal param $out + */ + public function curl_read_callback($curl_handle, $file_handle, $read_count) + { + $data = null; + if (!empty($file_handle) && is_numeric($read_count)) { + $data = fread($file_handle, $read_count); + } - // add archive info - foreach ( $this->additional_files_to_backup as $file ) { - $manifest['archive']['extra_files'][] = basename( $file ); - } - if ( isset( $this->steps_data['JOB_FILE'] ) ) { - if ( $this->job['backuproot'] ) { - $manifest['archive']['abspath'] = trailingslashit( $this->get_destination_path_replacement( ABSPATH ) ); - } - if ( $this->job['backupuploads'] ) { - $manifest['archive']['uploads'] = trailingslashit( $this->get_destination_path_replacement( BackWPup_File::get_upload_dir() ) ); - } - if ( $this->job['backupcontent'] ) { - $manifest['archive']['contents'] = trailingslashit( $this->get_destination_path_replacement( WP_CONTENT_DIR ) ); - } - if ( $this->job['backupplugins'] ) { - $manifest['archive']['plugins'] = trailingslashit( $this->get_destination_path_replacement( WP_PLUGIN_DIR ) ); - } - if ( $this->job['backupthemes'] ) { - $manifest['archive']['themes'] = trailingslashit( $this->get_destination_path_replacement( get_theme_root() ) ); - } - } - - if ( ! file_put_contents( BackWPup::get_plugin_data( 'TEMP' ) . 'manifest.json', json_encode( $manifest ) ) ) { - return false; - } - $this->substeps_done = 1; - - //Create backwpup_readme.txt - $readme_text = __( 'You may have noticed the manifest.json file in this archive.', 'backwpup' ) . PHP_EOL; - $readme_text .= __( 'manifest.json might be needed for later restoring a backup from this archive.', - 'backwpup' ) . PHP_EOL; - $readme_text .= __( 'Please leave manifest.json untouched and in place. Otherwise it is safe to be ignored.', - 'backwpup' ) . PHP_EOL; - if ( ! file_put_contents( BackWPup::get_plugin_data( 'TEMP' ) . 'backwpup_readme.txt', $readme_text ) ) { - return false; - } - $this->substeps_done = 2; - - //add file to backup files - if ( is_readable( BackWPup::get_plugin_data( 'TEMP' ) . 'manifest.json' ) ) { - $this->additional_files_to_backup[] = BackWPup::get_plugin_data( 'TEMP' ) . 'manifest.json'; - $this->additional_files_to_backup[] = BackWPup::get_plugin_data( 'TEMP' ) . 'backwpup_readme.txt'; - $this->log( sprintf( __( 'Added manifest.json file with %1$s to backup file list.', 'backwpup' ), - size_format( filesize( BackWPup::get_plugin_data( 'TEMP' ) . 'manifest.json' ), 2 ) ) ); - } - $this->substeps_done = 3; - - return true; - } - - /** - * @param $jobid - */ - public static function start_cli( $jobid ) { - - if ( php_sapi_name() != 'cli' ) { - return; - } - - //define DOING_CRON to prevent caching - if ( ! defined( 'DOING_CRON' ) ) { - define( 'DOING_CRON', true ); - } - - //load text domain - $log_level = get_site_option( 'backwpup_cfg_loglevel', 'normal_translated' ); - if ( strstr( $log_level, 'translated' ) ) { - BackWPup::load_text_domain(); - } else { - add_filter( 'override_load_textdomain', '__return_true' ); - $GLOBALS['l10n'] = array(); - } - - $jobid = absint( $jobid ); - - //Logs Folder - $log_folder = get_site_option( 'backwpup_cfg_logfolder' ); - $log_folder = BackWPup_File::get_absolute_path( $log_folder ); - - //check job id exists - $jobids = BackWPup_Option::get_job_ids(); - if ( ! in_array( $jobid, $jobids, true ) ) { - die( __( 'Wrong BackWPup JobID', 'backwpup' ) ); - } - //check folders - $log_folder_message = BackWPup_File::check_folder( $log_folder ); - if ( ! empty( $log_folder_message ) ) { - die( $log_folder_message ); - } - $log_folder_message = BackWPup_File::check_folder( BackWPup::get_plugin_data( 'TEMP' ), true ); - if ( ! empty( $log_folder_message ) ) { - die( $log_folder_message ); - } - //check running job - if ( file_exists( BackWPup::get_plugin_data( 'running_file' ) ) ) { - die( __( 'A BackWPup job is already running', 'backwpup' ) ); - } - - //start class - $backwpup_job_object = new self(); - $backwpup_job_object->create( 'runcli', (int) $jobid ); - $backwpup_job_object->run(); - } - - /** - * disable caches - */ - public static function disable_caches() { - - //Special settings - @putenv( 'nokeepalive=1' ); - @ini_set( 'zlib.output_compression', 'Off' ); - - // deactivate caches - if ( ! defined( 'DONOTCACHEDB' ) ) { - define( 'DONOTCACHEDB', true ); - } - if ( ! defined( 'DONOTCACHEPAGE' ) ) { - define( 'DONOTCACHEPAGE', true ); - } - } - - /** - * - * Reads a BackWPup logfile header and gives back a array of information - * - * @param string $logfile full logfile path - * - * @return array|bool - */ - public static function read_logheader( $logfile ) { - - $usedmetas = array( - "date" => "logtime", - "backwpup_logtime" => "logtime", //old value of date - "backwpup_errors" => "errors", - "backwpup_warnings" => "warnings", - "backwpup_jobid" => "jobid", - "backwpup_jobname" => "name", - "backwpup_jobtype" => "type", - "backwpup_jobruntime" => "runtime", - "backwpup_backupfilesize" => "backupfilesize", - ); - - //get metadata of logfile - $metas = array(); - if ( is_readable( $logfile ) ) { - if ( '.gz' == substr( $logfile, - 3 ) ) { - $metas = (array) get_meta_tags( 'compress.zlib://' . $logfile ); - } else { - $metas = (array) get_meta_tags( $logfile ); - } - } - - //only output needed data - foreach ( $usedmetas as $keyword => $field ) { - if ( isset( $metas[ $keyword ] ) ) { - $joddata[ $field ] = $metas[ $keyword ]; - } else { - $joddata[ $field ] = ''; - } - } - - //convert date - if ( isset( $metas['date'] ) ) { - $joddata['logtime'] = strtotime( $metas['date'] ) + ( get_option( 'gmt_offset' ) * 3600 ); - } - - //use file create date if none - if ( empty( $joddata['logtime'] ) ) { - $joddata['logtime'] = filectime( $logfile ); - } - - return $joddata; - } - - public static function user_abort() { - - /* @var $job_object BackWPup_Job */ - $job_object = BackWPup_Job::get_working_data(); - - unlink( BackWPup::get_plugin_data( 'running_file' ) ); - - //if job not working currently abort it this way for message - $not_worked_time = microtime( true ) - $job_object->timestamp_last_update; - $restart_time = get_site_option( 'backwpup_cfg_jobmaxexecutiontime' ); - if ( empty( $restart_time ) ) { - $restart_time = 60; - } - if ( empty( $job_object->pid ) || $not_worked_time > $restart_time ) { - $job_object->user_abort = true; - $job_object->update_working_data(); - } - - } - - /** - * Check whether exec has been disabled. - * - * @access public - * @static - * @return bool - */ - public static function is_exec() { - - // Is function avail - if ( ! function_exists( 'exec' ) ) { - return false; - } - - // Is shell_exec disabled? - if ( in_array( 'exec', array_map( 'trim', explode( ',', @ini_get( 'disable_functions' ) ) ), true ) ) { - return false; - } - - // Can we issue a simple echo command? - $output = exec( 'echo backwpupechotest' ); - if ( $output != 'backwpupechotest' ) { - return false; - } - - return true; - - } - - /** - * Check if mysqldump is installed in the server. - * - * @param string $dbdumpmysqlfolder Path to mysqldump binary - * - * @return bool - */ - public static function mysqldump_installed( $dbdumpmysqlfolder ) { - - if ( false === self::is_exec() ) { - return false; - } - - if ( ! file_exists( $dbdumpmysqlfolder ) || ! is_executable( $dbdumpmysqlfolder ) ) { - return false; - } - - return true; - } - - /** - * Returns mysqldump error message. - * - * @param string $dbdumpmysqlfolder Path to mysqldump binary - * - * @return string - */ - public static function mysqldump_error_message( $dbdumpmysqlfolder ) { - - $output = ''; - - if ( false === self::is_exec() ) { - $output = '' . __( 'exec command is not active.', 'backwpup' ) . ''; - - return $output; - } - - if ( ! file_exists( $dbdumpmysqlfolder ) || ! is_executable( $dbdumpmysqlfolder ) ) { - $output = '' . __( 'mysqldump binary not found.', 'backwpup' ) . ''; - - return $output; - } - - return $output; - } - - /** - * Delete some data on cloned objects - */ - public function __clone() { - - $this->temp = array(); - $this->run = array(); - } - - /** - * Signal handler - * - * @param $signal_send - */ - public function signal_handler( $signal_send ) { - - //known signals - $signals = array( - 'SIGHUP' => array( - 'description' => _x( 'Hangup detected on controlling terminal or death of controlling process', - 'SIGHUP: Please see http://man7.org/linux/man-pages/man7/signal.7.html for details', - 'backwpup' ), - 'error' => E_USER_ERROR, - ), - 'SIGINT' => array( - 'description' => _x( 'Interrupt from keyboard', - 'SIGINT: Please see http://man7.org/linux/man-pages/man7/signal.7.html for details', - 'backwpup' ), - 'error' => E_USER_ERROR, - ), - 'SIGQUIT' => array( - 'description' => _x( 'Quit from keyboard', - 'SIGQUIT: Please see http://man7.org/linux/man-pages/man7/signal.7.html for details', - 'backwpup' ), - 'error' => E_USER_ERROR, - ), - 'SIGILL' => array( - 'description' => _x( 'Illegal Instruction', - 'SIGILL: Please see http://man7.org/linux/man-pages/man7/signal.7.html for details', - 'backwpup' ), - 'error' => E_USER_ERROR, - ), - 'SIGABRT' => array( - 'description' => _x( 'Abort signal from abort(3)', - 'SIGABRT: Please see http://man7.org/linux/man-pages/man7/signal.7.html for details', - 'backwpup' ), - 'error' => E_USER_NOTICE, - ), - 'SIGBUS' => array( - 'description' => _x( 'Bus error (bad memory access)', - 'SIGBUS: Please see http://man7.org/linux/man-pages/man7/signal.7.html for details', - 'backwpup' ), - 'error' => E_USER_ERROR, - ), - 'SIGFPE' => array( - 'description' => _x( 'Floating point exception', - 'SIGFPE: Please see http://man7.org/linux/man-pages/man7/signal.7.html for details', - 'backwpup' ), - 'error' => E_USER_ERROR, - ), - 'SIGSEGV' => array( - 'description' => _x( 'Invalid memory reference', - 'SIGSEGV: Please see http://man7.org/linux/man-pages/man7/signal.7.html for details', - 'backwpup' ), - 'error' => E_USER_ERROR, - ), - 'SIGTERM' => array( - 'description' => _x( 'Termination signal', - 'SIGTERM: Please see http://man7.org/linux/man-pages/man7/signal.7.html for details', - 'backwpup' ), - 'error' => E_USER_WARNING, - ), - 'SIGSTKFLT' => array( - 'description' => _x( 'Stack fault on coprocessor', - 'SIGSTKFLT: Please see http://man7.org/linux/man-pages/man7/signal.7.html for details', - 'backwpup' ), - 'error' => E_USER_ERROR, - ), - 'SIGUSR1' => array( - 'description' => _x( 'User-defined signal 1', - 'SIGUSR1: Please see http://man7.org/linux/man-pages/man7/signal.7.html for details', - 'backwpup' ), - 'error' => E_USER_NOTICE, - ), - 'SIGUSR2' => array( - 'description' => _x( 'User-defined signal 2', - 'SIGUSR2: Please see http://man7.org/linux/man-pages/man7/signal.7.html for details', - 'backwpup' ), - 'error' => E_USER_NOTICE, - ), - 'SIGURG' => array( - 'description' => _x( 'Urgent condition on socket', - 'SIGURG: Please see http://man7.org/linux/man-pages/man7/signal.7.html for details', - 'backwpup' ), - 'error' => E_USER_NOTICE, - ), - 'SIGXCPU' => array( - 'description' => _x( 'CPU time limit exceeded', - 'SIGXCPU: Please see http://man7.org/linux/man-pages/man7/signal.7.html for details', - 'backwpup' ), - 'error' => E_USER_ERROR, - ), - 'SIGXFSZ' => array( - 'description' => _x( 'File size limit exceeded', - 'SIGXFSZ: Please see http://man7.org/linux/man-pages/man7/signal.7.html for details', - 'backwpup' ), - 'error' => E_USER_ERROR, - ), - 'SIGPWR' => array( - 'description' => _x( 'Power failure', - 'SIGPWR: Please see http://man7.org/linux/man-pages/man7/signal.7.html for details', - 'backwpup' ), - 'error' => E_USER_ERROR, - ), - 'SIGSYS' => array( - 'description' => _x( 'Bad argument to routine', - 'SIGSYS: Please see http://man7.org/linux/man-pages/man7/signal.7.html for details', - 'backwpup' ), - 'error' => E_USER_ERROR, - ), - ); - - foreach ( $signals as $signal => $config ) { - if ( defined( $signal ) && $signal_send === constant( $signal ) ) { - $this->log( sprintf( __( 'Signal "%1$s" (%2$s) is sent to script!', 'backwpup' ), - $signal, - $config['description'] ), - $config['error'] ); - $this->signal = $signal_send; - break; - } - } - - } - - /** - * - * Shutdown function is call if script terminates try to make a restart if needed - * - * Prepare the job for start - * - * @internal param int the signal that terminates the job - */ - public function shutdown() { - - //Put last error to log if one - $lasterror = error_get_last(); - if ( $lasterror['type'] === E_ERROR || $lasterror['type'] === E_PARSE || $lasterror['type'] === E_CORE_ERROR || $lasterror['type'] === E_CORE_WARNING || $lasterror['type'] === E_COMPILE_ERROR || $lasterror['type'] === E_COMPILE_WARNING ) { - $this->log( $lasterror['type'], $lasterror['message'], $lasterror['file'], $lasterror['line'] ); - } - - $error = false; - if ( function_exists( 'pcntl_get_last_error' ) ) { - $error = pcntl_get_last_error(); - if ( ! empty( $error ) ) { - $error_msg = pcntl_strerror( $error ); - if ( ! empty( $error_msg ) ) { - $error = '(' . $error . ') ' . $error_msg; - } - } - if ( ! empty( $error ) ) { - $this->log( sprintf( __( 'System: %s', 'backwpup' ), $error ), E_USER_ERROR ); - } - } - - if ( function_exists( 'posix_get_last_error' ) && ! $error ) { - $error = posix_get_last_error(); - if ( ! empty( $error ) ) { - $error_msg = posix_strerror( $error ); - if ( ! empty( $error_msg ) ) { - $error = '(' . $error . ') ' . $error_msg; - } - } - if ( ! empty( $error ) ) { - $this->log( sprintf( __( 'System: %s', 'backwpup' ), $error ), E_USER_ERROR ); - } - } - - $this->do_restart( true ); - } - - /** - * - * The uncouth exception handler - * - * @param object $exception - */ - public function exception_handler( $exception ) { - - $this->log( sprintf( __( 'Exception caught in %1$s: %2$s', 'backwpup' ), - get_class( $exception ), - $exception->getMessage() ), - E_USER_ERROR, - $exception->getFile(), - $exception->getLine() ); - } - - /** - * - * Callback for the CURLOPT_READFUNCTION that submit the transferred bytes - * to build the process bar - * - * @param $curl_handle - * @param $file_handle - * @param $read_count - * - * @return string - * @internal param $out - */ - public function curl_read_callback( $curl_handle, $file_handle, $read_count ) { - - $data = null; - if ( ! empty( $file_handle ) && is_numeric( $read_count ) ) { - $data = fread( $file_handle, $read_count ); - } - - if ( $this->job['backuptype'] == 'sync' ) { - return $data; - } - - $length = ( is_numeric( $read_count ) ) ? $read_count : strlen( $read_count ); - $this->substeps_done = $this->substeps_done + $length; - $this->update_working_data(); - - return $data; - } - - /** - * For storing and getting data in/from a extra temp file - * - * @param string $storage The name of the storage - * @param array $data data to save in storage - * - * @return array|mixed|null data from storage - */ - public function data_storage( $storage = null, $data = null ) { - - if ( empty( $storage ) ) { - return $data; - } - - $storage = strtolower( $storage ); - - $file = BackWPup::get_plugin_data( 'temp' ) . 'backwpup-' . BackWPup::get_plugin_data( 'hash' ) . '-' . $storage . '.json'; - - if ( ! empty( $data ) ) { - file_put_contents( $file, json_encode( $data ) ); - } elseif ( is_readable( $file ) ) { - $json = file_get_contents( $file ); - $data = json_decode( $json, true ); - } - - return $data; - } - - /** - * Add a Folders to Folder list that should be backup - * - * @param array $folders folder to add - * @param bool $new overwrite existing file - */ - public function add_folders_to_backup( $folders = array(), $new = false ) { - - if ( ! is_array( $folders ) ) { - $folders = (array) $folders; - } - - $file = BackWPup::get_plugin_data( 'temp' ) . 'backwpup-' . BackWPup::get_plugin_data( 'hash' ) . '-folder.php'; - - if ( ! file_exists( $file ) || $new ) { - file_put_contents( $file, 'job['jobid'], 'dbdumpfile' ); - - $dump_files = array( - $file_name . '.sql', - $file_name . '.sql.gz', - $file_name . '.xml', - ); - - return in_array( basename( $file ), $dump_files, true ); - } + if ($this->job['backuptype'] == 'sync') { + return $data; + } + + $length = (is_numeric($read_count)) ? $read_count : strlen($read_count); + $this->substeps_done = $this->substeps_done + $length; + $this->update_working_data(); + + return $data; + } + + /** + * For storing and getting data in/from a extra temp file. + * + * @param string $storage The name of the storage + * @param array $data data to save in storage + * + * @return array|mixed|null data from storage + */ + public function data_storage($storage = null, $data = null) + { + if (empty($storage)) { + return $data; + } + + $storage = strtolower($storage); + + $file = BackWPup::get_plugin_data('temp') . 'backwpup-' . BackWPup::get_plugin_data('hash') . '-' . $storage . '.json'; + + if (!empty($data)) { + file_put_contents($file, json_encode($data)); + } elseif (is_readable($file)) { + $json = file_get_contents($file); + $data = json_decode($json, true); + } + + return $data; + } + + /** + * Add a Folders to Folder list that should be backup. + * + * @param array $folders folder to add + * @param bool $new overwrite existing file + */ + public function add_folders_to_backup($folders = [], $new = false) + { + if (!is_array($folders)) { + $folders = (array) $folders; + } + + $file = BackWPup::get_plugin_data('temp') . 'backwpup-' . BackWPup::get_plugin_data('hash') . '-folder.php'; + + if (!file_exists($file) || $new) { + file_put_contents($file, 'job['jobid'], 'dbdumpfile'); + + $dump_files = [ + $file_name . '.sql', + $file_name . '.sql.gz', + $file_name . '.xml', + ]; + + return in_array(basename($file), $dump_files, true); + } } diff --git a/inc/class-jobtype-dbcheck.php b/inc/class-jobtype-dbcheck.php index 87328c91..01733419 100644 --- a/inc/class-jobtype-dbcheck.php +++ b/inc/class-jobtype-dbcheck.php @@ -1,148 +1,157 @@ info[ 'ID' ] = 'DBCHECK'; - $this->info[ 'name' ] = __( 'DB Check', 'backwpup' ); - $this->info[ 'description' ] = __( 'Check database tables', 'backwpup' ); - $this->info[ 'URI' ] = __( 'http://backwpup.com', 'backwpup' ); - $this->info[ 'author' ] = 'Inpsyde GmbH'; - $this->info[ 'authorURI' ] = __( 'http://inpsyde.com', 'backwpup' ); - $this->info[ 'version' ] = BackWPup::get_plugin_data( 'Version' ); - - } - - /** - * @return array - */ - public function option_defaults() { - return array( 'dbcheckwponly' => TRUE, 'dbcheckrepair' => FALSE ); - } - - - /** - * @param $jobid - */ - public function edit_tab( $jobid ) { - ?> -

+class BackWPup_JobType_DBCheck extends BackWPup_JobTypes +{ + public function __construct() + { + $this->info['ID'] = 'DBCHECK'; + $this->info['name'] = __('DB Check', 'backwpup'); + $this->info['description'] = __('Check database tables', 'backwpup'); + $this->info['URI'] = __('http://backwpup.com', 'backwpup'); + $this->info['author'] = 'Inpsyde GmbH'; + $this->info['authorURI'] = __('http://inpsyde.com', 'backwpup'); + $this->info['version'] = BackWPup::get_plugin_data('Version'); + } + + /** + * @return array + */ + public function option_defaults() + { + return ['dbcheckwponly' => true, 'dbcheckrepair' => false]; + } + + /** + * @param $jobid + */ + public function edit_tab($jobid) + { + ?> +

- + - +
log( sprintf( __( '%d. Trying to check database …', 'backwpup' ), $job_object->steps_data[ $job_object->step_working ][ 'STEP_TRY' ] ) ); - if ( ! isset( $job_object->steps_data[ $job_object->step_working ][ 'DONETABLE' ] ) || ! is_array( $job_object->steps_data[ $job_object->step_working ][ 'DONETABLE' ] ) ) - $job_object->steps_data[ $job_object->step_working ][ 'DONETABLE' ] = array(); - - //to check - $tables = array(); - $tablestype = array(); - $restables = $wpdb->get_results( 'SHOW FULL TABLES FROM `' . DB_NAME . '`', ARRAY_N ); - foreach ( $restables as $table ) { - if ( $job_object->job[ 'dbcheckwponly' ] && substr( $table[ 0 ], 0, strlen( $wpdb->prefix ) ) != $wpdb->prefix ) - continue; - $tables[ ] = $table[ 0 ]; - $tablestype[ $table[ 0 ] ] = $table[ 1 ]; - } - - //Set num - $job_object->substeps_todo = sizeof( $tables ); - - //Get table status - $status = array(); - $resstatus = $wpdb->get_results( "SHOW TABLE STATUS FROM `" . DB_NAME . "`", ARRAY_A ); - foreach ( $resstatus as $tablestatus ) { - $status[ $tablestatus[ 'Name' ] ] = $tablestatus; - } - - //check tables - if ( $job_object->substeps_todo > 0 ) { - foreach ( $tables as $table ) { - if ( in_array( $table, $job_object->steps_data[ $job_object->step_working ][ 'DONETABLE' ], true ) ) - continue; - - if ( $tablestype[ $table ] == 'VIEW' ) { - $job_object->log( sprintf( __( 'Table %1$s is a view. Not checked.', 'backwpup' ), $table ) ); - continue; - } - - if ( $status[ $table ][ 'Engine' ] != 'MyISAM' && $status[ $table ][ 'Engine' ] != 'InnoDB' ) { - $job_object->log( sprintf( __( 'Table %1$s is not a MyISAM/InnoDB table. Not checked.', 'backwpup' ), $table ) ); - continue; - } - - //CHECK TABLE funktioniert bei MyISAM- und InnoDB-Tabellen (http://dev.mysql.com/doc/refman/5.1/de/check-table.html) - $check = $wpdb->get_row( "CHECK TABLE `" . $table . "` MEDIUM", OBJECT ); - if ( strtolower( $check->Msg_text ) == 'ok' ) { - if ( $job_object->is_debug() ) { - $job_object->log( sprintf( __( 'Result of table check for %1$s is: %2$s', 'backwpup' ), $table, $check->Msg_text ) ); - } - } elseif ( strtolower( $check->Msg_type ) == 'warning' ) { - $job_object->log( sprintf( __( 'Result of table check for %1$s is: %2$s', 'backwpup' ), $table, $check->Msg_text ), E_USER_WARNING ); - } else { - $job_object->log( sprintf( __( 'Result of table check for %1$s is: %2$s', 'backwpup' ), $table, $check->Msg_text ), E_USER_ERROR ); - } - //Try to Repair table - if ( ! empty( $job_object->job[ 'dbcheckrepair' ] ) && strtolower( $check->Msg_text ) != 'ok' && $status[ $table ][ 'Engine' ] == 'MyISAM' ) { - $repair = $wpdb->get_row( 'REPAIR TABLE `' . $table . '` EXTENDED', OBJECT ); - if ( strtolower( $repair->Msg_text ) == 'ok' ) { - $job_object->log( sprintf( __( 'Result of table repair for %1$s is: %2$s', 'backwpup' ), $table, $repair->Msg_text ) ); - } elseif ( strtolower( $repair->Msg_type ) == 'warning' ) { - $job_object->log( sprintf( __( 'Result of table repair for %1$s is: %2$s', 'backwpup' ), $table, $repair->Msg_text ), E_USER_WARNING ); - } else { - $job_object->log( sprintf( __( 'Result of table repair for %1$s is: %2$s', 'backwpup' ), $table, $repair->Msg_text ), E_USER_ERROR ); - } - } - $job_object->steps_data[ $job_object->step_working ][ 'DONETABLE' ][ ] = $table; - $job_object->substeps_done ++; - } - $job_object->log( __( 'Database check done!', 'backwpup' ) ); - } - else { - $job_object->log( __( 'No tables to check.', 'backwpup' ) ); - } - - unset( $job_object->steps_data[ $job_object->step_working ][ 'DONETABLE' ] ); - return TRUE; - } + } + + /** + * @param $jobid + */ + public function edit_form_post_save($jobid) + { + BackWPup_Option::update($jobid, 'dbcheckwponly', !empty($_POST['dbcheckwponly'])); + BackWPup_Option::update($jobid, 'dbcheckrepair', !empty($_POST['dbcheckrepair'])); + } + + /** + * @param $job_object + * + * @return bool + */ + public function job_run(BackWPup_Job $job_object) + { + /** @var wpdb $wpdb */ + global $wpdb; + + $job_object->log(sprintf(__('%d. Trying to check database …', 'backwpup'), $job_object->steps_data[$job_object->step_working]['STEP_TRY'])); + if (!isset($job_object->steps_data[$job_object->step_working]['DONETABLE']) || !is_array($job_object->steps_data[$job_object->step_working]['DONETABLE'])) { + $job_object->steps_data[$job_object->step_working]['DONETABLE'] = []; + } + + //to check + $tables = []; + $tablestype = []; + $restables = $wpdb->get_results('SHOW FULL TABLES FROM `' . DB_NAME . '`', ARRAY_N); + + foreach ($restables as $table) { + if ($job_object->job['dbcheckwponly'] && substr($table[0], 0, strlen($wpdb->prefix)) != $wpdb->prefix) { + continue; + } + $tables[] = $table[0]; + $tablestype[$table[0]] = $table[1]; + } + + //Set num + $job_object->substeps_todo = sizeof($tables); + + //Get table status + $status = []; + $resstatus = $wpdb->get_results('SHOW TABLE STATUS FROM `' . DB_NAME . '`', ARRAY_A); + + foreach ($resstatus as $tablestatus) { + $status[$tablestatus['Name']] = $tablestatus; + } + + //check tables + if ($job_object->substeps_todo > 0) { + foreach ($tables as $table) { + if (in_array($table, $job_object->steps_data[$job_object->step_working]['DONETABLE'], true)) { + continue; + } + + if ($tablestype[$table] == 'VIEW') { + $job_object->log(sprintf(__('Table %1$s is a view. Not checked.', 'backwpup'), $table)); + + continue; + } + + if ($status[$table]['Engine'] != 'MyISAM' && $status[$table]['Engine'] != 'InnoDB') { + $job_object->log(sprintf(__('Table %1$s is not a MyISAM/InnoDB table. Not checked.', 'backwpup'), $table)); + + continue; + } + + //CHECK TABLE funktioniert bei MyISAM- und InnoDB-Tabellen (http://dev.mysql.com/doc/refman/5.1/de/check-table.html) + $check = $wpdb->get_row('CHECK TABLE `' . $table . '` MEDIUM', OBJECT); + if (strtolower($check->Msg_text) == 'ok') { + if ($job_object->is_debug()) { + $job_object->log(sprintf(__('Result of table check for %1$s is: %2$s', 'backwpup'), $table, $check->Msg_text)); + } + } elseif (strtolower($check->Msg_type) == 'warning') { + $job_object->log(sprintf(__('Result of table check for %1$s is: %2$s', 'backwpup'), $table, $check->Msg_text), E_USER_WARNING); + } else { + $job_object->log(sprintf(__('Result of table check for %1$s is: %2$s', 'backwpup'), $table, $check->Msg_text), E_USER_ERROR); + } + //Try to Repair table + if (!empty($job_object->job['dbcheckrepair']) && strtolower($check->Msg_text) != 'ok' && $status[$table]['Engine'] == 'MyISAM') { + $repair = $wpdb->get_row('REPAIR TABLE `' . $table . '` EXTENDED', OBJECT); + if (strtolower($repair->Msg_text) == 'ok') { + $job_object->log(sprintf(__('Result of table repair for %1$s is: %2$s', 'backwpup'), $table, $repair->Msg_text)); + } elseif (strtolower($repair->Msg_type) == 'warning') { + $job_object->log(sprintf(__('Result of table repair for %1$s is: %2$s', 'backwpup'), $table, $repair->Msg_text), E_USER_WARNING); + } else { + $job_object->log(sprintf(__('Result of table repair for %1$s is: %2$s', 'backwpup'), $table, $repair->Msg_text), E_USER_ERROR); + } + } + $job_object->steps_data[$job_object->step_working]['DONETABLE'][] = $table; + ++$job_object->substeps_done; + } + $job_object->log(__('Database check done!', 'backwpup')); + } else { + $job_object->log(__('No tables to check.', 'backwpup')); + } + + unset($job_object->steps_data[$job_object->step_working]['DONETABLE']); + + return true; + } } diff --git a/inc/class-jobtype-dbdump.php b/inc/class-jobtype-dbdump.php index 62f2f02b..8fa893b9 100644 --- a/inc/class-jobtype-dbdump.php +++ b/inc/class-jobtype-dbdump.php @@ -1,269 +1,273 @@ info[ 'ID' ] = 'DBDUMP'; - $this->info[ 'name' ] = __( 'DB Backup', 'backwpup' ); - $this->info[ 'description' ] = __( 'Database backup', 'backwpup' ); - $this->info[ 'URI' ] = __( 'http://backwpup.com', 'backwpup' ); - $this->info[ 'author' ] = 'Inpsyde GmbH'; - $this->info[ 'authorURI' ] = __( 'http://inpsyde.com', 'backwpup' ); - $this->info[ 'version' ] = BackWPup::get_plugin_data( 'Version' ); - - } - - /** - * @return bool - */ - public function creates_file() { - - return TRUE; - } - - /** - * @return array - */ - public function option_defaults() { - global $wpdb; - /* @var wpdb $wpdb */ - - $defaults = array( - 'dbdumpexclude' => array(), 'dbdumpfile' => sanitize_file_name( DB_NAME ), 'dbdumptype' => 'sql', 'dbdumpfilecompression' => '' - ); - //set only wordpress tables as default - $dbtables = $wpdb->get_results( 'SHOW TABLES FROM `' . DB_NAME . '`', ARRAY_N ); - foreach ( $dbtables as $dbtable) { - if ( $wpdb->prefix != substr( $dbtable[ 0 ], 0, strlen( $wpdb->prefix ) ) ) - $defaults[ 'dbdumpexclude' ][] = $dbtable[ 0 ]; - } - - return $defaults; - } - - - /** - * @param $jobid - */ - public function edit_tab( $jobid ) { - global $wpdb; - /* @var wpdb $wpdb */ - - ?> +class BackWPup_JobType_DBDump extends BackWPup_JobTypes +{ + public function __construct() + { + $this->info['ID'] = 'DBDUMP'; + $this->info['name'] = __('DB Backup', 'backwpup'); + $this->info['description'] = __('Database backup', 'backwpup'); + $this->info['URI'] = __('http://backwpup.com', 'backwpup'); + $this->info['author'] = 'Inpsyde GmbH'; + $this->info['authorURI'] = __('http://inpsyde.com', 'backwpup'); + $this->info['version'] = BackWPup::get_plugin_data('Version'); + } + + /** + * @return bool + */ + public function creates_file() + { + return true; + } + + /** + * @return array + */ + public function option_defaults() + { + /** @var wpdb $wpdb */ + global $wpdb; + + $defaults = [ + 'dbdumpexclude' => [], 'dbdumpfile' => sanitize_file_name(DB_NAME), 'dbdumptype' => 'sql', 'dbdumpfilecompression' => '', + ]; + //set only wordpress tables as default + $dbtables = $wpdb->get_results('SHOW TABLES FROM `' . DB_NAME . '`', ARRAY_N); + + foreach ($dbtables as $dbtable) { + if ($wpdb->prefix != substr($dbtable[0], 0, strlen($wpdb->prefix))) { + $defaults['dbdumpexclude'][] = $dbtable[0]; + } + } + + return $defaults; + } + + /** + * @param $jobid + */ + public function edit_tab($jobid) + { + /** @var wpdb $wpdb */ + global $wpdb; ?> -

+

- + - + - +
-   -   +   +   get_results( 'SHOW FULL TABLES FROM `' . DB_NAME . '`', ARRAY_N ); - echo '
'; - $next_row = ceil( count( $tables ) / 3 ); - $counter = 0; - foreach ( $tables as $table ) { - $tabletype = ''; - if ( $table[ 1 ] !== 'BASE TABLE' ) { - $tabletype = ' (' . strtolower( esc_html( $table[ 1 ] ) ) . ')'; - } - echo '
'; - $counter++; - if ($next_row <= $counter) { - echo '
'; - $counter = 0; - } - } - echo '
'; - ?> + $tables = $wpdb->get_results('SHOW FULL TABLES FROM `' . DB_NAME . '`', ARRAY_N); + echo '
'; + $next_row = ceil(count($tables) / 3); + $counter = 0; + + foreach ($tables as $table) { + $tabletype = ''; + if ($table[1] !== 'BASE TABLE') { + $tabletype = ' (' . strtolower(esc_html($table[1])) . ')'; + } + echo '
'; + ++$counter; + if ($next_row <= $counter) { + echo '
'; + $counter = 0; + } + } + echo '
'; ?>
.sql
' . __( 'none', 'backwpup' ). '
'; - if ( function_exists( 'gzopen' ) ) { - echo '
'; - } else { - echo '
'; - } - ?> + echo '
'; + if (function_exists('gzopen')) { + echo '
'; + } else { + echo '
'; + } ?>
get_results( 'SHOW TABLES FROM `' . DB_NAME . '`', ARRAY_N ); - foreach ( $dbtables as $dbtable ) { - if ( ! in_array( $dbtable[ 0 ], $checked_db_tables, true ) ) { - $dbdumpexclude[ ] = $dbtable[ 0 ]; - } - } - BackWPup_Option::update( $id, 'dbdumpexclude', $dbdumpexclude ); - - } - - /** - * Dumps the Database - * - * @param $job_object BackWPup_Job - * - * @return bool - */ - public function job_run( BackWPup_Job $job_object ) { - - $job_object->substeps_todo = 1; - - if ( $job_object->steps_data[ $job_object->step_working ]['SAVE_STEP_TRY'] != $job_object->steps_data[ $job_object->step_working ][ 'STEP_TRY' ] ) - $job_object->log( sprintf( __( '%d. Try to backup database …', 'backwpup' ), $job_object->steps_data[ $job_object->step_working ][ 'STEP_TRY' ] ) ); - - //build filename - if ( empty( $job_object->steps_data[ $job_object->step_working ][ 'dbdumpfile' ] ) ) - $job_object->steps_data[ $job_object->step_working ][ 'dbdumpfile' ] = $job_object->generate_filename( $job_object->job[ 'dbdumpfile' ], 'sql' ) . $job_object->job[ 'dbdumpfilecompression' ]; - - try { - - //Connect to Database - $sql_dump = new BackWPup_MySQLDump( array( - 'dumpfile' => BackWPup::get_plugin_data( 'TEMP' ) . $job_object->steps_data[ $job_object->step_working ][ 'dbdumpfile' ], - ) ); - - if ( $job_object->steps_data[ $job_object->step_working ]['SAVE_STEP_TRY'] != $job_object->steps_data[ $job_object->step_working ][ 'STEP_TRY' ] ) { - $job_object->log( sprintf( __( 'Connected to database %1$s on %2$s', 'backwpup' ), DB_NAME, DB_HOST ) ); - } - - - //Exclude Tables - foreach ( $sql_dump->tables_to_dump as $key => $table ) { - if ( in_array( $table, $job_object->job[ 'dbdumpexclude' ], true ) ) - unset( $sql_dump->tables_to_dump[ $key ] ); - } - - //set steps must done - $job_object->substeps_todo = count( $sql_dump->tables_to_dump ); - - if ( $job_object->substeps_todo == 0 ) { - $job_object->log( __( 'No tables to backup.', 'backwpup' ), E_USER_WARNING ); - unset( $sql_dump ); - - return TRUE; - } - - //dump head - if ( ! isset( $job_object->steps_data[ $job_object->step_working ][ 'is_head' ] ) ) { - $sql_dump->dump_head( TRUE ); - $job_object->steps_data[ $job_object->step_working ][ 'is_head' ] = TRUE; - } - //dump tables - $i = 0; - foreach( $sql_dump->tables_to_dump as $table ) { - if ( $i < $job_object->substeps_done ) { - $i++; - continue; - } - if ( empty( $job_object->steps_data[ $job_object->step_working ][ 'tables' ][ $table ] ) ) { - $num_records = $sql_dump->dump_table_head( $table ); - $job_object->steps_data[ $job_object->step_working ][ 'tables' ][ $table ] = array( 'start' => 0, - 'length' => 1000 ); - if ( $job_object->is_debug() ) { - $job_object->log( sprintf( __( 'Backup database table "%s" with "%s" records', 'backwpup' ), $table, $num_records ) ); - } - } - $while = true; - while ( $while ) { - $dump_start_time = microtime( TRUE ); - $done_records = $sql_dump->dump_table( $table ,$job_object->steps_data[ $job_object->step_working ][ 'tables' ][ $table ][ 'start' ], $job_object->steps_data[ $job_object->step_working ][ 'tables' ][ $table ][ 'length' ] ); - $dump_time = microtime( TRUE ) - $dump_start_time; - if ( empty( $dump_time ) ) - $dump_time = 0.01; - if ( $done_records < $job_object->steps_data[ $job_object->step_working ][ 'tables' ][ $table ][ 'length' ] ) //that is the last chunk - $while = FALSE; - $job_object->steps_data[ $job_object->step_working ][ 'tables' ][ $table ][ 'start' ] = $job_object->steps_data[ $job_object->step_working ][ 'tables' ][ $table ][ 'start' ] + $done_records; - // dump time per record and set next length - $length = ceil( ( $done_records / $dump_time ) * $job_object->get_restart_time() ); - if ( $length > 25000 || 0 >= $job_object->get_restart_time() ) - $length = 25000; - if ( $length < 1000 ) - $length = 1000; - $job_object->steps_data[ $job_object->step_working ][ 'tables' ][ $table ][ 'length' ] = $length; - $job_object->do_restart_time(); - } - $sql_dump->dump_table_footer( $table ); - $job_object->substeps_done++; - $i++; - $job_object->update_working_data(); - } - //dump footer - $sql_dump->dump_footer(); - unset( $sql_dump ); - - } catch ( Exception $e ) { - $job_object->log( $e->getMessage(), E_USER_ERROR, $e->getFile(), $e->getLine() ); - unset( $sql_dump ); - return FALSE; - } - - $filesize = filesize( BackWPup::get_plugin_data( 'TEMP' ) . $job_object->steps_data[ $job_object->step_working ][ 'dbdumpfile' ] ); - - if ( ! is_file( BackWPup::get_plugin_data( 'TEMP' ) . $job_object->steps_data[ $job_object->step_working ][ 'dbdumpfile' ] ) || $filesize < 1 ) { - $job_object->log( __( 'MySQL backup file not created', 'backwpup' ), E_USER_ERROR ); - return FALSE; - } else { - $job_object->additional_files_to_backup[ ] = BackWPup::get_plugin_data( 'TEMP' ) . $job_object->steps_data[ $job_object->step_working ][ 'dbdumpfile' ]; - $job_object->log( sprintf( __( 'Added database dump "%1$s" with %2$s to backup file list', 'backwpup' ), $job_object->steps_data[ $job_object->step_working ][ 'dbdumpfile' ], size_format( $filesize, 2 ) ) ); - } - - //cleanups - unset( $job_object->steps_data[ $job_object->step_working ][ 'tables' ] ); - - $job_object->log( __( 'Database backup done!', 'backwpup' ) ); - - return TRUE; - } - - public function admin_print_scripts() { - - if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) { - wp_enqueue_script( 'backwpupjobtypedbdump', BackWPup::get_plugin_data( 'URL' ) . '/assets/js/page_edit_jobtype_dbdump.js', array('jquery'), time(), TRUE ); - } else { - wp_enqueue_script( 'backwpupjobtypedbdump', BackWPup::get_plugin_data( 'URL' ) . '/assets/js/page_edit_jobtype_dbdump.min.js', array('jquery'), BackWPup::get_plugin_data( 'Version' ), TRUE ); - } - } - - + } + + /** + * @param $id + */ + public function edit_form_post_save($id) + { + /** @var wpdb $wpdb */ + global $wpdb; + + if ($_POST['dbdumpfilecompression'] === '' || $_POST['dbdumpfilecompression'] === '.gz') { + BackWPup_Option::update($id, 'dbdumpfilecompression', $_POST['dbdumpfilecompression']); + } + BackWPup_Option::update($id, 'dbdumpfile', BackWPup_Job::sanitize_file_name($_POST['dbdumpfile'])); + //selected tables + $dbdumpexclude = []; + $checked_db_tables = []; + if (isset($_POST['tabledb'])) { + foreach ($_POST['tabledb'] as $dbtable) { + $checked_db_tables[] = sanitize_text_field($dbtable); + } + } + $dbtables = $wpdb->get_results('SHOW TABLES FROM `' . DB_NAME . '`', ARRAY_N); + + foreach ($dbtables as $dbtable) { + if (!in_array($dbtable[0], $checked_db_tables, true)) { + $dbdumpexclude[] = $dbtable[0]; + } + } + BackWPup_Option::update($id, 'dbdumpexclude', $dbdumpexclude); + } + + /** + * Dumps the Database. + * + * @param $job_object BackWPup_Job + * + * @return bool + */ + public function job_run(BackWPup_Job $job_object) + { + $job_object->substeps_todo = 1; + + if ($job_object->steps_data[$job_object->step_working]['SAVE_STEP_TRY'] != $job_object->steps_data[$job_object->step_working]['STEP_TRY']) { + $job_object->log(sprintf(__('%d. Try to backup database …', 'backwpup'), $job_object->steps_data[$job_object->step_working]['STEP_TRY'])); + } + + //build filename + if (empty($job_object->steps_data[$job_object->step_working]['dbdumpfile'])) { + $job_object->steps_data[$job_object->step_working]['dbdumpfile'] = $job_object->generate_filename($job_object->job['dbdumpfile'], 'sql') . $job_object->job['dbdumpfilecompression']; + } + + try { + //Connect to Database + $sql_dump = new BackWPup_MySQLDump([ + 'dumpfile' => BackWPup::get_plugin_data('TEMP') . $job_object->steps_data[$job_object->step_working]['dbdumpfile'], + ]); + + if ($job_object->steps_data[$job_object->step_working]['SAVE_STEP_TRY'] != $job_object->steps_data[$job_object->step_working]['STEP_TRY']) { + $job_object->log(sprintf(__('Connected to database %1$s on %2$s', 'backwpup'), DB_NAME, DB_HOST)); + } + + //Exclude Tables + foreach ($sql_dump->tables_to_dump as $key => $table) { + if (in_array($table, $job_object->job['dbdumpexclude'], true)) { + unset($sql_dump->tables_to_dump[$key]); + } + } + + //set steps must done + $job_object->substeps_todo = count($sql_dump->tables_to_dump); + + if ($job_object->substeps_todo == 0) { + $job_object->log(__('No tables to backup.', 'backwpup'), E_USER_WARNING); + unset($sql_dump); + + return true; + } + + //dump head + if (!isset($job_object->steps_data[$job_object->step_working]['is_head'])) { + $sql_dump->dump_head(true); + $job_object->steps_data[$job_object->step_working]['is_head'] = true; + } + //dump tables + $i = 0; + + foreach ($sql_dump->tables_to_dump as $table) { + if ($i < $job_object->substeps_done) { + ++$i; + + continue; + } + if (empty($job_object->steps_data[$job_object->step_working]['tables'][$table])) { + $num_records = $sql_dump->dump_table_head($table); + $job_object->steps_data[$job_object->step_working]['tables'][$table] = ['start' => 0, + 'length' => 1000, ]; + if ($job_object->is_debug()) { + $job_object->log(sprintf(__('Backup database table "%s" with "%s" records', 'backwpup'), $table, $num_records)); + } + } + $while = true; + + while ($while) { + $dump_start_time = microtime(true); + $done_records = $sql_dump->dump_table($table, $job_object->steps_data[$job_object->step_working]['tables'][$table]['start'], $job_object->steps_data[$job_object->step_working]['tables'][$table]['length']); + $dump_time = microtime(true) - $dump_start_time; + if (empty($dump_time)) { + $dump_time = 0.01; + } + if ($done_records < $job_object->steps_data[$job_object->step_working]['tables'][$table]['length']) { //that is the last chunk + $while = false; + } + $job_object->steps_data[$job_object->step_working]['tables'][$table]['start'] = $job_object->steps_data[$job_object->step_working]['tables'][$table]['start'] + $done_records; + // dump time per record and set next length + $length = ceil(($done_records / $dump_time) * $job_object->get_restart_time()); + if ($length > 25000 || 0 >= $job_object->get_restart_time()) { + $length = 25000; + } + if ($length < 1000) { + $length = 1000; + } + $job_object->steps_data[$job_object->step_working]['tables'][$table]['length'] = $length; + $job_object->do_restart_time(); + } + $sql_dump->dump_table_footer($table); + ++$job_object->substeps_done; + ++$i; + $job_object->update_working_data(); + } + //dump footer + $sql_dump->dump_footer(); + unset($sql_dump); + } catch (Exception $e) { + $job_object->log($e->getMessage(), E_USER_ERROR, $e->getFile(), $e->getLine()); + + return false; + } + + $filesize = filesize(BackWPup::get_plugin_data('TEMP') . $job_object->steps_data[$job_object->step_working]['dbdumpfile']); + + if (!is_file(BackWPup::get_plugin_data('TEMP') . $job_object->steps_data[$job_object->step_working]['dbdumpfile']) || $filesize < 1) { + $job_object->log(__('MySQL backup file not created', 'backwpup'), E_USER_ERROR); + + return false; + } + $job_object->additional_files_to_backup[] = BackWPup::get_plugin_data('TEMP') . $job_object->steps_data[$job_object->step_working]['dbdumpfile']; + $job_object->log(sprintf(__('Added database dump "%1$s" with %2$s to backup file list', 'backwpup'), $job_object->steps_data[$job_object->step_working]['dbdumpfile'], size_format($filesize, 2))); + + //cleanups + unset($job_object->steps_data[$job_object->step_working]['tables']); + + $job_object->log(__('Database backup done!', 'backwpup')); + + return true; + } + + public function admin_print_scripts() + { + if (defined('SCRIPT_DEBUG') && SCRIPT_DEBUG) { + wp_enqueue_script('backwpupjobtypedbdump', BackWPup::get_plugin_data('URL') . '/assets/js/page_edit_jobtype_dbdump.js', ['jquery'], time(), true); + } else { + wp_enqueue_script('backwpupjobtypedbdump', BackWPup::get_plugin_data('URL') . '/assets/js/page_edit_jobtype_dbdump.min.js', ['jquery'], BackWPup::get_plugin_data('Version'), true); + } + } } diff --git a/inc/class-jobtype-file.php b/inc/class-jobtype-file.php index 552d18bd..360459da 100644 --- a/inc/class-jobtype-file.php +++ b/inc/class-jobtype-file.php @@ -1,42 +1,40 @@ info[ 'ID' ] = 'FILE'; - $this->info[ 'name' ] = __( 'Files', 'backwpup' ); - $this->info[ 'description' ] = __( 'File backup', 'backwpup' ); - $this->info[ 'URI' ] = __( 'http://backwpup.com', 'backwpup' ); - $this->info[ 'author' ] = 'Inpsyde GmbH'; - $this->info[ 'authorURI' ] = __( 'http://inpsyde.com', 'backwpup' ); - $this->info[ 'version' ] = BackWPup::get_plugin_data( 'Version' ); - - } - - public function admin_print_scripts() { - - if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) { - wp_enqueue_script( 'backwpupjobtypefile', BackWPup::get_plugin_data( 'URL' ) . '/assets/js/page_edit_jobtype_file.js', array( 'jquery' ), time(), TRUE ); - } else { - wp_enqueue_script( 'backwpupjobtypefile', BackWPup::get_plugin_data( 'URL' ) . '/assets/js/page_edit_jobtype_file.min.js', array( 'jquery' ), BackWPup::get_plugin_data( 'Version' ), TRUE ); - } - } +class BackWPup_JobType_File extends BackWPup_JobTypes +{ + public function __construct() + { + $this->info['ID'] = 'FILE'; + $this->info['name'] = __('Files', 'backwpup'); + $this->info['description'] = __('File backup', 'backwpup'); + $this->info['URI'] = __('http://backwpup.com', 'backwpup'); + $this->info['author'] = 'Inpsyde GmbH'; + $this->info['authorURI'] = __('http://inpsyde.com', 'backwpup'); + $this->info['version'] = BackWPup::get_plugin_data('Version'); + } - /** - * @return bool - */ - public function creates_file() { + public function admin_print_scripts() + { + if (defined('SCRIPT_DEBUG') && SCRIPT_DEBUG) { + wp_enqueue_script('backwpupjobtypefile', BackWPup::get_plugin_data('URL') . '/assets/js/page_edit_jobtype_file.js', ['jquery'], time(), true); + } else { + wp_enqueue_script('backwpupjobtypefile', BackWPup::get_plugin_data('URL') . '/assets/js/page_edit_jobtype_file.min.js', ['jquery'], BackWPup::get_plugin_data('Version'), true); + } + } - return TRUE; - } + /** + * @return bool + */ + public function creates_file() + { + return true; + } /** * @return array */ public function option_defaults() { - $log_folder = get_site_option('backwpup_cfg_logfolder'); $log_folder = BackWPup_File::get_absolute_path($log_folder); @@ -86,474 +84,471 @@ public function option_defaults() ]; } - /** - * @param $main - */ - public function edit_tab( $main ) { - - @set_time_limit( 300 ); - $abs_folder_up = BackWPup_Option::get( $main, 'backupabsfolderup' ); - $abs_path = realpath( BackWPup_Path_Fixer::fix_path( ABSPATH ) ); - if ( $abs_folder_up ) { - $abs_path = dirname( $abs_path ); - } - ?> -

+ /** + * @param $main + */ + public function edit_tab($main) + { + @set_time_limit(300); + $abs_folder_up = BackWPup_Option::get($main, 'backupabsfolderup'); + $abs_path = realpath(BackWPup_Path_Fixer::fix_path(ABSPATH)); + if ($abs_folder_up) { + $abs_path = dirname($abs_path); + } ?> +

- + - + - + - + - + - +
show_folder( 'root', $main, $abs_path ); - ?> + $this->show_folder('root', $main, $abs_path); ?>
show_folder( 'content', $main, WP_CONTENT_DIR ); - ?> + $this->show_folder('content', $main, WP_CONTENT_DIR); ?>
show_folder( 'plugins', $main, WP_PLUGIN_DIR ); - ?> + $this->show_folder('plugins', $main, WP_PLUGIN_DIR); ?>
show_folder( 'themes', $main, get_theme_root() ); - ?> + $this->show_folder('themes', $main, get_theme_root()); ?>
show_folder( 'uploads', $main, BackWPup_File::get_upload_dir() ); - ?> + $this->show_folder('uploads', $main, BackWPup_File::get_upload_dir()); ?>
- -

+ +

-

+

- + - +
- +
- -

+ +

-

+

- + - +
- +
- +
$value ) { - $normalized = wp_normalize_path( trim( $value ) ); - $normalized and $to_exclude_parsed[$key] = $normalized; - } - sort( $to_exclude_parsed ); - BackWPup_Option::update( $id, 'fileexclude', implode( ',', $to_exclude_parsed ) ); - unset( $exclude_input, $to_exclude_list, $to_exclude, $to_exclude_parsed, $normalized ); - - // Parse and save folders to include - $include_input = filter_input( INPUT_POST , 'dirinclude' ); - $include_list = $include_input ? str_replace( array( "\r\n", "\r" ), ',', $include_input ) : array(); - $to_include = $include_list ? explode( ',', $include_list ) : array(); - $to_include_parsed = array(); - foreach ( $to_include as $key => $value ) { - $normalized = trailingslashit( wp_normalize_path( trim( $value ) ) ); - $normalized and $normalized = filter_var( $normalized, FILTER_SANITIZE_URL ); - $realpath = $normalized && $normalized !== '/' ? realpath( $normalized ) : false; - $realpath and $to_include_parsed[$key] = $realpath; - } - sort( $to_include_parsed ); - BackWPup_Option::update( $id, 'dirinclude', implode( ',', $to_include_parsed ) ); - unset( $include_input, $include_list, $to_include, $to_include_parsed, $normalized, $realpath ); - - // Parse and save boolean fields - $boolean_fields_def = array( - 'backupexcludethumbs' => FILTER_VALIDATE_BOOLEAN, - 'backupspecialfiles' => FILTER_VALIDATE_BOOLEAN, - 'backuproot' => FILTER_VALIDATE_BOOLEAN, - 'backupabsfolderup' => FILTER_VALIDATE_BOOLEAN, - 'backupcontent' => FILTER_VALIDATE_BOOLEAN, - 'backupplugins' => FILTER_VALIDATE_BOOLEAN, - 'backupthemes' => FILTER_VALIDATE_BOOLEAN, - 'backupuploads' => FILTER_VALIDATE_BOOLEAN, - ); - $boolean_data = filter_input_array( INPUT_POST, $boolean_fields_def ); - $boolean_data or $boolean_data = array(); - foreach( $boolean_fields_def as $key => $value ) { - BackWPup_Option::update( $id, $key, ! empty( $boolean_data[$key] ) ); - } - unset( $boolean_fields_def, $boolean_data ); - - // Parse and save directories to exclude - $exclude_dirs_def = array( - 'backuprootexcludedirs' => array( 'filter' => FILTER_SANITIZE_URL, 'flags' => FILTER_FORCE_ARRAY ), - 'backupcontentexcludedirs' => array( 'filter' => FILTER_SANITIZE_URL, 'flags' => FILTER_FORCE_ARRAY ), - 'backuppluginsexcludedirs' => array( 'filter' => FILTER_SANITIZE_URL, 'flags' => FILTER_FORCE_ARRAY ), - 'backupthemesexcludedirs' => array( 'filter' => FILTER_SANITIZE_URL, 'flags' => FILTER_FORCE_ARRAY ), - 'backupuploadsexcludedirs' => array( 'filter' => FILTER_SANITIZE_URL, 'flags' => FILTER_FORCE_ARRAY ), - ); - $exclude_dirs = filter_input_array( INPUT_POST, $exclude_dirs_def ); - $exclude_dirs or $exclude_dirs = array(); - foreach( $exclude_dirs_def as $key => $filter ) { - $value = ! empty( $exclude_dirs[$key] ) && is_array( $exclude_dirs[$key] ) ? $exclude_dirs[$key] : array(); - BackWPup_Option::update( $id, $key, $value ); - } - unset( $exclude_dirs_def, $exclude_dirs ); - } - - /** - * @param $job_object - * @return bool - */ - public function job_run( BackWPup_Job $job_object ) { - - if ( $job_object->steps_data[ $job_object->step_working ]['SAVE_STEP_TRY'] != $job_object->steps_data[ $job_object->step_working ][ 'STEP_TRY' ] ) { - $job_object->log( sprintf( __( '%d. Trying to make a list of folders to back up …', 'backwpup' ), $job_object->steps_data[ $job_object->step_working ]['STEP_TRY'] ) ); - } - $job_object->substeps_todo = 8; - - $abs_path = realpath( BackWPup_Path_Fixer::fix_path( ABSPATH ) ); - if ( $job_object->job['backupabsfolderup'] ) { - $abs_path = dirname( $abs_path ); - } - $abs_path = trailingslashit( str_replace( '\\', '/', $abs_path ) ); - - $job_object->temp['folders_to_backup'] = array(); - $folders_already_in = $job_object->get_folders_to_backup(); - - //Folder lists for blog folders - if ( $job_object->substeps_done === 0 ) { - if ( $abs_path && ! empty( $job_object->job['backuproot'] ) ) { - $abs_path = trailingslashit( str_replace( '\\', '/', $abs_path ) ); - $excludes = $this->get_exclude_dirs( $abs_path, $folders_already_in ); - foreach ( $job_object->job['backuprootexcludedirs'] as $folder ) { - $excludes[] = trailingslashit( $abs_path . $folder ); - } - $this->get_folder_list( $job_object, $abs_path, $excludes ); - } - $job_object->substeps_done = 1; - $job_object->update_working_data(); - $job_object->do_restart_time(); - } - - if ( $job_object->substeps_done === 1 ) { - $wp_content_dir = realpath( WP_CONTENT_DIR ); - if ( $wp_content_dir && ! empty( $job_object->job['backupcontent'] ) ) { - $wp_content_dir = trailingslashit( str_replace( '\\', '/', $wp_content_dir ) ); - $excludes = $this->get_exclude_dirs( $wp_content_dir, $folders_already_in ); - foreach ( $job_object->job['backupcontentexcludedirs'] as $folder ) { - $excludes[] = trailingslashit( $wp_content_dir . $folder ); - } - $this->get_folder_list( $job_object, $wp_content_dir, $excludes ); - } - $job_object->substeps_done = 2; - $job_object->update_working_data(); - $job_object->do_restart_time(); - } - - if ( $job_object->substeps_done === 2 ) { - $wp_plugin_dir = realpath( WP_PLUGIN_DIR ); - if ( $wp_plugin_dir && ! empty( $job_object->job['backupplugins'] ) ) { - $wp_plugin_dir = trailingslashit( str_replace( '\\', '/', $wp_plugin_dir ) ); - $excludes = $this->get_exclude_dirs( $wp_plugin_dir, $folders_already_in ); - foreach ( $job_object->job['backuppluginsexcludedirs'] as $folder ) { - $excludes[] = trailingslashit( $wp_plugin_dir . $folder ); - } - $this->get_folder_list( $job_object, $wp_plugin_dir, $excludes ); - } - $job_object->substeps_done = 3; - $job_object->update_working_data(); - $job_object->do_restart_time(); - } - - if ( $job_object->substeps_done === 3 ) { - $theme_root = realpath( get_theme_root() ); - if ( $theme_root && ! empty( $job_object->job['backupthemes'] ) ) { - $theme_root = trailingslashit( str_replace( '\\', '/', $theme_root ) ); - $excludes = $this->get_exclude_dirs( $theme_root, $folders_already_in ); - foreach ( $job_object->job['backupthemesexcludedirs'] as $folder ) { - $excludes[] = trailingslashit( $theme_root . $folder ); - } - $this->get_folder_list( $job_object, $theme_root, $excludes ); - } - $job_object->substeps_done = 4; - $job_object->update_working_data(); - $job_object->do_restart_time(); - } - - if ( $job_object->substeps_done === 4 ) { - $upload_dir = realpath( BackWPup_File::get_upload_dir() ); - if ( $upload_dir && ! empty( $job_object->job['backupuploads'] ) ) { - $upload_dir = trailingslashit( str_replace( '\\', '/', $upload_dir ) ); - $excludes = $this->get_exclude_dirs( $upload_dir, $folders_already_in ); - foreach ( $job_object->job['backupuploadsexcludedirs'] as $folder ) { - $excludes[] = trailingslashit( $upload_dir . $folder ); - } - $this->get_folder_list( $job_object, $upload_dir, $excludes ); - } - $job_object->substeps_done = 5; - $job_object->update_working_data(); - $job_object->do_restart_time(); - } - - if ( $job_object->substeps_done === 5 ) { - //include dirs - if ( $job_object->job['dirinclude'] ) { - $dirinclude = explode( ',', $job_object->job['dirinclude'] ); - $dirinclude = array_unique( $dirinclude ); - //Crate file list for includes - foreach ( $dirinclude as $dirincludevalue ) { - if ( is_dir( $dirincludevalue ) ) { - $this->get_folder_list( $job_object, $dirincludevalue ); - } - } - } - $job_object->substeps_done = 6; - $job_object->update_working_data(); - $job_object->do_restart_time(); - } - - if ( $job_object->substeps_done === 6 ) { - //clean up folder list - $folders = $job_object->get_folders_to_backup(); - $job_object->add_folders_to_backup( $folders, true ); - $job_object->substeps_done = 7; - $job_object->update_working_data(); - $job_object->do_restart_time(); - } - - //add extra files if selected - if ( ! empty( $job_object->job['backupspecialfiles'] ) ) { - - // Special handling for wp-config.php - if ( is_readable( ABSPATH . 'wp-config.php' ) ) { - $job_object->additional_files_to_backup[] = str_replace( '\\', '/', ABSPATH . 'wp-config.php' ); - $job_object->log( sprintf( __( 'Added "%s" to backup file list', 'backwpup' ), 'wp-config.php' ) ); - } elseif ( BackWPup_File::is_in_open_basedir( dirname( ABSPATH ) . '/wp-config.php' ) ) { - if ( is_readable( dirname( ABSPATH ) . '/wp-config.php' ) && ! is_readable( dirname( ABSPATH ) . '/wp-settings.php' ) ) { - $job_object->additional_files_to_backup[] = str_replace( '\\', '/', dirname( ABSPATH ) . '/wp-config.php' ); - $job_object->log( sprintf( __( 'Added "%s" to backup file list', 'backwpup' ), 'wp-config.php' ) ); - } - } - - // Files to include - $special_files = array( - '.htaccess', - 'nginx.conf', - '.htpasswd', - 'robots.txt', - 'favicon.ico', - 'Web.config', - ); - - foreach ( $special_files as $file ) { - if ( is_readable( $abs_path . $file ) && empty( $job_object->job['backuproot'] ) ) { - $job_object->additional_files_to_backup[] = $abs_path . $file; - $job_object->log( sprintf( __( 'Added "%s" to backup file list', 'backwpup' ), $file ) ); - } - } - } - - if ( $job_object->count_folder === 0 && count( $job_object->additional_files_to_backup ) === 0 ) { - $job_object->log( __( 'No files/folder for the backup.', 'backwpup' ), E_USER_WARNING ); - } elseif ( $job_object->count_folder > 1 ) { - $job_object->log( sprintf( __( '%1$d folders to backup.', 'backwpup' ), $job_object->count_folder ) ); - } - - $job_object->substeps_done = 8; - - return true; - } - - /** - * - * Helper function for folder_list() - * - * @param $job_object BackWPup_Job - * @param string $folder - * @param array $excludedirs - * @param bool $first - * - * @return bool - * - */ - private function get_folder_list( &$job_object, $folder, $excludedirs = array(), $first = true ) { - - $folder = trailingslashit( $folder ); - - try { - $dir = new BackWPup_Directory( $folder ); - //add folder to folder list - $job_object->add_folders_to_backup( $folder ); - //scan folder - foreach ( $dir as $file ) { - if ( $file->isDot() ) { - continue; - } - $path = str_replace( '\\', '/', realpath( $file->getPathname() ) ); - foreach ( $job_object->exclude_from_backup as $exclusion ) { //exclude files - $exclusion = trim( $exclusion ); - if ( stripos( $path, $exclusion ) !== false && ! empty( $exclusion ) ) { - continue 2; - } - } - if ( $file->isDir() ) { - if ( in_array( trailingslashit( $path ), $excludedirs, true ) ) { - continue; - } - if ( file_exists( trailingslashit( $file->getPathname() ) . '.donotbackup' ) ) { - continue; - } - if ( ! $file->isReadable() ) { - $job_object->log( sprintf( __( 'Folder "%s" is not readable!', 'backwpup' ), $file->getPathname() ), E_USER_WARNING ); - continue; - } - $this->get_folder_list( $job_object, trailingslashit( $path ), $excludedirs, false ); - } - if ( $first ) { - $job_object->do_restart_time(); - } - } - } - catch ( UnexpectedValueException $e ) { - $job_object->log( sprintf( __( "Could not open path: %s", 'backwpup' ), $e->getMessage() ), E_USER_WARNING ); - } - - return true; - } - - /** - * - * Get folder to exclude from a given folder for file backups - * - * @param $folder string folder to check for excludes - * - * @param array $excludedir - * - * @return array of folder to exclude - */ - private function get_exclude_dirs( $folder, $excludedir = array() ) { - - $folder = trailingslashit( str_replace( '\\', '/', realpath( BackWPup_Path_Fixer::fix_path( $folder ) ) ) ); - - if ( false !== strpos( trailingslashit( str_replace( '\\', '/', realpath( WP_CONTENT_DIR ) ) ), $folder ) && trailingslashit( str_replace( '\\', '/', realpath( WP_CONTENT_DIR ) ) ) != $folder ) { - $excludedir[] = trailingslashit( str_replace( '\\', '/', realpath( WP_CONTENT_DIR ) ) ); - } - if ( false !== strpos( trailingslashit( str_replace( '\\', '/', realpath( WP_PLUGIN_DIR ) ) ), $folder ) && trailingslashit( str_replace( '\\', '/', realpath( WP_PLUGIN_DIR ) ) ) != $folder ) { - $excludedir[] = trailingslashit( str_replace( '\\', '/', realpath( WP_PLUGIN_DIR ) ) ); - } - if ( false !== strpos( trailingslashit( str_replace( '\\', '/', realpath( get_theme_root() ) ) ), $folder ) && trailingslashit( str_replace( '\\', '/', realpath( get_theme_root() ) ) ) != $folder ) { - $excludedir[] = trailingslashit( str_replace( '\\', '/', realpath( get_theme_root() ) ) ); - } - if ( false !== strpos( trailingslashit( str_replace( '\\', '/', realpath( BackWPup_File::get_upload_dir() ) ) ), $folder ) && trailingslashit( str_replace( '\\', '/', realpath( BackWPup_File::get_upload_dir() ) ) ) != $folder ) { - $excludedir[] = trailingslashit( str_replace( '\\', '/', realpath( BackWPup_File::get_upload_dir() ) ) ); - } - - return array_unique( $excludedir ); - } - - /** - * Shows a folder with the options of which files to exclude. - * - */ - private function show_folder( $id, $jobid, $path ) { - $folder = realpath( BackWPup_Path_Fixer::fix_path( $path ) ); - if ( $folder ) { - $folder = untrailingslashit( str_replace( '\\', '/', $folder ) ); - $folder_size = ( get_site_option( 'backwpup_cfg_showfoldersize') ) ? ' (' . size_format( BackWPup_File::get_folder_size( $folder, FALSE ), 2 ) . ')' : ''; - } - ?> + } + + /** + * @param $id + */ + public function edit_form_post_save($id) + { + // Parse and save files to exclude + $exclude_input = filter_input(INPUT_POST, 'fileexclude'); + $to_exclude_list = $exclude_input ? str_replace(["\r\n", "\r"], ',', $exclude_input) : []; + $to_exclude_list and $to_exclude_list = sanitize_text_field(stripslashes($to_exclude_list)); + $to_exclude = $to_exclude_list ? explode(',', $to_exclude_list) : []; + $to_exclude_parsed = []; + + foreach ($to_exclude as $key => $value) { + $normalized = wp_normalize_path(trim($value)); + $normalized and $to_exclude_parsed[$key] = $normalized; + } + sort($to_exclude_parsed); + BackWPup_Option::update($id, 'fileexclude', implode(',', $to_exclude_parsed)); + unset($exclude_input, $to_exclude_list, $to_exclude, $to_exclude_parsed, $normalized); + + // Parse and save folders to include + $include_input = filter_input(INPUT_POST, 'dirinclude'); + $include_list = $include_input ? str_replace(["\r\n", "\r"], ',', $include_input) : []; + $to_include = $include_list ? explode(',', $include_list) : []; + $to_include_parsed = []; + + foreach ($to_include as $key => $value) { + $normalized = trailingslashit(wp_normalize_path(trim($value))); + $normalized and $normalized = filter_var($normalized, FILTER_SANITIZE_URL); + $realpath = $normalized && $normalized !== '/' ? realpath($normalized) : false; + $realpath and $to_include_parsed[$key] = $realpath; + } + sort($to_include_parsed); + BackWPup_Option::update($id, 'dirinclude', implode(',', $to_include_parsed)); + unset($include_input, $include_list, $to_include, $to_include_parsed, $normalized, $realpath); + + // Parse and save boolean fields + $boolean_fields_def = [ + 'backupexcludethumbs' => FILTER_VALIDATE_BOOLEAN, + 'backupspecialfiles' => FILTER_VALIDATE_BOOLEAN, + 'backuproot' => FILTER_VALIDATE_BOOLEAN, + 'backupabsfolderup' => FILTER_VALIDATE_BOOLEAN, + 'backupcontent' => FILTER_VALIDATE_BOOLEAN, + 'backupplugins' => FILTER_VALIDATE_BOOLEAN, + 'backupthemes' => FILTER_VALIDATE_BOOLEAN, + 'backupuploads' => FILTER_VALIDATE_BOOLEAN, + ]; + $boolean_data = filter_input_array(INPUT_POST, $boolean_fields_def); + $boolean_data or $boolean_data = []; + + foreach ($boolean_fields_def as $key => $value) { + BackWPup_Option::update($id, $key, !empty($boolean_data[$key])); + } + unset($boolean_fields_def, $boolean_data); + + // Parse and save directories to exclude + $exclude_dirs_def = [ + 'backuprootexcludedirs' => ['filter' => FILTER_SANITIZE_URL, 'flags' => FILTER_FORCE_ARRAY], + 'backupcontentexcludedirs' => ['filter' => FILTER_SANITIZE_URL, 'flags' => FILTER_FORCE_ARRAY], + 'backuppluginsexcludedirs' => ['filter' => FILTER_SANITIZE_URL, 'flags' => FILTER_FORCE_ARRAY], + 'backupthemesexcludedirs' => ['filter' => FILTER_SANITIZE_URL, 'flags' => FILTER_FORCE_ARRAY], + 'backupuploadsexcludedirs' => ['filter' => FILTER_SANITIZE_URL, 'flags' => FILTER_FORCE_ARRAY], + ]; + $exclude_dirs = filter_input_array(INPUT_POST, $exclude_dirs_def); + $exclude_dirs or $exclude_dirs = []; + + foreach ($exclude_dirs_def as $key => $filter) { + $value = !empty($exclude_dirs[$key]) && is_array($exclude_dirs[$key]) ? $exclude_dirs[$key] : []; + BackWPup_Option::update($id, $key, $value); + } + unset($exclude_dirs_def, $exclude_dirs); + } + + /** + * @param $job_object + * + * @return bool + */ + public function job_run(BackWPup_Job $job_object) + { + if ($job_object->steps_data[$job_object->step_working]['SAVE_STEP_TRY'] != $job_object->steps_data[$job_object->step_working]['STEP_TRY']) { + $job_object->log(sprintf(__('%d. Trying to make a list of folders to back up …', 'backwpup'), $job_object->steps_data[$job_object->step_working]['STEP_TRY'])); + } + $job_object->substeps_todo = 8; + + $abs_path = realpath(BackWPup_Path_Fixer::fix_path(ABSPATH)); + if ($job_object->job['backupabsfolderup']) { + $abs_path = dirname($abs_path); + } + $abs_path = trailingslashit(str_replace('\\', '/', $abs_path)); + + $job_object->temp['folders_to_backup'] = []; + $folders_already_in = $job_object->get_folders_to_backup(); + + //Folder lists for blog folders + if ($job_object->substeps_done === 0) { + if ($abs_path && !empty($job_object->job['backuproot'])) { + $abs_path = trailingslashit(str_replace('\\', '/', $abs_path)); + $excludes = $this->get_exclude_dirs($abs_path, $folders_already_in); + + foreach ($job_object->job['backuprootexcludedirs'] as $folder) { + $excludes[] = trailingslashit($abs_path . $folder); + } + $this->get_folder_list($job_object, $abs_path, $excludes); + } + $job_object->substeps_done = 1; + $job_object->update_working_data(); + $job_object->do_restart_time(); + } + + if ($job_object->substeps_done === 1) { + $wp_content_dir = realpath(WP_CONTENT_DIR); + if ($wp_content_dir && !empty($job_object->job['backupcontent'])) { + $wp_content_dir = trailingslashit(str_replace('\\', '/', $wp_content_dir)); + $excludes = $this->get_exclude_dirs($wp_content_dir, $folders_already_in); + + foreach ($job_object->job['backupcontentexcludedirs'] as $folder) { + $excludes[] = trailingslashit($wp_content_dir . $folder); + } + $this->get_folder_list($job_object, $wp_content_dir, $excludes); + } + $job_object->substeps_done = 2; + $job_object->update_working_data(); + $job_object->do_restart_time(); + } + + if ($job_object->substeps_done === 2) { + $wp_plugin_dir = realpath(WP_PLUGIN_DIR); + if ($wp_plugin_dir && !empty($job_object->job['backupplugins'])) { + $wp_plugin_dir = trailingslashit(str_replace('\\', '/', $wp_plugin_dir)); + $excludes = $this->get_exclude_dirs($wp_plugin_dir, $folders_already_in); + + foreach ($job_object->job['backuppluginsexcludedirs'] as $folder) { + $excludes[] = trailingslashit($wp_plugin_dir . $folder); + } + $this->get_folder_list($job_object, $wp_plugin_dir, $excludes); + } + $job_object->substeps_done = 3; + $job_object->update_working_data(); + $job_object->do_restart_time(); + } + + if ($job_object->substeps_done === 3) { + $theme_root = realpath(get_theme_root()); + if ($theme_root && !empty($job_object->job['backupthemes'])) { + $theme_root = trailingslashit(str_replace('\\', '/', $theme_root)); + $excludes = $this->get_exclude_dirs($theme_root, $folders_already_in); + + foreach ($job_object->job['backupthemesexcludedirs'] as $folder) { + $excludes[] = trailingslashit($theme_root . $folder); + } + $this->get_folder_list($job_object, $theme_root, $excludes); + } + $job_object->substeps_done = 4; + $job_object->update_working_data(); + $job_object->do_restart_time(); + } + + if ($job_object->substeps_done === 4) { + $upload_dir = realpath(BackWPup_File::get_upload_dir()); + if ($upload_dir && !empty($job_object->job['backupuploads'])) { + $upload_dir = trailingslashit(str_replace('\\', '/', $upload_dir)); + $excludes = $this->get_exclude_dirs($upload_dir, $folders_already_in); + + foreach ($job_object->job['backupuploadsexcludedirs'] as $folder) { + $excludes[] = trailingslashit($upload_dir . $folder); + } + $this->get_folder_list($job_object, $upload_dir, $excludes); + } + $job_object->substeps_done = 5; + $job_object->update_working_data(); + $job_object->do_restart_time(); + } + + if ($job_object->substeps_done === 5) { + //include dirs + if ($job_object->job['dirinclude']) { + $dirinclude = explode(',', $job_object->job['dirinclude']); + $dirinclude = array_unique($dirinclude); + //Crate file list for includes + foreach ($dirinclude as $dirincludevalue) { + if (is_dir($dirincludevalue)) { + $this->get_folder_list($job_object, $dirincludevalue); + } + } + } + $job_object->substeps_done = 6; + $job_object->update_working_data(); + $job_object->do_restart_time(); + } + + if ($job_object->substeps_done === 6) { + //clean up folder list + $folders = $job_object->get_folders_to_backup(); + $job_object->add_folders_to_backup($folders, true); + $job_object->substeps_done = 7; + $job_object->update_working_data(); + $job_object->do_restart_time(); + } + + //add extra files if selected + if (!empty($job_object->job['backupspecialfiles'])) { + // Special handling for wp-config.php + if (is_readable(ABSPATH . 'wp-config.php')) { + $job_object->additional_files_to_backup[] = str_replace('\\', '/', ABSPATH . 'wp-config.php'); + $job_object->log(sprintf(__('Added "%s" to backup file list', 'backwpup'), 'wp-config.php')); + } elseif (BackWPup_File::is_in_open_basedir(dirname(ABSPATH) . '/wp-config.php')) { + if (is_readable(dirname(ABSPATH) . '/wp-config.php') && !is_readable(dirname(ABSPATH) . '/wp-settings.php')) { + $job_object->additional_files_to_backup[] = str_replace('\\', '/', dirname(ABSPATH) . '/wp-config.php'); + $job_object->log(sprintf(__('Added "%s" to backup file list', 'backwpup'), 'wp-config.php')); + } + } + + // Files to include + $special_files = [ + '.htaccess', + 'nginx.conf', + '.htpasswd', + 'robots.txt', + 'favicon.ico', + 'Web.config', + ]; + + foreach ($special_files as $file) { + if (is_readable($abs_path . $file) && empty($job_object->job['backuproot'])) { + $job_object->additional_files_to_backup[] = $abs_path . $file; + $job_object->log(sprintf(__('Added "%s" to backup file list', 'backwpup'), $file)); + } + } + } + + if ($job_object->count_folder === 0 && count($job_object->additional_files_to_backup) === 0) { + $job_object->log(__('No files/folder for the backup.', 'backwpup'), E_USER_WARNING); + } elseif ($job_object->count_folder > 1) { + $job_object->log(sprintf(__('%1$d folders to backup.', 'backwpup'), $job_object->count_folder)); + } + + $job_object->substeps_done = 8; + + return true; + } + + /** + * Helper function for folder_list(). + * + * @param $job_object BackWPup_Job + * @param string $folder + * @param array $excludedirs + * @param bool $first + * + * @return bool + */ + private function get_folder_list(&$job_object, $folder, $excludedirs = [], $first = true) + { + $folder = trailingslashit($folder); + + try { + $dir = new BackWPup_Directory($folder); + //add folder to folder list + $job_object->add_folders_to_backup($folder); + //scan folder + foreach ($dir as $file) { + if ($file->isDot()) { + continue; + } + $path = str_replace('\\', '/', realpath($file->getPathname())); + + foreach ($job_object->exclude_from_backup as $exclusion) { //exclude files + $exclusion = trim($exclusion); + if (stripos($path, $exclusion) !== false && !empty($exclusion)) { + continue 2; + } + } + if ($file->isDir()) { + if (in_array(trailingslashit($path), $excludedirs, true)) { + continue; + } + if (file_exists(trailingslashit($file->getPathname()) . '.donotbackup')) { + continue; + } + if (!$file->isReadable()) { + $job_object->log(sprintf(__('Folder "%s" is not readable!', 'backwpup'), $file->getPathname()), E_USER_WARNING); + + continue; + } + $this->get_folder_list($job_object, trailingslashit($path), $excludedirs, false); + } + if ($first) { + $job_object->do_restart_time(); + } + } + } catch (UnexpectedValueException $e) { + $job_object->log(sprintf(__('Could not open path: %s', 'backwpup'), $e->getMessage()), E_USER_WARNING); + } + + return true; + } + + /** + * Get folder to exclude from a given folder for file backups. + * + * @param $folder string folder to check for excludes + * @param array $excludedir + * + * @return array of folder to exclude + */ + private function get_exclude_dirs($folder, $excludedir = []) + { + $folder = trailingslashit(str_replace('\\', '/', realpath(BackWPup_Path_Fixer::fix_path($folder)))); + + if (false !== strpos(trailingslashit(str_replace('\\', '/', realpath(WP_CONTENT_DIR))), $folder) && trailingslashit(str_replace('\\', '/', realpath(WP_CONTENT_DIR))) != $folder) { + $excludedir[] = trailingslashit(str_replace('\\', '/', realpath(WP_CONTENT_DIR))); + } + if (false !== strpos(trailingslashit(str_replace('\\', '/', realpath(WP_PLUGIN_DIR))), $folder) && trailingslashit(str_replace('\\', '/', realpath(WP_PLUGIN_DIR))) != $folder) { + $excludedir[] = trailingslashit(str_replace('\\', '/', realpath(WP_PLUGIN_DIR))); + } + if (false !== strpos(trailingslashit(str_replace('\\', '/', realpath(get_theme_root()))), $folder) && trailingslashit(str_replace('\\', '/', realpath(get_theme_root()))) != $folder) { + $excludedir[] = trailingslashit(str_replace('\\', '/', realpath(get_theme_root()))); + } + if (false !== strpos(trailingslashit(str_replace('\\', '/', realpath(BackWPup_File::get_upload_dir()))), $folder) && trailingslashit(str_replace('\\', '/', realpath(BackWPup_File::get_upload_dir()))) != $folder) { + $excludedir[] = trailingslashit(str_replace('\\', '/', realpath(BackWPup_File::get_upload_dir()))); + } + + return array_unique($excludedir); + } + + /** + * Shows a folder with the options of which files to exclude. + */ + private function show_folder($id, $jobid, $path) + { + $folder = realpath(BackWPup_Path_Fixer::fix_path($path)); + $folder_size = 0; + if ($folder) { + $folder = untrailingslashit(str_replace('\\', '/', $folder)); + $folder_size = (get_site_option('backwpup_cfg_showfoldersize')) ? ' (' . size_format(BackWPup_File::get_folder_size($folder), 2) . ')' : ''; + } ?> - name="backup" id="idbackup" value="1" /> + type="checkbox" + name="backup" id="idbackup" value="1" /> -
- +
+ isDot() && $file->isDir() && ! in_array( trailingslashit( $file->getPathname() ), $this->get_exclude_dirs( $folder ), true ) ) { - $donotbackup = file_exists( $file->getPathname() . '/.donotbackup' ); - $folder_size = ( get_site_option( 'backwpup_cfg_showfoldersize' ) ) ? ' (' . size_format( BackWPup_File::get_folder_size( $file->getPathname() ), 2 ) . ')' : ''; - $title = ''; - if ( $donotbackup ) { - $excludes[] = $file->getFilename(); - $title = ' title="' . esc_attr__( 'Excluded by .donotbackup file!', 'backwpup' ) . '"'; - } - echo '
'; - } - } - } - catch ( Exception $e ) { - // Do nothing, just skip - } - ?> + try { + $dir = new BackWPup_Directory($folder); + $excludes = BackWPup_Option::get($jobid, 'backup' . $id . 'excludedirs'); + + foreach ($dir as $file) { + if (!$file->isDot() && $file->isDir() && !in_array(trailingslashit($file->getPathname()), $this->get_exclude_dirs($folder), true)) { + $donotbackup = file_exists($file->getPathname() . '/.donotbackup'); + $folder_size = (get_site_option('backwpup_cfg_showfoldersize')) ? ' (' . size_format(BackWPup_File::get_folder_size($file->getPathname()), 2) . ')' : ''; + $title = ''; + if ($donotbackup) { + $excludes[] = $file->getFilename(); + $title = ' title="' . esc_attr__('Excluded by .donotbackup file!', 'backwpup') . '"'; + } + echo '
'; + } + } + } catch (Exception $e) { + // Do nothing, just skip + } ?>
info[ 'ID' ] = 'WPEXP'; - $this->info[ 'name' ] = __( 'XML export', 'backwpup' ); - $this->info[ 'description' ] = __( 'WordPress XML export', 'backwpup' ); - $this->info[ 'URI' ] = __( 'http://backwpup.com', 'backwpup' ); - $this->info[ 'author' ] = 'Inpsyde GmbH'; - $this->info[ 'authorURI' ] = __( 'http://inpsyde.com', 'backwpup' ); - $this->info[ 'version' ] = BackWPup::get_plugin_data( 'Version' ); - - } - - /** - * @return bool - */ - public function creates_file() { - - return TRUE; - } - - /** - * @return array - */ - public function option_defaults() { - return array( 'wpexportcontent' => 'all', 'wpexportfilecompression' => '', 'wpexportfile' => sanitize_text_field( get_bloginfo( 'name' ) ) . '.wordpress.%Y-%m-%d' ); - } - - - /** - * @param $jobid - * @internal param $main - */ - public function edit_tab( $jobid ) { - ?> +use Inpsyde\BackWPup\Infrastructure\Xml\Exception\InvalidWxrFileException; +use Inpsyde\BackWPup\Infrastructure\Xml\Exception\InvalidXmlException; +use Inpsyde\BackWPup\Infrastructure\Xml\WxrValidator; + +class BackWPup_JobType_WPEXP extends BackWPup_JobTypes +{ + public function __construct() + { + $this->info['ID'] = 'WPEXP'; + $this->info['name'] = __('XML export', 'backwpup'); + $this->info['description'] = __('WordPress XML export', 'backwpup'); + $this->info['URI'] = __('http://backwpup.com', 'backwpup'); + $this->info['author'] = 'Inpsyde GmbH'; + $this->info['authorURI'] = __('http://inpsyde.com', 'backwpup'); + $this->info['version'] = BackWPup::get_plugin_data('Version'); + } + + /** + * @return bool + */ + public function creates_file() + { + return true; + } + + /** + * @return array + */ + public function option_defaults() + { + return ['wpexportcontent' => 'all', 'wpexportfilecompression' => '', 'wpexportfile' => sanitize_text_field(get_bloginfo('name')) . '.wordpress.%Y-%m-%d']; + } + + /** + * @param int $jobid + * + * @internal param $main + */ + public function edit_tab($jobid) + { + ?> - + - + - +
-
-
-
+
+
+
FALSE, 'can_export' => TRUE ), 'objects' ) as $post_type ) { - ?> -
- + foreach (get_post_types(['_builtin' => false, 'can_export' => true], 'objects') as $post_type) { + ?> +
+
.xml
' . esc_html__( 'none', 'backwpup' ). '
'; - if ( function_exists( 'gzopen' ) ) - echo '
'; - else - echo '
'; - ?> + echo '
'; + if (function_exists('gzopen')) { + echo '
'; + } else { + echo '
'; + } ?>
steps_data[ $job_object->step_working ]['SAVE_STEP_TRY'] != $job_object->steps_data[ $job_object->step_working ][ 'STEP_TRY' ] ) { - $job_object->log( sprintf( __( '%d. Trying to create a WordPress export to XML file …', 'backwpup' ), $job_object->steps_data[ $job_object->step_working ][ 'STEP_TRY' ] ) ); - $job_object->steps_data[ $job_object->step_working ]['wpexportfile'] = BackWPup::get_plugin_data( 'TEMP' ) . $job_object->generate_filename( $job_object->job[ 'wpexportfile' ], 'xml', TRUE ); - $job_object->steps_data[ $job_object->step_working ]['substep'] = 'header'; - $job_object->steps_data[ $job_object->step_working ]['post_ids'] = array(); - $job_object->substeps_todo = 10; - $job_object->substeps_done = 0; - } - - add_filter( 'backwpup_wxr_export_skip_postmeta', array( $this, 'wxr_filter_postmeta' ), 10, 2 ); - - if ( $job_object->steps_data[ $job_object->step_working ]['substep'] == 'header' ) { - - if ( 'all' != $job_object->job[ 'wpexportcontent' ] && post_type_exists( $job_object->job[ 'wpexportcontent' ] ) ) { - $ptype = get_post_type_object( $job_object->job[ 'wpexportcontent' ] ); - if ( ! $ptype->can_export ) { - $job_object->log( sprintf( __( 'WP Export: Post type “%s” does not allow export.', 'backwpup' ), $job_object->job[ 'wpexportcontent' ] ), E_USER_ERROR ); - return FALSE; - } - $where = $wpdb->prepare( "{$wpdb->posts}.post_type = %s", $job_object->job[ 'wpexportcontent' ] ); - } else { - $post_types = get_post_types( array( 'can_export' => true ) ); - $esses = array_fill( 0, count($post_types), '%s' ); - $where = $wpdb->prepare( "{$wpdb->posts}.post_type IN (" . implode( ',', $esses ) . ')', $post_types ); - $job_object->job[ 'wpexportcontent' ] = 'all'; - } - $where .= " AND {$wpdb->posts}.post_status != 'auto-draft'"; - - // grab a snapshot of post IDs, just in case it changes during the export - $job_object->steps_data[ $job_object->step_working ]['post_ids'] = $wpdb->get_col( "SELECT ID FROM {$wpdb->posts} WHERE $where" ); - $job_object->substeps_todo = $job_object->substeps_todo + count( $job_object->steps_data[ $job_object->step_working ]['post_ids'] ); - - $header = '\n"; - $header .= "\n"; - $header .= "\n"; - $header .= "\n"; - $header .= "\n\n"; - $header .= "\n"; - $header .= "\n"; - $header .= "\n"; - $header .= "\n"; - $header .= "\n"; - $header .= "\n"; - $header .= "\n"; - $header .= "\n"; - $header .= "\n"; - $header .= "\n"; - $header .= "\n\n"; - $header .= "\n"; - $header .= "\n"; - $header .= "\n"; - $header .= "\t" . get_bloginfo_rss( 'name' ) ."\n"; - $header .= "\t" . get_bloginfo_rss( 'url' ) ."\n"; - $header .= "\t" . get_bloginfo_rss( 'description' ) ."\n"; - $header .= "\t" . date( 'D, d M Y H:i:s +0000' ) ."\n"; - $header .= "\t" . get_bloginfo_rss( 'language' ) ."\n"; - $header .= "\t" . $wxr_version ."\n"; - $header .= "\t" . $this->wxr_site_url() ."\n"; - $header .= "\t" . get_bloginfo_rss( 'url' ) ."\n"; - $written = file_put_contents( $job_object->steps_data[ $job_object->step_working ][ 'wpexportfile' ], $header, FILE_APPEND ); - if ( $written === FALSE ) { - $job_object->log( __( 'WP Export file could not written.', 'backwpup' ), E_USER_ERROR ); - - return FALSE; - } - unset( $header ); - $job_object->steps_data[ $job_object->step_working ]['substep'] = 'authors'; - $job_object->substeps_done ++; - $job_object->update_working_data(); - $job_object->do_restart_time(); - } - - if ( $job_object->steps_data[ $job_object->step_working ]['substep'] == 'authors' ) { - $written = file_put_contents( $job_object->steps_data[ $job_object->step_working ][ 'wpexportfile' ], $this->wxr_authors_list(), FILE_APPEND ); - if ( $written === FALSE ) { - $job_object->log( __( 'WP Export file could not written.', 'backwpup' ), E_USER_ERROR ); - - return FALSE; - } - $job_object->steps_data[ $job_object->step_working ]['substep'] = 'cats'; - $job_object->substeps_done ++; - $job_object->update_working_data(); - $job_object->do_restart_time(); - } - - - if ( $job_object->steps_data[ $job_object->step_working ]['substep'] == 'cats' ) { - if ( 'all' == $job_object->job[ 'wpexportcontent' ] ) { - $cats = array(); - $categories = (array) get_categories( array( 'get' => 'all' ) ); - // put categories in order with no child going before its parent - while ( $cat = array_shift( $categories ) ) { - if ( $cat->parent == 0 || isset( $cats[$cat->parent] ) ) - $cats[$cat->term_id] = $cat; - else - $categories[] = $cat; - } - $cats_xml = ''; - foreach ( $cats as $c ) { - $parent_slug = $c->parent ? $cats[$c->parent]->slug : ''; - $cats_xml .= "\t" . $c->term_id ."" . $c->slug . "" . $parent_slug . "" . $this->wxr_cat_name( $c ) . $this->wxr_category_description( $c ) ."\n"; - } - $written = file_put_contents( $job_object->steps_data[ $job_object->step_working ][ 'wpexportfile' ], $cats_xml, FILE_APPEND ); - if ( $written === FALSE ) { - $job_object->log( __( 'WP Export file could not written.', 'backwpup' ), E_USER_ERROR ); - - return FALSE; - } - unset( $cats_xml ); - } - $job_object->steps_data[ $job_object->step_working ]['substep'] = 'tags'; - $job_object->substeps_done ++; - $job_object->update_working_data(); - $job_object->do_restart_time(); - } - - if ( $job_object->steps_data[ $job_object->step_working ]['substep'] == 'tags' ) { - if ( 'all' == $job_object->job[ 'wpexportcontent' ] ) { - $tags = (array) get_tags( array( 'get' => 'all' ) ); - $tags_xml = ''; - foreach ( $tags as $t ) { - $tags_xml .= "\t" . $t->term_id ."" . $t->slug ."" . $this->wxr_tag_name( $t ) . $this->wxr_tag_description( $t ) ."\n"; - } - $written = file_put_contents( $job_object->steps_data[ $job_object->step_working ][ 'wpexportfile' ], $tags_xml, FILE_APPEND ); - if ( $written === FALSE ) { - $job_object->log( __( 'WP Export file could not written.', 'backwpup' ), E_USER_ERROR ); - - return FALSE; - } - unset( $tags_xml ); - } - $job_object->steps_data[ $job_object->step_working ]['substep'] = 'terms'; - $job_object->substeps_done ++; - $job_object->update_working_data(); - $job_object->do_restart_time(); - } - - if ( $job_object->steps_data[ $job_object->step_working ]['substep'] == 'terms' ) { - if ( 'all' == $job_object->job[ 'wpexportcontent' ] ) { - - $terms = array(); - $custom_taxonomies = get_taxonomies( array( '_builtin' => false ) ); - $custom_terms = (array) get_terms( $custom_taxonomies, array( 'get' => 'all' ) ); - - // put terms in order with no child going before its parent - while ( $t = array_shift( $custom_terms ) ) { - if ( $t->parent == 0 || isset( $terms[$t->parent] ) ) - $terms[$t->term_id] = $t; - else - $custom_terms[] = $t; - } - $terms_xml = ''; - foreach ( $terms as $t ) { - $parent_slug = $t->parent ? $terms[$t->parent]->slug : ''; - $terms_xml .= "\t" . $t->term_id ."" . $t->taxonomy . "" . $t->slug ."" . $parent_slug ."" . $this->wxr_term_name( $t ) . $this->wxr_term_description( $t ) ."\n"; - } - $written = file_put_contents( $job_object->steps_data[ $job_object->step_working ][ 'wpexportfile' ], $terms_xml, FILE_APPEND ); - if ( $written === FALSE ) { - $job_object->log( __( 'WP Export file could not written.', 'backwpup' ), E_USER_ERROR ); - - return FALSE; - } - unset( $terms_xml ); - } - $job_object->steps_data[ $job_object->step_working ]['substep'] = 'menus'; - $job_object->substeps_done ++; - $job_object->update_working_data(); - $job_object->do_restart_time(); - } - - if ( $job_object->steps_data[ $job_object->step_working ]['substep'] == 'menus' ) { - $menu_xml = ''; - if ( 'all' == $job_object->job[ 'wpexportcontent' ] ) { - $menu_xml .= $this->wxr_nav_menu_terms(); - } - $menu_xml .= "\thttp://wordpress.org/?v=" . get_bloginfo_rss( 'version' ) . "\n"; - $written = file_put_contents( $job_object->steps_data[ $job_object->step_working ][ 'wpexportfile' ], $menu_xml, FILE_APPEND ); - if ( $written === FALSE ) { - $job_object->log( __( 'WP Export file could not written.', 'backwpup' ), E_USER_ERROR ); - - return FALSE; - } - unset( $menu_xml ); - - $job_object->steps_data[ $job_object->step_working ]['substep'] = 'posts'; - $job_object->substeps_done ++; - $job_object->update_working_data(); - $job_object->do_restart_time(); - } - - - if ( $job_object->steps_data[ $job_object->step_working ]['substep'] == 'posts' ) { - - if ( ! empty( $job_object->steps_data[ $job_object->step_working ]['post_ids'] ) ) { - $wp_query->in_the_loop = true; // Fake being in the loop. - - // fetch 20 posts at a time rather than loading the entire table into memory - while ( $next_posts = array_splice( $job_object->steps_data[ $job_object->step_working ]['post_ids'], 0, 20 ) ) { - $where = 'WHERE ID IN (' . join( ',', $next_posts ) . ')'; - $posts = $wpdb->get_results( "SELECT * FROM {$wpdb->posts} $where" ); - $wxr_post = ''; - // Begin Loop - foreach ( $posts as $post ) { - /* @var WP_Post $post */ - - $is_sticky = is_sticky( $post->ID ) ? 1 : 0; - - $wxr_post .= "\t\n"; - $wxr_post .= "\t\t" . apply_filters( 'the_title_rss', $post->post_title ) ."\n"; - $wxr_post .= "\t\t" . esc_url( apply_filters( 'the_permalink_rss', get_permalink( $post ) ) ) ."\n"; - $wxr_post .= "\t\t" . mysql2date( 'D, d M Y H:i:s +0000', get_post_time( 'Y-m-d H:i:s', true, $post ), false ) ."\n"; - $wxr_post .= "\t\t" . $this->wxr_cdata( get_the_author_meta( 'login', $post->post_author ) ) ."\n"; - $wxr_post .= "\t\t" . esc_url( get_the_guid( $post->ID ) ) ."\n"; - $wxr_post .= "\t\t\n"; - $wxr_post .= "\t\t" . $this->wxr_cdata( apply_filters( 'the_content_export', $post->post_content ) ) . "\n"; - $wxr_post .= "\t\t" . $this->wxr_cdata( apply_filters( 'the_excerpt_export', $post->post_excerpt ) ) . "\n"; - $wxr_post .= "\t\t" . $post->ID . "\n"; - $wxr_post .= "\t\t" . $post->post_date . "\n"; - $wxr_post .= "\t\t" . $post->post_date_gmt . "\n"; - $wxr_post .= "\t\t" . $post->comment_status . "\n"; - $wxr_post .= "\t\t" . $post->ping_status . "\n"; - $wxr_post .= "\t\t" . $post->post_name . "\n"; - $wxr_post .= "\t\t" . $post->post_status . "\n"; - $wxr_post .= "\t\t" . $post->post_parent . "\n"; - $wxr_post .= "\t\t" . $post->menu_order . "\n"; - $wxr_post .= "\t\t" . $post->post_type . "\n"; - $wxr_post .= "\t\t" . $post->post_password . "\n"; - $wxr_post .= "\t\t" . $is_sticky . "\n"; - if ( $post->post_type == 'attachment' ) { - $wxr_post .= "\t\t" . wp_get_attachment_url( $post->ID ) . "\n"; - } - $wxr_post .= $this->wxr_post_taxonomy(); - - $postmeta = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM $wpdb->postmeta WHERE post_id = %d", $post->ID ) ); - foreach ( $postmeta as $meta ) { - if ( apply_filters( 'backwpup_wxr_export_skip_postmeta', false, $meta->meta_key, $meta ) ) { - continue; - } - $wxr_post .= "\t\t\n\t\t\t" . $meta->meta_key ."\n\t\t\t" .$this->wxr_cdata( $meta->meta_value ) ."\n\t\t\n"; - } - - $comments = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM $wpdb->comments WHERE comment_post_ID = %d AND comment_approved <> 'spam'", $post->ID ) ); - foreach ( $comments as $c ) { - $wxr_post .= "\t\t\n"; - $wxr_post .= "\t\t\t" . $c->comment_ID . "\n"; - $wxr_post .= "\t\t\t" . $this->wxr_cdata( $c->comment_author ) . "\n"; - $wxr_post .= "\t\t\t" . $c->comment_author_email . "\n"; - $wxr_post .= "\t\t\t" . esc_url_raw( $c->comment_author_url ) . "\n"; - $wxr_post .= "\t\t\t" . $c->comment_author_IP . "\n"; - $wxr_post .= "\t\t\t" . $c->comment_date . "\n"; - $wxr_post .= "\t\t\t" . $c->comment_date_gmt . "\n"; - $wxr_post .= "\t\t\t" . $this->wxr_cdata( $c->comment_content ) . "\n"; - $wxr_post .= "\t\t\t" . $c->comment_approved . "\n"; - $wxr_post .= "\t\t\t" . $c->comment_type . "\n"; - $wxr_post .= "\t\t\t" . $c->comment_parent . "\n"; - $wxr_post .= "\t\t\t" . $c->user_id . "\n"; - $c_meta = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM $wpdb->commentmeta WHERE comment_id = %d", $c->comment_ID ) ); - foreach ( $c_meta as $meta ) { - $wxr_post .= "\t\t\t\n\t\t\t\t" . $meta->meta_key ."\n\t\t\t\t" .$this->wxr_cdata( $meta->meta_value ) ."\n\t\t\t\n"; - } - $wxr_post .= "\t\t\n"; - } - $wxr_post .= "\t\n"; - $job_object->substeps_done ++; - } - $written = file_put_contents( $job_object->steps_data[ $job_object->step_working ][ 'wpexportfile' ], $wxr_post, FILE_APPEND ); - if ( $written === FALSE ) { - $job_object->log( __( 'WP Export file could not written.', 'backwpup' ), E_USER_ERROR ); - - return FALSE; - } - $job_object->do_restart_time(); - } - } - $written = file_put_contents( $job_object->steps_data[ $job_object->step_working ][ 'wpexportfile' ], "\n", FILE_APPEND ); - if ( $written === FALSE ) { - $job_object->log( __( 'WP Export file could not written.', 'backwpup' ), E_USER_ERROR ); - - return FALSE; - } - $job_object->steps_data[ $job_object->step_working ]['substep'] = 'check'; - $job_object->substeps_done ++; - $job_object->update_working_data(); - $job_object->do_restart_time(); - } - - remove_filter( 'backwpup_wxr_export_skip_postmeta', array( $this, 'wxr_filter_postmeta' ), 10 ); - - if ( $job_object->steps_data[ $job_object->step_working ]['substep'] == 'check' ) { - if ( class_exists( 'DOMDocument' ) ) { - $job_object->log( __( 'Check WP Export file …', 'backwpup' ) ); - $job_object->need_free_memory( filesize( $job_object->steps_data[ $job_object->step_working ]['wpexportfile'] ) * 2 ); - - try { - $validator = new WxrValidator( $job_object->steps_data[ $job_object->step_working ]['wpexportfile'] ); - $validator->validateWxr(); - - $job_object->log( __( 'WP Export file is a valid WXR file.', 'backwpup' ) ); - } catch (InvalidXmlException $e) { - $errors = $e->getErrors(); - - foreach ( $errors as $error ) { - switch ( $error->level ) { - case LIBXML_ERR_WARNING: - $job_object->log( E_USER_WARNING, sprintf( __( 'XML WARNING (%s): %s', 'backwpup' ), $error->code, trim( $error->message ) ), $job_object->steps_data[ $job_object->step_working ]['wpexportfile'], $error->line ); - break; - case LIBXML_ERR_ERROR: - $job_object->log( E_USER_WARNING, sprintf( __( 'XML RECOVERABLE (%s): %s', 'backwpup' ), $error->code, trim( $error->message ) ), $job_object->steps_data[ $job_object->step_working ]['wpexportfile'], $error->line ); - break; - case LIBXML_ERR_FATAL: - $job_object->log( E_USER_WARNING, sprintf( __( 'XML ERROR (%s): %s', 'backwpup' ),$error->code, trim( $error->message ) ), $job_object->steps_data[ $job_object->step_working ]['wpexportfile'], $error->line ); - break; - } - } - } catch (InvalidWxrFileException $e) { - $job_object->log( $e->getMessage(), E_USER_ERROR ); - } - } else { - $job_object->log( __( 'WP Export file can not be checked, because no XML extension is loaded, to ensure the file verification.', 'backwpup' ) ); - } - - $job_object->steps_data[ $job_object->step_working ]['substep'] = 'compress'; - $job_object->substeps_done ++; - $job_object->update_working_data(); - $job_object->do_restart_time(); - } - - //Compress file - if ( $job_object->steps_data[ $job_object->step_working ]['substep'] == 'compress' ) { - if ( ! empty( $job_object->job[ 'wpexportfilecompression' ] ) ) { - $job_object->log( __( 'Compressing file …', 'backwpup' ) ); - try { - $compress = new BackWPup_Create_Archive( $job_object->steps_data[ $job_object->step_working ]['wpexportfile'] . $job_object->job[ 'wpexportfilecompression' ] ); - if ( $compress->add_file( $job_object->steps_data[ $job_object->step_working ]['wpexportfile'] ) ) { - unset( $compress ); - unlink( $job_object->steps_data[ $job_object->step_working ]['wpexportfile'] ); - $job_object->steps_data[ $job_object->step_working ]['wpexportfile'] .= $job_object->job[ 'wpexportfilecompression' ]; - $job_object->log( __( 'Compressing done.', 'backwpup' ) ); - } - } catch ( Exception $e ) { - $job_object->log( $e->getMessage(), E_USER_ERROR, $e->getFile(), $e->getLine() ); - unset( $compress ); - return FALSE; - } - } - $job_object->steps_data[ $job_object->step_working ]['substep'] = 'addfile'; - $job_object->substeps_done ++; - $job_object->update_working_data(); - $job_object->do_restart_time(); - } - - if ( $job_object->steps_data[ $job_object->step_working ]['substep'] == 'addfile' ) { - //add XML file to backup files - if ( is_readable( $job_object->steps_data[ $job_object->step_working ]['wpexportfile'] ) ) { - $job_object->additional_files_to_backup[ ] = $job_object->steps_data[ $job_object->step_working ]['wpexportfile']; - $filesize = filesize( $job_object->steps_data[ $job_object->step_working ][ 'wpexportfile' ] ); - $job_object->log( sprintf( __( 'Added XML export "%1$s" with %2$s to backup file list.', 'backwpup' ), basename( $job_object->steps_data[ $job_object->step_working ]['wpexportfile'] ), size_format( $filesize, 2 ) ) ); - } - $job_object->substeps_done ++; - $job_object->update_working_data(); - } - - return TRUE; - } - - /** - * Wrap given string in XML CDATA tag. - * - * @since WordPress 2.1.0 - * - * @param string $str String to wrap in XML CDATA tag. - * @return string - */ - private function wxr_cdata( $str ) { - if ( ! seems_utf8( $str ) ) { - $str = utf8_encode( $str ); - } - - // not allowed UTF-8 chars in XML - $str = preg_replace( '/[^\x{0009}\x{000a}\x{000d}\x{0020}-\x{D7FF}\x{E000}-\x{FFFD}]+/u', '', $str ); - - // $str = ent2ncr(esc_html($str)); - $str = '', ']]]]>', $str ) . ']]>'; - - return $str; - } - - /** - * Return the URL of the site - * - * @since WordPress 2.5.0 - * - * @return string Site URL. - */ - private function wxr_site_url() { - // ms: the base url - if ( is_multisite() ) - return network_home_url(); - // wp: the blog url - else - return get_bloginfo_rss( 'url' ); - } - - /** - * Output a cat_name XML tag from a given category object - * - * @since WordPress 2.1.0 - * - * @param object $category Category Object - * @return string - */ - private function wxr_cat_name( $category ) { - if ( empty( $category->name ) ) - return ''; - - return '' . $this->wxr_cdata( $category->name ) . ''; - } - - /** - * Output a category_description XML tag from a given category object - * - * @since WordPress 2.1.0 - * - * @param object $category Category Object - * @return string - */ - private function wxr_category_description( $category ) { - if ( empty( $category->description ) ) - return ''; - - return '' . $this->wxr_cdata( $category->description ) . ''; - } - - /** - * Output a tag_name XML tag from a given tag object - * - * @since WordPress 2.3.0 - * - * @param object $tag Tag Object - * @return string - */ - private function wxr_tag_name( $tag ) { - if ( empty( $tag->name ) ) - return ''; - - return '' . $this->wxr_cdata( $tag->name ) . ''; - } - - /** - * Output a tag_description XML tag from a given tag object - * - * @since WordPress 2.3.0 - * - * @param object $tag Tag Object - * @return string - */ - private function wxr_tag_description( $tag ) { - if ( empty( $tag->description ) ) - return ''; - - return '' . $this->wxr_cdata( $tag->description ) . ''; - } - - /** - * Output a term_name XML tag from a given term object - * - * @since WordPress 2.9.0 - * - * @param object $term Term Object - * @return string - */ - private function wxr_term_name( $term ) { - if ( empty( $term->name ) ) - return ''; - - return '' . $this->wxr_cdata( $term->name ) . ''; - } - - /** - * Output a term_description XML tag from a given term object - * - * @since WordPress 2.9.0 - * - * @param object $term Term Object - * @return string - */ - private function wxr_term_description( $term ) { - if ( empty( $term->description ) ) - return ''; - - return '' . $this->wxr_cdata( $term->description ) . ''; - } - - /** - * Output list of authors with posts - * - * @since WordPress 3.1.0 - * - * @return string - */ - private function wxr_authors_list( ) { - global $wpdb; - - $authors = array(); - $results = $wpdb->get_results( "SELECT DISTINCT post_author FROM $wpdb->posts WHERE post_status != 'auto-draft'" ); - foreach ( (array) $results as $result ) - $authors[] = get_userdata( $result->post_author ); - - $authors = array_filter( $authors ); - - $wxr_authors = ''; - - foreach ( $authors as $author ) { - $wxr_authors .= "\t"; - $wxr_authors .= '' . $author->ID . ''; - $wxr_authors .= '' . $author->user_login . ''; - $wxr_authors .= '' . $author->user_email . ''; - $wxr_authors .= '' . $this->wxr_cdata( $author->display_name ) . ''; - $wxr_authors .= '' . $this->wxr_cdata( $author->user_firstname ) . ''; - $wxr_authors .= '' . $this->wxr_cdata( $author->user_lastname ) . ''; - $wxr_authors .= "\n"; - } - - return $wxr_authors; - } - - /** - * Ouput all navigation menu terms - * - * @since WordPress 3.1.0 - */ - private function wxr_nav_menu_terms() { - $nav_menus = wp_get_nav_menus(); - if ( empty( $nav_menus ) || ! is_array( $nav_menus ) ) - return ''; - - $wxr_nav_meuns = ''; - - foreach ( $nav_menus as $menu ) { - $wxr_nav_meuns .= "\t{$menu->term_id}nav_menu{$menu->slug}"; - $wxr_nav_meuns .= $this->wxr_term_name( $menu ); - $wxr_nav_meuns .= "\n"; - } - - return $wxr_nav_meuns; - } - - /** - * Output list of taxonomy terms, in XML tag format, associated with a post - * - * @since WordPress 2.3.0 - */ - private function wxr_post_taxonomy() { - $post = get_post(); - - $taxonomies = get_object_taxonomies( $post->post_type ); - if ( empty( $taxonomies ) ) - return ''; - $terms = wp_get_object_terms( $post->ID, $taxonomies ); - - $wxr_post_tags = ''; - - foreach ( (array) $terms as $term ) { - $wxr_post_tags .= "\t\ttaxonomy}\" nicename=\"{$term->slug}\">" . $this->wxr_cdata( $term->name ) . "\n"; - } - - return $wxr_post_tags; - } - - public function wxr_filter_postmeta( $return_me, $meta_key ) { - if ( '_edit_lock' == $meta_key ) - $return_me = true; - return $return_me; - } + } + + /** + * @param int $id + */ + public function edit_form_post_save($id) + { + BackWPup_Option::update($id, 'wpexportcontent', sanitize_text_field($_POST['wpexportcontent'])); + BackWPup_Option::update($id, 'wpexportfile', BackWPup_Job::sanitize_file_name($_POST['wpexportfile'])); + if ($_POST['wpexportfilecompression'] === '' || $_POST['wpexportfilecompression'] === '.gz' || $_POST['wpexportfilecompression'] === '.bz2') { + BackWPup_Option::update($id, 'wpexportfilecompression', $_POST['wpexportfilecompression']); + } + } + + /** + * @return bool + */ + public function job_run(BackWPup_Job $job_object) + { + global $wpdb, $post, $wp_query; + + $wxr_version = '1.2'; + + if ($job_object->steps_data[$job_object->step_working]['SAVE_STEP_TRY'] != $job_object->steps_data[$job_object->step_working]['STEP_TRY']) { + $job_object->log(sprintf(__('%d. Trying to create a WordPress export to XML file …', 'backwpup'), $job_object->steps_data[$job_object->step_working]['STEP_TRY'])); + $job_object->steps_data[$job_object->step_working]['wpexportfile'] = BackWPup::get_plugin_data('TEMP') . $job_object->generate_filename($job_object->job['wpexportfile'], 'xml', true); + $job_object->steps_data[$job_object->step_working]['substep'] = 'header'; + $job_object->steps_data[$job_object->step_working]['post_ids'] = []; + $job_object->substeps_todo = 10; + $job_object->substeps_done = 0; + } + + add_filter('backwpup_wxr_export_skip_postmeta', [$this, 'wxr_filter_postmeta'], 10, 2); + + if ($job_object->steps_data[$job_object->step_working]['substep'] == 'header') { + if ('all' != $job_object->job['wpexportcontent'] && post_type_exists($job_object->job['wpexportcontent'])) { + $ptype = get_post_type_object($job_object->job['wpexportcontent']); + if (!$ptype->can_export) { + $job_object->log(sprintf(__('WP Export: Post type “%s” does not allow export.', 'backwpup'), $job_object->job['wpexportcontent']), E_USER_ERROR); + + return false; + } + $where = $wpdb->prepare("{$wpdb->posts}.post_type = %s", $job_object->job['wpexportcontent']); + } else { + $post_types = get_post_types(['can_export' => true]); + $esses = array_fill(0, count($post_types), '%s'); + $where = $wpdb->prepare("{$wpdb->posts}.post_type IN (" . implode(',', $esses) . ')', $post_types); + $job_object->job['wpexportcontent'] = 'all'; + } + $where .= " AND {$wpdb->posts}.post_status != 'auto-draft'"; + + // grab a snapshot of post IDs, just in case it changes during the export + $job_object->steps_data[$job_object->step_working]['post_ids'] = $wpdb->get_col("SELECT ID FROM {$wpdb->posts} WHERE {$where}"); + $job_object->substeps_todo = $job_object->substeps_todo + count($job_object->steps_data[$job_object->step_working]['post_ids']); + + $header = '\n"; + $header .= "\n"; + $header .= "\n"; + $header .= "\n"; + $header .= "\n\n"; + $header .= "\n"; + $header .= "\n"; + $header .= "\n"; + $header .= "\n"; + $header .= "\n"; + $header .= "\n"; + $header .= "\n"; + $header .= "\n"; + $header .= "\n"; + $header .= "\n"; + $header .= "\n\n"; + $header .= '\n"; + $header .= "\n"; + $header .= "\n"; + $header .= "\t" . get_bloginfo_rss('name') . "\n"; + $header .= "\t" . get_bloginfo_rss('url') . "\n"; + $header .= "\t" . get_bloginfo_rss('description') . "\n"; + $header .= "\t" . date('D, d M Y H:i:s +0000') . "\n"; + $header .= "\t" . get_bloginfo_rss('language') . "\n"; + $header .= "\t" . $wxr_version . "\n"; + $header .= "\t" . $this->wxr_site_url() . "\n"; + $header .= "\t" . get_bloginfo_rss('url') . "\n"; + $written = file_put_contents($job_object->steps_data[$job_object->step_working]['wpexportfile'], $header, FILE_APPEND); + if ($written === false) { + $job_object->log(__('WP Export file could not written.', 'backwpup'), E_USER_ERROR); + + return false; + } + unset($header); + $job_object->steps_data[$job_object->step_working]['substep'] = 'authors'; + ++$job_object->substeps_done; + $job_object->update_working_data(); + $job_object->do_restart_time(); + } + + if ($job_object->steps_data[$job_object->step_working]['substep'] == 'authors') { + $written = file_put_contents($job_object->steps_data[$job_object->step_working]['wpexportfile'], $this->wxr_authors_list(), FILE_APPEND); + if ($written === false) { + $job_object->log(__('WP Export file could not written.', 'backwpup'), E_USER_ERROR); + + return false; + } + $job_object->steps_data[$job_object->step_working]['substep'] = 'cats'; + ++$job_object->substeps_done; + $job_object->update_working_data(); + $job_object->do_restart_time(); + } + + if ($job_object->steps_data[$job_object->step_working]['substep'] == 'cats') { + if ('all' == $job_object->job['wpexportcontent']) { + $cats = []; + $categories = (array) get_categories(['get' => 'all']); + // put categories in order with no child going before its parent + while ($cat = array_shift($categories)) { + if ($cat->parent == 0 || isset($cats[$cat->parent])) { + $cats[$cat->term_id] = $cat; + } else { + $categories[] = $cat; + } + } + $cats_xml = ''; + + foreach ($cats as $c) { + $parent_slug = $c->parent ? $cats[$c->parent]->slug : ''; + $cats_xml .= "\t" . $c->term_id . '' . $c->slug . '' . $parent_slug . '' . $this->wxr_cat_name($c) . $this->wxr_category_description($c) . "\n"; + } + $written = file_put_contents($job_object->steps_data[$job_object->step_working]['wpexportfile'], $cats_xml, FILE_APPEND); + if ($written === false) { + $job_object->log(__('WP Export file could not written.', 'backwpup'), E_USER_ERROR); + + return false; + } + unset($cats_xml); + } + $job_object->steps_data[$job_object->step_working]['substep'] = 'tags'; + ++$job_object->substeps_done; + $job_object->update_working_data(); + $job_object->do_restart_time(); + } + + if ($job_object->steps_data[$job_object->step_working]['substep'] == 'tags') { + if ('all' == $job_object->job['wpexportcontent']) { + $tags = (array) get_tags(['get' => 'all']); + $tags_xml = ''; + + foreach ($tags as $t) { + $tags_xml .= "\t" . $t->term_id . '' . $t->slug . '' . $this->wxr_tag_name($t) . $this->wxr_tag_description($t) . "\n"; + } + $written = file_put_contents($job_object->steps_data[$job_object->step_working]['wpexportfile'], $tags_xml, FILE_APPEND); + if ($written === false) { + $job_object->log(__('WP Export file could not written.', 'backwpup'), E_USER_ERROR); + + return false; + } + unset($tags_xml); + } + $job_object->steps_data[$job_object->step_working]['substep'] = 'terms'; + ++$job_object->substeps_done; + $job_object->update_working_data(); + $job_object->do_restart_time(); + } + + if ($job_object->steps_data[$job_object->step_working]['substep'] == 'terms') { + if ('all' == $job_object->job['wpexportcontent']) { + $terms = []; + $custom_taxonomies = get_taxonomies(['_builtin' => false]); + $custom_terms = (array) get_terms($custom_taxonomies, ['get' => 'all']); + + // put terms in order with no child going before its parent + while ($t = array_shift($custom_terms)) { + if ($t->parent == 0 || isset($terms[$t->parent])) { + $terms[$t->term_id] = $t; + } else { + $custom_terms[] = $t; + } + } + $terms_xml = ''; + + foreach ($terms as $t) { + $parent_slug = $t->parent ? $terms[$t->parent]->slug : ''; + $terms_xml .= "\t" . $t->term_id . '' . $t->taxonomy . '' . $t->slug . '' . $parent_slug . '' . $this->wxr_term_name($t) . $this->wxr_term_description($t) . "\n"; + } + $written = file_put_contents($job_object->steps_data[$job_object->step_working]['wpexportfile'], $terms_xml, FILE_APPEND); + if ($written === false) { + $job_object->log(__('WP Export file could not written.', 'backwpup'), E_USER_ERROR); + + return false; + } + unset($terms_xml); + } + $job_object->steps_data[$job_object->step_working]['substep'] = 'menus'; + ++$job_object->substeps_done; + $job_object->update_working_data(); + $job_object->do_restart_time(); + } + + if ($job_object->steps_data[$job_object->step_working]['substep'] == 'menus') { + $menu_xml = ''; + if ('all' == $job_object->job['wpexportcontent']) { + $menu_xml .= $this->wxr_nav_menu_terms(); + } + $menu_xml .= "\thttp://wordpress.org/?v=" . get_bloginfo_rss('version') . "\n"; + $written = file_put_contents($job_object->steps_data[$job_object->step_working]['wpexportfile'], $menu_xml, FILE_APPEND); + if ($written === false) { + $job_object->log(__('WP Export file could not written.', 'backwpup'), E_USER_ERROR); + + return false; + } + unset($menu_xml); + + $job_object->steps_data[$job_object->step_working]['substep'] = 'posts'; + ++$job_object->substeps_done; + $job_object->update_working_data(); + $job_object->do_restart_time(); + } + + if ($job_object->steps_data[$job_object->step_working]['substep'] == 'posts') { + if (!empty($job_object->steps_data[$job_object->step_working]['post_ids'])) { + $wp_query->in_the_loop = true; // Fake being in the loop. + + // fetch 20 posts at a time rather than loading the entire table into memory + while ($next_posts = array_splice($job_object->steps_data[$job_object->step_working]['post_ids'], 0, 20)) { + $where = 'WHERE ID IN (' . join(',', $next_posts) . ')'; + $posts = $wpdb->get_results("SELECT * FROM {$wpdb->posts} {$where}"); + $wxr_post = ''; + // Begin Loop + /** @var WP_Post $post */ + foreach ($posts as $post) { + $is_sticky = is_sticky($post->ID) ? 1 : 0; + + $wxr_post .= "\t\n"; + $wxr_post .= "\t\t" . apply_filters('the_title_rss', $post->post_title) . "\n"; + $wxr_post .= "\t\t" . esc_url(apply_filters('the_permalink_rss', get_permalink($post))) . "\n"; + $wxr_post .= "\t\t" . mysql2date('D, d M Y H:i:s +0000', get_post_time('Y-m-d H:i:s', true, $post), false) . "\n"; + $wxr_post .= "\t\t" . $this->wxr_cdata(get_the_author_meta('login', $post->post_author)) . "\n"; + $wxr_post .= "\t\t" . esc_url(get_the_guid($post->ID)) . "\n"; + $wxr_post .= "\t\t\n"; + $wxr_post .= "\t\t" . $this->wxr_cdata(apply_filters('the_content_export', $post->post_content)) . "\n"; + $wxr_post .= "\t\t" . $this->wxr_cdata(apply_filters('the_excerpt_export', $post->post_excerpt)) . "\n"; + $wxr_post .= "\t\t" . $post->ID . "\n"; + $wxr_post .= "\t\t" . $post->post_date . "\n"; + $wxr_post .= "\t\t" . $post->post_date_gmt . "\n"; + $wxr_post .= "\t\t" . $post->comment_status . "\n"; + $wxr_post .= "\t\t" . $post->ping_status . "\n"; + $wxr_post .= "\t\t" . $post->post_name . "\n"; + $wxr_post .= "\t\t" . $post->post_status . "\n"; + $wxr_post .= "\t\t" . $post->post_parent . "\n"; + $wxr_post .= "\t\t" . $post->menu_order . "\n"; + $wxr_post .= "\t\t" . $post->post_type . "\n"; + $wxr_post .= "\t\t" . $post->post_password . "\n"; + $wxr_post .= "\t\t" . $is_sticky . "\n"; + if ($post->post_type == 'attachment') { + $wxr_post .= "\t\t" . wp_get_attachment_url($post->ID) . "\n"; + } + $wxr_post .= $this->wxr_post_taxonomy(); + + $postmeta = $wpdb->get_results($wpdb->prepare("SELECT * FROM {$wpdb->postmeta} WHERE post_id = %d", $post->ID)); + + foreach ($postmeta as $meta) { + if (apply_filters('backwpup_wxr_export_skip_postmeta', false, $meta->meta_key, $meta)) { + continue; + } + $wxr_post .= "\t\t\n\t\t\t" . $meta->meta_key . "\n\t\t\t" . $this->wxr_cdata($meta->meta_value) . "\n\t\t\n"; + } + + $comments = $wpdb->get_results($wpdb->prepare("SELECT * FROM {$wpdb->comments} WHERE comment_post_ID = %d AND comment_approved <> 'spam'", $post->ID)); + + foreach ($comments as $c) { + $wxr_post .= "\t\t\n"; + $wxr_post .= "\t\t\t" . $c->comment_ID . "\n"; + $wxr_post .= "\t\t\t" . $this->wxr_cdata($c->comment_author) . "\n"; + $wxr_post .= "\t\t\t" . $c->comment_author_email . "\n"; + $wxr_post .= "\t\t\t" . esc_url_raw($c->comment_author_url) . "\n"; + $wxr_post .= "\t\t\t" . $c->comment_author_IP . "\n"; + $wxr_post .= "\t\t\t" . $c->comment_date . "\n"; + $wxr_post .= "\t\t\t" . $c->comment_date_gmt . "\n"; + $wxr_post .= "\t\t\t" . $this->wxr_cdata($c->comment_content) . "\n"; + $wxr_post .= "\t\t\t" . $c->comment_approved . "\n"; + $wxr_post .= "\t\t\t" . $c->comment_type . "\n"; + $wxr_post .= "\t\t\t" . $c->comment_parent . "\n"; + $wxr_post .= "\t\t\t" . $c->user_id . "\n"; + $c_meta = $wpdb->get_results($wpdb->prepare("SELECT * FROM {$wpdb->commentmeta} WHERE comment_id = %d", $c->comment_ID)); + + foreach ($c_meta as $meta) { + $wxr_post .= "\t\t\t\n\t\t\t\t" . $meta->meta_key . "\n\t\t\t\t" . $this->wxr_cdata($meta->meta_value) . "\n\t\t\t\n"; + } + $wxr_post .= "\t\t\n"; + } + $wxr_post .= "\t\n"; + ++$job_object->substeps_done; + } + $written = file_put_contents($job_object->steps_data[$job_object->step_working]['wpexportfile'], $wxr_post, FILE_APPEND); + if ($written === false) { + $job_object->log(__('WP Export file could not written.', 'backwpup'), E_USER_ERROR); + + return false; + } + $job_object->do_restart_time(); + } + } + $written = file_put_contents($job_object->steps_data[$job_object->step_working]['wpexportfile'], "\n", FILE_APPEND); + if ($written === false) { + $job_object->log(__('WP Export file could not written.', 'backwpup'), E_USER_ERROR); + + return false; + } + $job_object->steps_data[$job_object->step_working]['substep'] = 'check'; + ++$job_object->substeps_done; + $job_object->update_working_data(); + $job_object->do_restart_time(); + } + + remove_filter('backwpup_wxr_export_skip_postmeta', [$this, 'wxr_filter_postmeta'], 10); + + if ($job_object->steps_data[$job_object->step_working]['substep'] == 'check') { + if (class_exists(\DOMDocument::class)) { + $job_object->log(__('Check WP Export file …', 'backwpup')); + $job_object->need_free_memory(filesize($job_object->steps_data[$job_object->step_working]['wpexportfile']) * 2); + + try { + $validator = new WxrValidator($job_object->steps_data[$job_object->step_working]['wpexportfile']); + $validator->validateWxr(); + + $job_object->log(__('WP Export file is a valid WXR file.', 'backwpup')); + } catch (InvalidXmlException $e) { + $errors = $e->getErrors(); + + foreach ($errors as $error) { + switch ($error->level) { + case LIBXML_ERR_WARNING: + $job_object->log(E_USER_WARNING, sprintf(__('XML WARNING (%s): %s', 'backwpup'), $error->code, trim($error->message)), $job_object->steps_data[$job_object->step_working]['wpexportfile'], $error->line); + break; + + case LIBXML_ERR_ERROR: + $job_object->log(E_USER_WARNING, sprintf(__('XML RECOVERABLE (%s): %s', 'backwpup'), $error->code, trim($error->message)), $job_object->steps_data[$job_object->step_working]['wpexportfile'], $error->line); + break; + + case LIBXML_ERR_FATAL: + $job_object->log(E_USER_WARNING, sprintf(__('XML ERROR (%s): %s', 'backwpup'), $error->code, trim($error->message)), $job_object->steps_data[$job_object->step_working]['wpexportfile'], $error->line); + break; + } + } + } catch (InvalidWxrFileException $e) { + $job_object->log($e->getMessage(), E_USER_ERROR); + } + } else { + $job_object->log(__('WP Export file can not be checked, because no XML extension is loaded, to ensure the file verification.', 'backwpup')); + } + + $job_object->steps_data[$job_object->step_working]['substep'] = 'compress'; + ++$job_object->substeps_done; + $job_object->update_working_data(); + $job_object->do_restart_time(); + } + + //Compress file + if ($job_object->steps_data[$job_object->step_working]['substep'] == 'compress') { + if (!empty($job_object->job['wpexportfilecompression'])) { + $job_object->log(__('Compressing file …', 'backwpup')); + + try { + $compress = new BackWPup_Create_Archive($job_object->steps_data[$job_object->step_working]['wpexportfile'] . $job_object->job['wpexportfilecompression']); + if ($compress->add_file($job_object->steps_data[$job_object->step_working]['wpexportfile'])) { + unset($compress); + unlink($job_object->steps_data[$job_object->step_working]['wpexportfile']); + $job_object->steps_data[$job_object->step_working]['wpexportfile'] .= $job_object->job['wpexportfilecompression']; + $job_object->log(__('Compressing done.', 'backwpup')); + } + } catch (Exception $e) { + $job_object->log($e->getMessage(), E_USER_ERROR, $e->getFile(), $e->getLine()); + + return false; + } + } + $job_object->steps_data[$job_object->step_working]['substep'] = 'addfile'; + ++$job_object->substeps_done; + $job_object->update_working_data(); + $job_object->do_restart_time(); + } + + if ($job_object->steps_data[$job_object->step_working]['substep'] == 'addfile') { + //add XML file to backup files + if (is_readable($job_object->steps_data[$job_object->step_working]['wpexportfile'])) { + $job_object->additional_files_to_backup[] = $job_object->steps_data[$job_object->step_working]['wpexportfile']; + $filesize = filesize($job_object->steps_data[$job_object->step_working]['wpexportfile']); + $job_object->log(sprintf(__('Added XML export "%1$s" with %2$s to backup file list.', 'backwpup'), basename($job_object->steps_data[$job_object->step_working]['wpexportfile']), size_format($filesize, 2))); + } + ++$job_object->substeps_done; + $job_object->update_working_data(); + } + + return true; + } + + /** + * Wrap given string in XML CDATA tag. + * + * @since WordPress 2.1.0 + * + * @param string $str string to wrap in XML CDATA tag + * + * @return string + */ + private function wxr_cdata($str) + { + if (!seems_utf8($str)) { + $str = utf8_encode($str); + } + + // not allowed UTF-8 chars in XML + $str = preg_replace('/[^\x{0009}\x{000a}\x{000d}\x{0020}-\x{D7FF}\x{E000}-\x{FFFD}]+/u', '', $str); + + // $str = ent2ncr(esc_html($str)); + return '', ']]]]>', $str) . ']]>'; + } + + /** + * Return the URL of the site. + * + * @since WordPress 2.5.0 + * + * @return string site URL + */ + private function wxr_site_url() + { + // ms: the base url + if (is_multisite()) { + return network_home_url(); + } + // wp: the blog url + + return get_bloginfo_rss('url'); + } + + /** + * Output a cat_name XML tag from a given category object. + * + * @since WordPress 2.1.0 + * + * @param object $category Category Object + * + * @return string + */ + private function wxr_cat_name($category) + { + if (empty($category->name)) { + return ''; + } + + return '' . $this->wxr_cdata($category->name) . ''; + } + + /** + * Output a category_description XML tag from a given category object. + * + * @since WordPress 2.1.0 + * + * @param object $category Category Object + * + * @return string + */ + private function wxr_category_description($category) + { + if (empty($category->description)) { + return ''; + } + + return '' . $this->wxr_cdata($category->description) . ''; + } + + /** + * Output a tag_name XML tag from a given tag object. + * + * @since WordPress 2.3.0 + * + * @param object $tag Tag Object + * + * @return string + */ + private function wxr_tag_name($tag) + { + if (empty($tag->name)) { + return ''; + } + + return '' . $this->wxr_cdata($tag->name) . ''; + } + + /** + * Output a tag_description XML tag from a given tag object. + * + * @since WordPress 2.3.0 + * + * @param object $tag Tag Object + * + * @return string + */ + private function wxr_tag_description($tag) + { + if (empty($tag->description)) { + return ''; + } + + return '' . $this->wxr_cdata($tag->description) . ''; + } + + /** + * Output a term_name XML tag from a given term object. + * + * @since WordPress 2.9.0 + * + * @param object $term Term Object + * + * @return string + */ + private function wxr_term_name($term) + { + if (empty($term->name)) { + return ''; + } + + return '' . $this->wxr_cdata($term->name) . ''; + } + + /** + * Output a term_description XML tag from a given term object. + * + * @since WordPress 2.9.0 + * + * @param object $term Term Object + * + * @return string + */ + private function wxr_term_description($term) + { + if (empty($term->description)) { + return ''; + } + + return '' . $this->wxr_cdata($term->description) . ''; + } + + /** + * Output list of authors with posts. + * + * @since WordPress 3.1.0 + * + * @return string + */ + private function wxr_authors_list() + { + global $wpdb; + + $authors = []; + $results = $wpdb->get_results("SELECT DISTINCT post_author FROM {$wpdb->posts} WHERE post_status != 'auto-draft'"); + + foreach ((array) $results as $result) { + $authors[] = get_userdata($result->post_author); + } + + $authors = array_filter($authors); + + $wxr_authors = ''; + + foreach ($authors as $author) { + $wxr_authors .= "\t"; + $wxr_authors .= '' . $author->ID . ''; + $wxr_authors .= '' . $author->user_login . ''; + $wxr_authors .= '' . $author->user_email . ''; + $wxr_authors .= '' . $this->wxr_cdata($author->display_name) . ''; + $wxr_authors .= '' . $this->wxr_cdata($author->user_firstname) . ''; + $wxr_authors .= '' . $this->wxr_cdata($author->user_lastname) . ''; + $wxr_authors .= "\n"; + } + + return $wxr_authors; + } + + /** + * Ouput all navigation menu terms. + * + * @since WordPress 3.1.0 + */ + private function wxr_nav_menu_terms() + { + $nav_menus = wp_get_nav_menus(); + if (empty($nav_menus) || !is_array($nav_menus)) { + return ''; + } + + $wxr_nav_meuns = ''; + + foreach ($nav_menus as $menu) { + $wxr_nav_meuns .= "\t{$menu->term_id}nav_menu{$menu->slug}"; + $wxr_nav_meuns .= $this->wxr_term_name($menu); + $wxr_nav_meuns .= "\n"; + } + + return $wxr_nav_meuns; + } + + /** + * Output list of taxonomy terms, in XML tag format, associated with a post. + * + * @since WordPress 2.3.0 + */ + private function wxr_post_taxonomy() + { + $post = get_post(); + + $taxonomies = get_object_taxonomies($post->post_type); + if (empty($taxonomies)) { + return ''; + } + $terms = wp_get_object_terms($post->ID, $taxonomies); + + $wxr_post_tags = ''; + + foreach ((array) $terms as $term) { + $wxr_post_tags .= "\t\ttaxonomy}\" nicename=\"{$term->slug}\">" . $this->wxr_cdata($term->name) . "\n"; + } + + return $wxr_post_tags; + } + + public function wxr_filter_postmeta($return_me, $meta_key) + { + if ('_edit_lock' == $meta_key) { + $return_me = true; + } + + return $return_me; + } } diff --git a/inc/class-jobtype-wpplugin.php b/inc/class-jobtype-wpplugin.php index 955ae97c..766341ea 100644 --- a/inc/class-jobtype-wpplugin.php +++ b/inc/class-jobtype-wpplugin.php @@ -1,141 +1,148 @@ info[ 'ID' ] = 'WPPLUGIN'; - $this->info[ 'name' ] = __( 'Plugins', 'backwpup' ); - $this->info[ 'description' ] = __( 'Installed plugins list', 'backwpup' ); - $this->info[ 'URI' ] = __( 'http://backwpup.com', 'backwpup' ); - $this->info[ 'author' ] = 'Inpsyde GmbH'; - $this->info[ 'authorURI' ] = __( 'http://inpsyde.com', 'backwpup' ); - $this->info[ 'version' ] = BackWPup::get_plugin_data( 'Version' ); - - } - - /** - * @return bool - */ - public function creates_file() { - - return TRUE; - } - - /** - * @return array - */ - public function option_defaults() { - return array( 'pluginlistfilecompression' => '', 'pluginlistfile' => sanitize_file_name( get_bloginfo( 'name' ) ) . '.pluginlist.%Y-%m-%d' ); - } - - /** - * @param $jobid - * @return void - */ - public function edit_tab( $jobid ) { - ?> +class BackWPup_JobType_WPPlugin extends BackWPup_JobTypes +{ + public function __construct() + { + $this->info['ID'] = 'WPPLUGIN'; + $this->info['name'] = __('Plugins', 'backwpup'); + $this->info['description'] = __('Installed plugins list', 'backwpup'); + $this->info['URI'] = __('http://backwpup.com', 'backwpup'); + $this->info['author'] = 'Inpsyde GmbH'; + $this->info['authorURI'] = __('http://inpsyde.com', 'backwpup'); + $this->info['version'] = BackWPup::get_plugin_data('Version'); + } + + /** + * @return bool + */ + public function creates_file() + { + return true; + } + + /** + * @return array + */ + public function option_defaults() + { + return ['pluginlistfilecompression' => '', 'pluginlistfile' => sanitize_file_name(get_bloginfo('name')) . '.pluginlist.%Y-%m-%d']; + } + + /** + * @param $jobid + */ + public function edit_tab($jobid) + { + ?> - + - +
.txt
' . esc_html__( 'none', 'backwpup' ). '
'; - if ( function_exists( 'gzopen' ) ) { - echo '
'; - } else { - echo '
'; - } - ?> + echo '
'; + if (function_exists('gzopen')) { + echo '
'; + } else { + echo '
'; + } ?>
substeps_todo = 1; - - $job_object->log( sprintf( __( '%d. Trying to generate a file with installed plugin names …', 'backwpup' ), $job_object->steps_data[ $job_object->step_working ][ 'STEP_TRY' ] ) ); - //build filename - if ( empty( $job_object->temp[ 'pluginlistfile' ] ) ) - $job_object->temp[ 'pluginlistfile' ] = $job_object->generate_filename( $job_object->job[ 'pluginlistfile' ], 'txt' ) . $job_object->job[ 'pluginlistfilecompression' ]; - - if ( $job_object->job[ 'pluginlistfilecompression' ] == '.gz' ) - $handle = fopen( 'compress.zlib://' . BackWPup::get_plugin_data( 'TEMP' ) . $job_object->temp[ 'pluginlistfile' ], 'w' ); - else - $handle = fopen( BackWPup::get_plugin_data( 'TEMP' ) . $job_object->temp[ 'pluginlistfile' ], 'w' ); - - if ( $handle ) { - //open file - $header = "------------------------------------------------------------" . PHP_EOL; - $header .= " Plugin list generated with BackWPup version: " . BackWPup::get_plugin_data( 'Version' ) . PHP_EOL; - $header .= " http://backwpup.com" . PHP_EOL; - $header .= " Blog Name: " . get_bloginfo( 'name' ) . PHP_EOL; - $header .= " Blog URL: " . get_bloginfo( 'url' ) . PHP_EOL; - $header .= " Generated on: " . date( 'Y-m-d H:i.s', current_time( 'timestamp' ) ) . PHP_EOL; - $header .= "------------------------------------------------------------" . PHP_EOL . PHP_EOL; - fwrite( $handle, $header ); - //get Plugins - if ( ! function_exists( 'get_plugins' ) ) - require_once ABSPATH . 'wp-admin/includes/plugin.php'; - $plugins = get_plugins(); - $plugins_active = get_option( 'active_plugins' ); - //write it to file - fwrite( $handle, PHP_EOL . __( 'All plugin information:', 'backwpup' ) . PHP_EOL . '------------------------------' . PHP_EOL ); - foreach ( $plugins as $plugin ) { - fwrite( $handle, $plugin[ 'Name' ] . ' (v.' . $plugin[ 'Version' ] . ') ' . html_entity_decode( sprintf( __( 'from %s', 'backwpup' ), $plugin[ 'Author' ] ), ENT_QUOTES ) . PHP_EOL . "\t" . $plugin[ 'PluginURI' ] . PHP_EOL ); - } - fwrite( $handle, PHP_EOL . __( 'Active plugins:', 'backwpup' ) . PHP_EOL . '------------------------------' . PHP_EOL ); - - foreach ( $plugins as $key => $plugin ) { - if ( in_array( $key, $plugins_active, true ) ) - fwrite( $handle, $plugin[ 'Name' ] . PHP_EOL ); - } - fwrite( $handle, PHP_EOL . __( 'Inactive plugins:', 'backwpup' ) . PHP_EOL . '------------------------------' . PHP_EOL ); - foreach ( $plugins as $key => $plugin ) { - if ( ! in_array( $key, $plugins_active, true ) ) - fwrite( $handle, $plugin[ 'Name' ] . PHP_EOL ); - } - fclose( $handle ); - } else { - $job_object->log( __( 'Can not open target file for writing.', 'backwpup' ), E_USER_ERROR ); - return FALSE; - } - - //add file to backup files - if ( is_readable( BackWPup::get_plugin_data( 'TEMP' ) . $job_object->temp[ 'pluginlistfile' ] ) ) { - $job_object->additional_files_to_backup[ ] = BackWPup::get_plugin_data( 'TEMP' ) . $job_object->temp[ 'pluginlistfile' ]; - $job_object->log( sprintf( __( 'Added plugin list file "%1$s" with %2$s to backup file list.', 'backwpup' ), $job_object->temp[ 'pluginlistfile' ], size_format( filesize( BackWPup::get_plugin_data( 'TEMP' ) . $job_object->temp[ 'pluginlistfile' ] ), 2 ) ) ); - } - $job_object->substeps_done = 1; - - return TRUE; - } + } + + /** + * @param $id + */ + public function edit_form_post_save($id) + { + BackWPup_Option::update($id, 'pluginlistfile', sanitize_text_field($_POST['pluginlistfile'])); + if ($_POST['pluginlistfilecompression'] === '' || $_POST['pluginlistfilecompression'] === '.gz' || $_POST['pluginlistfilecompression'] === '.bz2') { + BackWPup_Option::update($id, 'pluginlistfilecompression', $_POST['pluginlistfilecompression']); + } + } + + /** + * @param $job_object + * + * @return bool + */ + public function job_run(BackWPup_Job $job_object) + { + $job_object->substeps_todo = 1; + + $job_object->log(sprintf(__('%d. Trying to generate a file with installed plugin names …', 'backwpup'), $job_object->steps_data[$job_object->step_working]['STEP_TRY'])); + //build filename + if (empty($job_object->temp['pluginlistfile'])) { + $job_object->temp['pluginlistfile'] = $job_object->generate_filename($job_object->job['pluginlistfile'], 'txt') . $job_object->job['pluginlistfilecompression']; + } + + if ($job_object->job['pluginlistfilecompression'] == '.gz') { + $handle = fopen('compress.zlib://' . BackWPup::get_plugin_data('TEMP') . $job_object->temp['pluginlistfile'], 'w'); + } else { + $handle = fopen(BackWPup::get_plugin_data('TEMP') . $job_object->temp['pluginlistfile'], 'w'); + } + + if ($handle) { + //open file + $header = '------------------------------------------------------------' . PHP_EOL; + $header .= ' Plugin list generated with BackWPup version: ' . BackWPup::get_plugin_data('Version') . PHP_EOL; + $header .= ' http://backwpup.com' . PHP_EOL; + $header .= ' Blog Name: ' . get_bloginfo('name') . PHP_EOL; + $header .= ' Blog URL: ' . get_bloginfo('url') . PHP_EOL; + $header .= ' Generated on: ' . date('Y-m-d H:i.s', current_time('timestamp')) . PHP_EOL; + $header .= '------------------------------------------------------------' . PHP_EOL . PHP_EOL; + fwrite($handle, $header); + //get Plugins + if (!function_exists('get_plugins')) { + require_once ABSPATH . 'wp-admin/includes/plugin.php'; + } + $plugins = get_plugins(); + $plugins_active = get_option('active_plugins'); + //write it to file + fwrite($handle, PHP_EOL . __('All plugin information:', 'backwpup') . PHP_EOL . '------------------------------' . PHP_EOL); + + foreach ($plugins as $plugin) { + fwrite($handle, $plugin['Name'] . ' (v.' . $plugin['Version'] . ') ' . html_entity_decode(sprintf(__('from %s', 'backwpup'), $plugin['Author']), ENT_QUOTES) . PHP_EOL . "\t" . $plugin['PluginURI'] . PHP_EOL); + } + fwrite($handle, PHP_EOL . __('Active plugins:', 'backwpup') . PHP_EOL . '------------------------------' . PHP_EOL); + + foreach ($plugins as $key => $plugin) { + if (in_array($key, $plugins_active, true)) { + fwrite($handle, $plugin['Name'] . PHP_EOL); + } + } + fwrite($handle, PHP_EOL . __('Inactive plugins:', 'backwpup') . PHP_EOL . '------------------------------' . PHP_EOL); + + foreach ($plugins as $key => $plugin) { + if (!in_array($key, $plugins_active, true)) { + fwrite($handle, $plugin['Name'] . PHP_EOL); + } + } + fclose($handle); + } else { + $job_object->log(__('Can not open target file for writing.', 'backwpup'), E_USER_ERROR); + + return false; + } + + //add file to backup files + if (is_readable(BackWPup::get_plugin_data('TEMP') . $job_object->temp['pluginlistfile'])) { + $job_object->additional_files_to_backup[] = BackWPup::get_plugin_data('TEMP') . $job_object->temp['pluginlistfile']; + $job_object->log(sprintf(__('Added plugin list file "%1$s" with %2$s to backup file list.', 'backwpup'), $job_object->temp['pluginlistfile'], size_format(filesize(BackWPup::get_plugin_data('TEMP') . $job_object->temp['pluginlistfile']), 2))); + } + $job_object->substeps_done = 1; + + return true; + } } diff --git a/inc/class-jobtypes.php b/inc/class-jobtypes.php index f3d7a597..0f5a5759 100644 --- a/inc/class-jobtypes.php +++ b/inc/class-jobtypes.php @@ -1,86 +1,88 @@ set_box_html( @@ -9,128 +9,126 @@ * ); * $message_box->init_hooks(); */ -class BackWPup_Message_Box { - - /** - * ID of this message box - * @var string - */ - private $box_id = ''; - - /** - * HTML of this message box - * @var string - */ - private $box_html = ''; - - /** - * @var string Date to a campaign should be displayed - */ - private $campaign_to_date = '0000-00-00'; - - /** - * BackWPup_Message_Box constructor. - * - * @param string $box_id Name for box to have more than one or future one - */ - public function __construct( $box_id ) { - - if ( ! $box_id || ! is_string( $box_id ) ) { - return null; - } - - $this->box_id = sanitize_title_with_dashes( $box_id ); - } - - /** - * Init hooks to displaying message box - */ - public function init_hooks() { - - if ( ! current_user_can( 'backwpup' ) ) { - return; - } - - $boxes_display = get_user_meta( get_current_user_id(), 'backwpup_message_boxes_not_display', true ); - if ( ! $boxes_display ) { - $boxes_display = array(); - } - - if ( ! empty( $boxes_display[ $this->box_id ] ) ) { - return; - } - - if ( isset( $_GET['page'] ) && $_GET['page'] === 'backwpupabout' ) { - return; - } - - - if ( $this->campaign_to_date !== '0000-00-00' ) { - $this_day = date( 'Y-m-d' ); - if ( $this_day > $this->campaign_to_date ) { - return; - } - } - - add_action( 'admin_notices', array( $this, 'output_box_html' ) ); - add_action( 'admin_init', array( $this, 'save_not_display' ) ); - } - - /** - * Output the message box - */ - public function output_box_html() { - - $url = add_query_arg( array( 'backwpup_msg_' . $this->box_id => 1 ), '//' . $_SERVER['SERVER_NAME'] . $_SERVER['REQUEST_URI'] ); - - ?> +class BackWPup_Message_Box +{ + /** + * ID of this message box. + * + * @var string + */ + private $box_id = ''; + + /** + * HTML of this message box. + * + * @var string + */ + private $box_html = ''; + + /** + * @var string Date to a campaign should be displayed + */ + private $campaign_to_date = '0000-00-00'; + + /** + * BackWPup_Message_Box constructor. + * + * @param string $box_id Name for box to have more than one or future one + */ + public function __construct($box_id) + { + if (!$box_id || !is_string($box_id)) { + return null; + } + + $this->box_id = sanitize_title_with_dashes($box_id); + } + + /** + * Init hooks to displaying message box. + */ + public function init_hooks() + { + if (!current_user_can('backwpup')) { + return; + } + + $boxes_display = get_user_meta(get_current_user_id(), 'backwpup_message_boxes_not_display', true); + if (!$boxes_display) { + $boxes_display = []; + } + + if (!empty($boxes_display[$this->box_id])) { + return; + } + + if (isset($_GET['page']) && $_GET['page'] === 'backwpupabout') { + return; + } + + if ($this->campaign_to_date !== '0000-00-00') { + $this_day = date('Y-m-d'); + if ($this_day > $this->campaign_to_date) { + return; + } + } + + add_action('admin_notices', [$this, 'output_box_html']); + add_action('admin_init', [$this, 'save_not_display']); + } + + /** + * Output the message box. + */ + public function output_box_html() + { + $url = add_query_arg(['backwpup_msg_' . $this->box_id => 1], '//' . $_SERVER['SERVER_NAME'] . $_SERVER['REQUEST_URI']); ?>
box_html; ?> - +
box_html = $html; - } - - /** - * Save user meta for boxes that should not be displayed - */ - public function save_not_display() { - - if ( ! empty( $_GET[ 'backwpup_msg_' . $this->box_id ] ) ) { - $boxes_display = get_user_meta( get_current_user_id(), 'backwpup_message_boxes_not_display', true ); - if ( ! $boxes_display ) { - $boxes_display = array(); - } - $boxes_display[ $this->box_id ] = true; - update_user_meta( get_current_user_id(), 'backwpup_message_boxes_not_display', $boxes_display ); - remove_action( 'admin_notices', array( $this, 'output_box_html' ) ); - } - } - - /** - * Date to a campaign should be displayed - * - * @since 3.3.2 - * - * @param string $campaign_to_date - */ - public function set_campaign_to_date( $campaign_to_date = '0000-00-00' ) { - - $this->campaign_to_date = $campaign_to_date; - } + } + + /** + * Add box html for output with this box. + * + * @param $html + */ + public function set_box_html($html) + { + if (!$html || !is_string($html)) { + return; + } + + $this->box_html = $html; + } + + /** + * Save user meta for boxes that should not be displayed. + */ + public function save_not_display() + { + if (!empty($_GET['backwpup_msg_' . $this->box_id])) { + $boxes_display = get_user_meta(get_current_user_id(), 'backwpup_message_boxes_not_display', true); + if (!$boxes_display) { + $boxes_display = []; + } + $boxes_display[$this->box_id] = true; + update_user_meta(get_current_user_id(), 'backwpup_message_boxes_not_display', $boxes_display); + remove_action('admin_notices', [$this, 'output_box_html']); + } + } + + /** + * Date to a campaign should be displayed. + * + * @since 3.3.2 + * + * @param string $campaign_to_date + */ + public function set_campaign_to_date($campaign_to_date = '0000-00-00') + { + $this->campaign_to_date = $campaign_to_date; + } } diff --git a/inc/class-msazure-destination-configuration.php b/inc/class-msazure-destination-configuration.php index d3f9ca6c..657413c7 100644 --- a/inc/class-msazure-destination-configuration.php +++ b/inc/class-msazure-destination-configuration.php @@ -2,24 +2,35 @@ namespace Inpsyde\BackWPup; -class MsAzureDestinationConfiguration { +class MsAzureDestinationConfiguration +{ + public const MSAZURE_ACCNAME = 'msazureaccname'; + public const MSAZURE_KEY = 'msazurekey'; + public const MSAZURE_CONTAINER = 'msazurecontainer'; - const MSAZURE_ACCNAME = 'msazureaccname'; - const MSAZURE_KEY = 'msazurekey'; - const MSAZURE_CONTAINER = 'msazurecontainer'; - - /** @var string */ + /** + * @var string + */ private $msazureaccname; - /** @var string */ + /** + * @var string + */ private $msazurekey; - /** @var string */ + /** + * @var string + */ private $msazurecontainer; + /** + * @var bool + */ + private $new = false; + public function __construct($msazureaccname, $msazurekey, $msazurecontainer) { - $items = array($msazureaccname, $msazurekey, $msazurecontainer); + $items = [$msazureaccname, $msazurekey, $msazurecontainer]; $areConfigPartsValid = array_filter($items); if (count($areConfigPartsValid) !== count($items)) { throw new \UnexpectedValueException( @@ -32,27 +43,31 @@ public function __construct($msazureaccname, $msazurekey, $msazurecontainer) $this->msazurecontainer = $msazurecontainer; } - /** - * @return string - */ - public function msazureaccname() + public static function withNewContainer(string $accountName, string $key, string $container): self + { + $configuration = new self($accountName, $key, $container); + $configuration->new = true; + + return $configuration; + } + + public function msazureaccname(): string { return $this->msazureaccname; } - /** - * @return string - */ - public function msazurekey() + public function msazurekey(): string { return $this->msazurekey; } - /** - * @return string - */ - public function msazurecontainer() + public function msazurecontainer(): string { return $this->msazurecontainer; } + + public function isNew(): bool + { + return $this->new; + } } diff --git a/inc/class-mysqldump.php b/inc/class-mysqldump.php index efa9a62f..cbbc4d59 100644 --- a/inc/class-mysqldump.php +++ b/inc/class-mysqldump.php @@ -1,959 +1,974 @@ configureOptions($resolver); - $args = $resolver->resolve($args); - - $driver = new mysqli_driver(); - $mode = $driver->report_mode; - $driver->report_mode = MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT; - - $this->connect($args); - - $driver->report_mode = $mode; - - //set charset - if ( ! empty( $args[ 'dbcharset' ] ) ) { - $this->setCharset( $args[ 'dbcharset' ] ); - } - - //set compression - $this->compression = $args[ 'compression' ]; - - //open file if set - if ( $args[ 'dumpfile' ] ) { - if ( $args['compression'] === self::COMPRESS_GZ ) { - if ( ! function_exists( 'gzencode' ) ) { - throw new BackWPup_MySQLDump_Exception( __( 'Functions for gz compression not available', 'backwpup' ) ); - } - - $this->handle = fopen( 'compress.zlib://' . $args[ 'dumpfile' ], 'ab' ); - } else { - $this->handle = fopen( $args[ 'dumpfile' ], 'ab' ); - } - } else { - $this->handle = $args[ 'dumpfilehandle' ]; - } - - //check file handle - if ( ! $this->handle ) { - throw new BackWPup_MySQLDump_Exception( __( 'Cannot open SQL backup file', 'backwpup' ) ); - } - - //get table info - $res = $this->mysqli->query( "SHOW TABLE STATUS FROM `" . $this->dbname . "`" ); - $GLOBALS[ 'wpdb' ]->num_queries ++; - if ( $this->mysqli->error ) { - throw new BackWPup_MySQLDump_Exception( sprintf( __( 'Database error %1$s for query %2$s', 'backwpup' ), $this->mysqli->error, "SHOW TABLE STATUS FROM `" .$this->dbname . "`" ) ); - } else { - while ( $tablestatus = $res->fetch_assoc() ) { - $this->table_status[ $tablestatus[ 'Name' ] ] = $tablestatus; - } - $res->close(); - } - - //get table names and types from Database - $res = $this->mysqli->query( 'SHOW FULL TABLES FROM `' . $this->dbname . '`' ); - $GLOBALS[ 'wpdb' ]->num_queries ++; - if ( $this->mysqli->error ) { - throw new BackWPup_MySQLDump_Exception( sprintf( __( 'Database error %1$s for query %2$s', 'backwpup' ), $this->mysqli->error, 'SHOW FULL TABLES FROM `' . $this->dbname . '`' ) ); - } else { - while ( $table = $res->fetch_array( MYSQLI_NUM ) ) { - $this->table_types[ $table[ 0 ] ] = $table[ 1 ]; - $this->tables_to_dump[] = $table[ 0 ]; - if ( $table[ 1 ] === 'VIEW' ) { - $this->views_to_dump[] = $table[ 0 ]; - $this->table_status[ $table[ 0 ] ][ 'Rows' ] = 0; - } - } - $res->close(); - } - - } - - /** - * Configure options - * - * @param \Symfony\Component\OptionsResolver\OptionsResolver $resolver - */ - protected function configureOptions(OptionsResolver $resolver) - { - $resolver->setDefaults([ - 'dbhost' => DB_HOST, - 'dbport' => null, - 'dbsocket' => null, - 'dbname' => DB_NAME, - 'dbuser' => DB_USER, - 'dbpassword' => DB_PASSWORD, - 'dbcharset' => defined('DB_CHARSET') ? DB_CHARSET : 'utf8mb4', - 'dumpfilehandle' => fopen('php://output', 'wb'), - 'dumpfile' => null, - 'dbclientflags' => defined('MYSQL_CLIENT_FLAGS') ? MYSQL_CLIENT_FLAGS : 0, - 'compression' => function (Options $options) { - if ($options['dumpfile'] !== null - && substr(strtolower($options['dumpfile']), -3) === '.gz') { - return self::COMPRESS_GZ; - } - - return self::COMPRESS_NONE; - }, - ]); - - $port = $socket = null; - - $resolver->setNormalizer('dbhost', function (Options $options, $value) use (&$port, &$socket) { - if (strpos($value, ':') !== false) { - list($value, $part) = array_map('trim', explode(':', $value, 2)); - if (is_numeric($part)) { - $port = intval($part); - } elseif (!empty($part)) { - $socket = $part; - } - } - - return $value ?: 'localhost'; - }); - - $resolver->setDefault('dbport', function (Options $options) use (&$port) { - return $port; - }); - - $resolver->setDefault('dbsocket', function (Options $options) use (&$socket) { - return $socket; - }); - - $resolver->setAllowedValues('dumpfilehandle', function ($value) { - // Ensure handle is writable - $metadata = stream_get_meta_data($value); - if ($metadata['mode'][0] === 'r' && strpos($metadata['mode'], '+') === false) { - return false; - } - - return true; - }); - - $resolver->setAllowedValues('compression', [self::COMPRESS_NONE, self::COMPRESS_GZ]); - - $resolver->setAllowedTypes('dbhost', 'string'); - $resolver->setAllowedTypes('dbport', ['null', 'int']); - $resolver->setAllowedTypes('dbsocket', ['null', 'string']); - $resolver->setAllowedTypes('dbname', 'string'); - $resolver->setAllowedTypes('dbuser', 'string'); - $resolver->setAllowedTypes('dbpassword', 'string'); - $resolver->setAllowedTypes('dbcharset', ['null', 'string']); - $resolver->setAllowedTypes('dumpfilehandle', 'resource'); - $resolver->setAllowedTypes('dumpfile', ['null', 'string']); - $resolver->setAllowedTypes('dbclientflags', 'int'); - } - - /** - * Set the best available database charset - * - * @param string $charset The charset to try setting - * - * @return string The set charset - */ - public function setCharset($charset) - { - if ($charset === 'utf8' && $this->getConnection()->set_charset('utf8mb4') === true) { - return 'utf8mb4'; - } elseif ($this->getConnection()->set_charset($charset) === true) { - return $charset; - } elseif ($charset === 'utf8mb4' && $this->getConnection()->set_charset('utf8') === true) { - return 'utf8'; - } - - trigger_error( - sprintf( - __('Cannot set DB charset to %s', 'backwpup'), - $charset - ), - E_USER_WARNING - ); - - return false; - } - - /** - * Get the database connection - * - * @return \mysqli - */ - protected function getConnection() - { - if ($this->mysqli === null) { - $this->mysqli = mysqli_init(); - } - - return $this->mysqli; - } - - /** - * Whether the database is connected - * - * @return bool - */ - public function isConnected() - { - return $this->connected === true; - } - - /** - * Connect to the database - * - * @param array $args Connection parameters - */ - protected function connect(array $args) - { - if ($this->isConnected()) { - return; - } - - $mysqli = $this->getConnection(); - - if (!$mysqli->options(MYSQLI_OPT_CONNECT_TIMEOUT, 5)) { - trigger_error(__('Setting of MySQLi connection timeout failed', 'backwpup'), E_USER_WARNING); // phpcs:ignore - } - - //connect to Database - try { - $mysqli->real_connect( - $args['dbhost'], - $args['dbuser'], - $args['dbpassword'], - $args['dbname'], - $args['dbport'], - $args['dbsocket'], - $args['dbclientflags'] - ); - } catch (\mysqli_sql_exception $e) { - throw new BackWPup_MySQLDump_Exception( - sprintf( - __('Cannot connect to MySQL database (%1$d: %2$s)', 'backwpup'), - $e->getCode(), - $e->getMessage() - ) - ); - } - - //set db name - $this->dbname = $args['dbname']; - - // We are now connected - $this->connected = true; - } - - /** - * Get the name of the database - * - * @return string - */ - protected function getDbName() - { - return $this->dbname; - } - - /** - * Report a query error - * - * @param \mysqli_sql_exception $exception The thrown exception - * @param string $query The query that caused the error - */ - protected function logQueryError(\mysqli_sql_exception $exception, $query) - { - // phpcs:disable WordPress.PHP.DevelopmentFunctions.error_log_trigger_error, WordPress.XSS.EscapeOutput.OutputNotEscaped - trigger_error( - sprintf( - __('Database error: %1$s. Query: %2$s', 'backwpup'), - $exception->getMessage(), - $query - ), - E_USER_WARNING - ); - // phpcs:enable WordPress.PHP.DevelopmentFunctions.error_log_trigger_error. WordPress.XSS.EscapeOutput.OutputNotEscaped - } - - /** - * Send a query to the database - * - * @param string $query The query to execute - * @return mixed The result of the query - */ - protected function query($sql) - { - $result = $this->getConnection()->query($sql); - backwpup_wpdb()->num_queries++; - - return $result; - } - - /** - * Start the dump - */ - public function execute() { - - //increase time limit - @set_time_limit( 300 ); - //write dump head - $this->dump_head(); - //write tables - foreach( $this->tables_to_dump as $table ) { - $this->dump_table_head( $table ); - $this->dump_table( $table ); - $this->dump_table_footer( $table ); - } - //write footer - $this->dump_footer(); - - } - - /** - * Write Dump Header - * - * @param bool $wp_info Dump WordPress info in dump head - */ - public function dump_head( $wp_info = FALSE ) { - - // get sql timezone - $res = $this->mysqli->query( "SELECT @@time_zone" ); - $GLOBALS[ 'wpdb' ]->num_queries ++; - $mysqltimezone = $res->fetch_row(); - $mysqltimezone = $mysqltimezone[0]; - $res->close(); - - //For SQL always use \n as MySQL wants this on all platforms. - $dbdumpheader = "-- ---------------------------------------------------------\n"; - $dbdumpheader .= "-- Backup with BackWPup ver.: " . BackWPup::get_plugin_data( 'Version' ) . "\n"; - $dbdumpheader .= "-- http://backwpup.com/\n"; - if ( $wp_info ) { - $dbdumpheader .= "-- Blog Name: " . get_bloginfo( 'name' ) . "\n"; - $dbdumpheader .= "-- Blog URL: " . trailingslashit( get_bloginfo( 'url' ) ) . "\n"; - $dbdumpheader .= "-- Blog ABSPATH: " . trailingslashit( str_replace( '\\', '/', ABSPATH ) ) . "\n"; - $dbdumpheader .= "-- Blog Charset: " . get_bloginfo( 'charset' ) . "\n"; - $dbdumpheader .= "-- Table Prefix: " . $GLOBALS[ 'wpdb' ]->prefix . "\n"; - } - $dbdumpheader .= "-- Database Name: " . $this->dbname . "\n"; - $dbdumpheader .= "-- Backup on: " . date( 'Y-m-d H:i.s', current_time( 'timestamp' ) ) . "\n"; - $dbdumpheader .= "-- ---------------------------------------------------------\n\n"; - //for better import with mysql client - $dbdumpheader .= "/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;\n"; - $dbdumpheader .= "/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;\n"; - $dbdumpheader .= "/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;\n"; - $dbdumpheader .= "/*!40101 SET NAMES " . $this->mysqli->character_set_name() . " */;\n"; - $dbdumpheader .= "/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;\n"; - $dbdumpheader .= "/*!40103 SET TIME_ZONE='" . $mysqltimezone . "' */;\n"; - $dbdumpheader .= "/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;\n"; - $dbdumpheader .= "/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;\n"; - $dbdumpheader .= "/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;\n"; - $dbdumpheader .= "/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;\n\n"; - $this->write( $dbdumpheader ); - } - - /** - * Write Dump Footer with dump of functions and procedures - */ - public function dump_footer() { - - //dump Views - foreach( $this->views_to_dump as $view ) { - $this->dump_view_table_head( $view ); - } - - //dump procedures and functions - $this->write("\n--\n-- Backup routines for database '" . $this->dbname . "'\n--\n"); - - // Temporarily set mysqli to throw exceptions - $driver = new \mysqli_driver(); - $mode = $driver->report_mode; - $driver->report_mode = MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT; - - // Dump Functions - $this->dump_functions(); - - // Dump Procedures - $this->dump_procedures(); - - // Dump Triggers - $this->dump_triggers(); - - // Restore report mode for other methods that do not support exceptions yet - // This should be changed ASAP to support SQL exceptions globally - $driver->report_mode = $mode; - - //for better import with mysql client - $dbdumpfooter = "\n/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;\n"; - $dbdumpfooter .= "/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;\n"; - $dbdumpfooter .= "/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;\n"; - $dbdumpfooter .= "/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;\n"; - $dbdumpfooter .= "/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;\n"; - $dbdumpfooter .= "/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;\n"; - $dbdumpfooter .= "/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;\n"; - $dbdumpfooter .= "/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;\n"; - $dbdumpfooter .= "\n-- Backup completed on " . date( 'Y-m-d H:i:s', current_time( 'timestamp' ) ). "\n"; - $this->write( $dbdumpfooter ); - } - - - /** - * Dump table structure - * - * @param string $table name of Table to dump - * @throws BackWPup_MySQLDump_Exception - * @return int Size of table - */ - public function dump_table_head( $table ) { - - //dump View - if ( $this->table_types[ $table ] === 'VIEW' ) { - //Dump the view table structure - $fields = array(); - $res = $this->mysqli->query( "SELECT * FROM `" . $table . "` LIMIT 1" ); - $GLOBALS[ 'wpdb' ]->num_queries ++; - if ( $this->mysqli->error ) { - trigger_error( sprintf( __( 'Database error %1$s for query %2$s', 'backwpup' ), $this->mysqli->error, "SELECT * FROM `" . $table . "` LIMIT 1" ), E_USER_WARNING ); - } else { - $fields = $res->fetch_fields(); - $res->close(); - } - if ( $res ) { - $tablecreate = "\n--\n-- Temporary table structure for view `" . $table . "`\n--\n\n"; - $tablecreate .= "DROP TABLE IF EXISTS `" . $table . "`;\n"; - $tablecreate .= "/*!50001 DROP VIEW IF EXISTS `" . $table . "`*/;\n"; - $tablecreate .= "/*!40101 SET @saved_cs_client = @@character_set_client */;\n"; - $tablecreate .= "/*!40101 SET character_set_client = '" . $this->mysqli->character_set_name() . "' */;\n"; - $tablecreate .= "CREATE TABLE `" . $table . "` (\n"; - foreach( $fields as $field ) { - $tablecreate .= " `". $field->orgname . "` tinyint NOT NULL,\n"; - } - $tablecreate = substr( $tablecreate, 0, -2 ) ."\n"; - $tablecreate .= ");\n"; - $tablecreate .= "/*!40101 SET character_set_client = @saved_cs_client */;\n"; - $this->write( $tablecreate ); - } - return 0; - } - - //dump normal Table - $tablecreate = "\n--\n-- Table structure for `" . $table . "`\n--\n\n"; - $tablecreate .= "DROP TABLE IF EXISTS `" . $table . "`;\n"; - $tablecreate .= "/*!40101 SET @saved_cs_client = @@character_set_client */;\n"; - $tablecreate .= "/*!40101 SET character_set_client = '" . $this->mysqli->character_set_name() . "' */;\n"; - //Dump the table structure - $res = $this->mysqli->query( "SHOW CREATE TABLE `" . $table . "`" ); - $GLOBALS[ 'wpdb' ]->num_queries ++; - if ( $this->mysqli->error ) { - trigger_error( sprintf( __( 'Database error %1$s for query %2$s', 'backwpup' ), $this->mysqli->error, "SHOW CREATE TABLE `" . $table . "`" ), E_USER_WARNING ); - } else { - $createtable = $res->fetch_assoc(); - $res->close(); - $tablecreate .= $createtable[ 'Create Table' ] . ";\n"; - $tablecreate .= "/*!40101 SET character_set_client = @saved_cs_client */;\n"; - $this->write( $tablecreate ); - - if ( $this->table_status[ $table ][ 'Engine' ] !== 'MyISAM' ) { - $this->table_status[ $table ][ 'Rows' ] = '~' . $this->table_status[ $table ][ 'Rows' ]; - } - - if ( $this->table_status[ $table ][ 'Rows' ] !== 0 ) { - //Dump Table data - $this->write( "\n--\n-- Backup data for table `" . $table . "`\n--\n\nLOCK TABLES `" . $table . "` WRITE;\n/*!40000 ALTER TABLE `" . $table . "` DISABLE KEYS */;\n" ); - } - - return $this->table_status[ $table ][ 'Rows' ]; - } - - return 0; - } - - - /** - * Dump view structure - * - * @param string $view name of Table to dump - * @throws BackWPup_MySQLDump_Exception - * @return int Size of table - */ - public function dump_view_table_head( $view ) { - - //Dump the view structure - $res = $this->mysqli->query( "SHOW CREATE VIEW `" . $view . "`" ); - $GLOBALS[ 'wpdb' ]->num_queries ++; - if ( $this->mysqli->error ) { - trigger_error( sprintf( __( 'Database error %1$s for query %2$s', 'backwpup' ), $this->mysqli->error, "SHOW CREATE VIEW `" . $view . "`" ), E_USER_WARNING ); - } else { - $createview = $res->fetch_assoc(); - $res->close(); - $tablecreate = "\n--\n-- View structure for `" . $view . "`\n--\n\n"; - $tablecreate .= "DROP TABLE IF EXISTS `" . $view . "`;\n"; - $tablecreate .= "DROP VIEW IF EXISTS `" . $view . "`;\n"; - $tablecreate .= "/*!40101 SET @saved_cs_client = @@character_set_client */;\n"; - $tablecreate .= "/*!40101 SET character_set_client = '" . $this->mysqli->character_set_name() . "' */;\n"; - $tablecreate .= $createview[ 'Create View' ] . ";\n"; - $tablecreate .= "/*!40101 SET character_set_client = @saved_cs_client */;\n"; - $this->write( $tablecreate ); - } - - } - - /** - * Dump table footer - * - * @param string $table name of Table to dump - * @return int Size of table - */ - public function dump_table_footer( $table ) { - - if ( $this->table_status[ $table ][ 'Rows' ] !== 0 ) { - $this->write( "/*!40000 ALTER TABLE `" . $table . "` ENABLE KEYS */;\nUNLOCK TABLES;\n" ); - } - } - - - /** - * Dump table Data - * - * @param string $table name of Table to dump - * @param int $start Start of lengh paramter - * @param int $length how many - * @return int done records in this backup - * @throws BackWPup_MySQLDump_Exception - */ - public function dump_table( $table, $start = 0, $length = 0 ) { - - if ( ! is_numeric( $start ) || $start < 0 ) { - throw new BackWPup_MySQLDump_Exception( sprintf( __( 'Start for table backup is not correctly set: %1$s', 'backwpup' ), $start ) ); - } - - if ( ! is_numeric( $length ) || $length < 0 ) { - throw new BackWPup_MySQLDump_Exception( sprintf( __( 'Length for table backup is not correctly set: %1$s', 'backwpup' ), $length ) ); - } - - $done_records = 0; - - if ( $this->get_table_type_for( $table ) === 'VIEW' ) { - return $done_records; - } - - //get data from table - try { - $res = $this->do_table_query( $table, $start, $length ); - } catch ( BackWPup_MySQLDump_Exception $e ) { - trigger_error( sprintf( __( 'Database error %1$s for query %2$s', 'backwpup' ), $e->getMessage(), "SELECT * FROM `" . $table . "`" ), E_USER_WARNING ); - return 0; - } - - $fieldsarray = array(); - $fieldinfo = array(); - $fields = $res->fetch_fields(); - $i = 0; - foreach ( $fields as $field ) { - $fieldsarray[ $i ] = $field->orgname; - $fieldinfo[ $fieldsarray[ $i ] ] = $field; - $i ++; - } - $dump = ''; - while ( $data = $res->fetch_assoc() ) { - $values = array(); - foreach ( $data as $key => $value ) { - if ( is_null( $value ) || ! isset( $value ) ) { // Make Value NULL to string NULL - $value = "NULL"; - } elseif ( in_array( (int) $fieldinfo[ $key ]->type, array( MYSQLI_TYPE_DECIMAL, MYSQLI_TYPE_NEWDECIMAL, MYSQLI_TYPE_BIT, MYSQLI_TYPE_TINY, MYSQLI_TYPE_SHORT, MYSQLI_TYPE_LONG, MYSQLI_TYPE_FLOAT, MYSQLI_TYPE_DOUBLE, MYSQLI_TYPE_LONGLONG, MYSQLI_TYPE_INT24, MYSQLI_TYPE_YEAR ), true ) ) {//is value numeric no esc - $value = empty( $value ) ? 0 : $value; - } elseif ( in_array( (int) $fieldinfo[ $key ]->type, array( MYSQLI_TYPE_TIMESTAMP, MYSQLI_TYPE_DATE, MYSQLI_TYPE_TIME, MYSQLI_TYPE_DATETIME, MYSQLI_TYPE_NEWDATE ), true ) ) {//date/time types - $value = "'$value'"; - } elseif ( $fieldinfo[ $key ]->flags & MYSQLI_BINARY_FLAG ) {//is value binary - $hex = unpack( 'H*', $value ); - $value = empty( $value ) ? "''" : "0x$hex[1]"; - } else { - $value = "'" . $this->escapeString( $value ) . "'"; - } - $values[ ] = $value; - } - //new query in dump on more than 50000 chars. - if ( empty( $dump ) ) - $dump = "INSERT INTO `" . $table . "` (`" . implode( "`, `", $fieldsarray ) . "`) VALUES \n"; - if ( strlen( $dump ) <= 50000 ) { - $dump .= "(" . implode( ", ", $values ) . "),\n"; - } else { - $dump .= "(" . implode( ", ", $values ) . ");\n"; - $this->write( $dump ); - $dump = ''; - } - $done_records ++; - } - if ( ! empty( $dump ) ) { - // Remove trailing , and newline. - $dump = substr( $dump, 0, -2 ) . ";\n" ; - $this->write( $dump ); - } - $res->close(); - - return $done_records; - } - - /** - * Dump functions - * - * Dumps all functions found in the database. - */ - protected function dump_functions() - { - try { - $statusResult = $this->query("SHOW FUNCTION STATUS"); - } catch (\mysqli_sql_exception $e) { - $this->logQueryError($e, 'SHOW FUNCTION STATUS'); - return; - } - - while ($function = $statusResult->fetch_assoc()) { - if ($this->getDbName() !== $function['Db']) { - continue; - } - - $query = sprintf( - 'SHOW CREATE FUNCTION `%1$s`.`%2$s`', - $function['Db'], - $function['Name'] - ); - - try { - $createResult = $this->query($query); - $createFunction = $createResult->fetch_assoc(); - $createResult->close(); - - if ($createFunction === null) { - continue; - } - - $sql = sprintf( - "\n--\n" . - "-- Function structure for %1\$s\n" . - "--\n\n" . - "/*!50003 DROP FUNCTION IF EXISTS `%1\$s` */;\n" . - "/*!50003 SET @saved_cs_client = @@character_set_client */ ;\n" . - "/*!50003 SET @saved_cs_results = @@character_set_results */ ;\n" . - "/*!50003 SET @saved_col_connection = @@collation_connection */ ;\n" . - "/*!50003 SET character_set_client = %2\$s */ ;\n" . - "/*!50003 SET character_set_results = %2\$s */ ;\n" . - "/*!50003 SET collation_connection = %3\$s */ ;\n" . - "/*!50003 SET @saved_sql_mode = @@sql_mode */ ;\n" . - "/*!50003 SET sql_mode = '%4\$s' */ ;\n" . - "DELIMITER ;;\n" . - "%5\$s ;;\n" . - "DELIMITER ;\n" . - "/*!50003 SET sql_mode = @saved_sql_mode */ ;\n" . - "/*!50003 SET character_set_client = @saved_cs_client */ ;\n" . - "/*!50003 SET character_set_results = @saved_cs_results */ ;\n" . - "/*!50003 SET collation_connection = @saved_col_connection */ ;\n", - $createFunction['Function'], - $createFunction['character_set_client'], - $createFunction['collation_connection'], - $createFunction['sql_mode'], - $createFunction['Create Function'] - ); - $this->write($sql); - } catch (\mysqli_sql_exception $e) { - $this->logQueryError($e, $query); - } - } - - $statusResult->close(); - } - - /** - * Dump procedures - * - * Dumps all stored procedures found in the database. - */ - protected function dump_procedures() - { - try { - $statusResult = $this->query('SHOW PROCEDURE STATUS'); - } catch (mysqli_sql_exception $e) { - $this->logQueryError($e, 'SHOW PROCEDURE STATUS'); - return; - } - - while ($procedure = $statusResult->fetch_assoc()) { - if ($this->getDbName() !== $procedure['Db']) { - continue; - } - - $query = sprintf( - 'SHOW CREATE PROCEDURE `%1$s`.`%2$s`', - $procedure['Db'], - $procedure['Name'] - ); - - try { - $createResult = $this->query($query); - $createProcedure = $createResult->fetch_assoc(); - $createResult->close(); - - if ($createProcedure === null) { - continue; - } - - $sql = sprintf( - "\n--\n" . - "-- Procedure structure for %1\$s\n" . - "--\n\n" . - "/*!50003 DROP PROCEDURE IF EXISTS `%1\$s` */;\n" . - "/*!50003 SET @saved_cs_client = @@character_set_client */ ;\n" . - "/*!50003 SET @saved_cs_results = @@character_set_results */ ;\n" . - "/*!50003 SET @saved_col_connection = @@collation_connection */ ;\n" . - "/*!50003 SET character_set_client = %2\$s */ ;\n" . - "/*!50003 SET character_set_results = %2\$s */ ;\n" . - "/*!50003 SET collation_connection = %3\$s */ ;\n" . - "/*!50003 SET @saved_sql_mode = @@sql_mode */ ;\n" . - "/*!50003 SET sql_mode = '%4\$s' */ ;\n" . - "DELIMITER ;;\n" . - "%5\$s ;;\n" . - "DELIMITER ;\n" . - "/*!50003 SET sql_mode = @saved_sql_mode */ ;\n" . - "/*!50003 SET character_set_client = @saved_cs_client */ ;\n" . - "/*!50003 SET character_set_results = @saved_cs_results */ ;\n" . - "/*!50003 SET collation_connection = @saved_col_connection */ ;\n", - $createProcedure['Procedure'], - $createProcedure['character_set_client'], - $createProcedure['collation_connection'], - $createProcedure['sql_mode'], - $createProcedure['Create Procedure'] - ); - $this->write($sql); - } catch (mysqli_sql_exception $e) { - $this->logQueryError($e, $query); - } - } - - $statusResult->close(); - } - - /** - * Dump triggers - * - * Dumps all triggers found in the database. - */ - protected function dump_triggers() - { - $query = sprintf('SHOW TRIGGERS FROM `%1$s`', $this->getDbName()); - - try { - $statusResult = $this->query($query); - } catch (\mysqli_sql_exception $e) { - $this->logQueryError($e, $query); - return; - } - - while ($trigger = $statusResult->fetch_assoc()) { - $query = sprintf( - 'SHOW CREATE TRIGGER `%1$s`.`%2$s`', - $this->getDbName(), - $trigger['Trigger'] - ); - - try { - $createResult = $this->query($query); - $createTrigger = $createResult->fetch_assoc(); - $createResult->close(); - - if ($createTrigger === null) { - continue; - } - - $sql = sprintf( - "\n--\n" . - "-- Trigger structure for %1\$s\n" . - "--\n\n" . - "/*!50032 DROP TRIGGER IF EXISTS `%1\$s` */;\n" . - "/*!50003 SET @saved_cs_client = @@character_set_client */ ;\n" . - "/*!50003 SET @saved_cs_results = @@character_set_results */ ;\n" . - "/*!50003 SET @saved_col_connection = @@collation_connection */ ;\n" . - "/*!50003 SET character_set_client = %2\$s */ ;\n" . - "/*!50003 SET character_set_results = %2\$s */ ;\n" . - "/*!50003 SET collation_connection = %3\$s */ ;\n" . - "/*!50003 SET @saved_sql_mode = @@sql_mode */ ;\n" . - "/*!50003 SET sql_mode = '%4\$s' */ ;\n" . - "DELIMITER ;;\n" . - "/*!50003 %5\$s */;;\n" . - "DELIMITER ;\n" . - "/*!50003 SET sql_mode = @saved_sql_mode */ ;\n" . - "/*!50003 SET character_set_client = @saved_cs_client */ ;\n" . - "/*!50003 SET character_set_results = @saved_cs_results */ ;\n" . - "/*!50003 SET collation_connection = @saved_col_connection */ ;\n", - $createTrigger['Trigger'], - $createTrigger['character_set_client'], - $createTrigger['collation_connection'], - $createTrigger['sql_mode'], - $createTrigger['SQL Original Statement'] - ); - $this->write($sql); - } catch (\mysqli_sql_exception $e) { - $this->logQueryError($e, $query); - } - } - - $statusResult->close(); - } - - /** - * Writes data to handle and compress - * - * @param $data string to write - * @throws BackWPup_MySQLDump_Exception - */ - protected function write( $data ) { - - $written = fwrite( $this->handle, $data ); - - if ( ! $written ) - throw new BackWPup_MySQLDump_Exception( __( 'Error while writing file!', 'backwpup' ) ); - } - - /** - * Closes all confections on shutdown. - */ - public function __destruct() { - - //close MySQL connection - if ($this->mysqli !== null) { - $this->mysqli->close(); - } - //close file handle - if ( is_resource( $this->handle ) ) - fclose( $this->handle ); - } - - /** - * Get table type for given table - * - * @param string $table The table to look up the type for. - * - * @return string The table type if found - */ - protected function get_table_type_for( $table ) { - - if ( isset( $this->table_types[ $table ] ) ) { - return $this->table_types[ $table ]; - } - - return null; - } - - /** - * Perform query to fetch table rows - * - * @param string $table The table on which to perform the query - * @param int $start The record to start at - @param int $length How many records to fetch - * - * @return \mysqli_result The resulting query - * @throws \BackWPup_MySQLDump_Exception In case of mysql error - */ - protected function do_table_query( $table, $start, $length ) { - - if ( $length == 0 && $start == 0 ) { - $res = $this->mysqli->query( "SELECT * FROM `" . $table . "` ", MYSQLI_USE_RESULT ); - } else { - $res = $this->mysqli->query( "SELECT * FROM `" . $table . "` LIMIT " . $start . ", " . $length, MYSQLI_USE_RESULT ); - } - $GLOBALS[ 'wpdb' ]->num_queries ++; - if ( $this->mysqli->error ) { - throw new BackWPup_MySQLDump_Exception( $this->mysqli->error ); - } - - return $res; - } - - /** - * Escapes a string for MySQL - * - * @param string $value The value to escape - * - * @return string The escaped string - */ - protected function escapeString( $value ) { - return $this->mysqli->real_escape_string( $value ); - } + // Compression flags + public const COMPRESS_NONE = ''; + public const COMPRESS_GZ = 'gz'; + + /** + * Table names of Tables in Database. + */ + public $tables_to_dump = []; + + /** + * View names of Views in Database. + */ + public $views_to_dump = []; + + /** + * Holder for mysqli resource. + */ + private $mysqli; + + /** + * Whether we are connected to the database. + * + * @var bool + */ + private $connected = false; + + /** + * Holder for dump file handle. + */ + private $handle; + + /** + * Table names of Tables in Database. + */ + private $table_types = []; + + /** + * Table information of Tables in Database. + */ + private $table_status = []; + + /** + * Database name. + */ + private $dbname = ''; + + /** + * Compression to use + * empty for none + * gz for Gzip. + */ + private $compression = ''; + + /** + * Check params and makes confections + * gets the table information too. + * + * @param $args array with arguments + * + * @throws BackWPup_MySQLDump_Exception + * + * @global $wpdb wpdb + */ + public function __construct($args = []) + { + if (!class_exists(\mysqli::class)) { + throw new BackWPup_MySQLDump_Exception(__('No MySQLi extension found. Please install it.', 'backwpup')); + } + + $resolver = new OptionsResolver(); + $this->configureOptions($resolver); + $args = $resolver->resolve($args); + + $driver = new mysqli_driver(); + $mode = $driver->report_mode; + $driver->report_mode = MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT; + + $this->connect($args); + + $driver->report_mode = $mode; + + //set charset + if (!empty($args['dbcharset'])) { + $this->setCharset($args['dbcharset']); + } + + //set compression + $this->compression = $args['compression']; + + //open file if set + if ($args['dumpfile']) { + if ($args['compression'] === self::COMPRESS_GZ) { + if (!function_exists('gzencode')) { + throw new BackWPup_MySQLDump_Exception(__('Functions for gz compression not available', 'backwpup')); + } + + $this->handle = fopen('compress.zlib://' . $args['dumpfile'], 'ab'); + } else { + $this->handle = fopen($args['dumpfile'], 'ab'); + } + } else { + $this->handle = $args['dumpfilehandle']; + } + + //check file handle + if (!$this->handle) { + throw new BackWPup_MySQLDump_Exception(__('Cannot open SQL backup file', 'backwpup')); + } + + //get table info + $res = $this->mysqli->query('SHOW TABLE STATUS FROM `' . $this->dbname . '`'); + ++$GLOBALS[\wpdb::class]->num_queries; + if ($this->mysqli->error) { + throw new BackWPup_MySQLDump_Exception(sprintf(__('Database error %1$s for query %2$s', 'backwpup'), $this->mysqli->error, 'SHOW TABLE STATUS FROM `' . $this->dbname . '`')); + } + + while ($tablestatus = $res->fetch_assoc()) { + $this->table_status[$tablestatus['Name']] = $tablestatus; + } + $res->close(); + + //get table names and types from Database + $res = $this->mysqli->query('SHOW FULL TABLES FROM `' . $this->dbname . '`'); + ++$GLOBALS[\wpdb::class]->num_queries; + if ($this->mysqli->error) { + throw new BackWPup_MySQLDump_Exception(sprintf(__('Database error %1$s for query %2$s', 'backwpup'), $this->mysqli->error, 'SHOW FULL TABLES FROM `' . $this->dbname . '`')); + } + + while ($table = $res->fetch_array(MYSQLI_NUM)) { + $this->table_types[$table[0]] = $table[1]; + $this->tables_to_dump[] = $table[0]; + if ($table[1] === 'VIEW') { + $this->views_to_dump[] = $table[0]; + $this->table_status[$table[0]]['Rows'] = 0; + } + } + $res->close(); + } + + /** + * Configure options. + */ + protected function configureOptions(OptionsResolver $resolver) + { + $resolver->setDefaults([ + 'dbhost' => DB_HOST, + 'dbport' => null, + 'dbsocket' => null, + 'dbname' => DB_NAME, + 'dbuser' => DB_USER, + 'dbpassword' => DB_PASSWORD, + 'dbcharset' => defined('DB_CHARSET') ? DB_CHARSET : 'utf8mb4', + 'dumpfilehandle' => fopen('php://output', 'wb'), + 'dumpfile' => null, + 'dbclientflags' => defined('MYSQL_CLIENT_FLAGS') ? MYSQL_CLIENT_FLAGS : 0, + 'compression' => function (Options $options) { + if ($options['dumpfile'] !== null + && substr(strtolower($options['dumpfile']), -3) === '.gz') { + return self::COMPRESS_GZ; + } + + return self::COMPRESS_NONE; + }, + ]); + + $port = $socket = null; + + $resolver->setNormalizer('dbhost', function (Options $options, $value) use (&$port, &$socket) { + if (strpos($value, ':') !== false) { + [$value, $part] = array_map('trim', explode(':', $value, 2)); + if (is_numeric($part)) { + $port = intval($part); + } elseif (!empty($part)) { + $socket = $part; + } + } + + return $value ?: 'localhost'; + }); + + $resolver->setDefault('dbport', function (Options $options) use (&$port) { + return $port; + }); + + $resolver->setDefault('dbsocket', function (Options $options) use (&$socket) { + return $socket; + }); + + $resolver->setAllowedValues('dumpfilehandle', function ($value) { + // Ensure handle is writable + $metadata = stream_get_meta_data($value); + + return !($metadata['mode'][0] === 'r' && strpos($metadata['mode'], '+') === false); + }); + + $resolver->setAllowedValues('compression', [self::COMPRESS_NONE, self::COMPRESS_GZ]); + + $resolver->setAllowedTypes('dbhost', 'string'); + $resolver->setAllowedTypes('dbport', ['null', 'int']); + $resolver->setAllowedTypes('dbsocket', ['null', 'string']); + $resolver->setAllowedTypes('dbname', 'string'); + $resolver->setAllowedTypes('dbuser', 'string'); + $resolver->setAllowedTypes('dbpassword', 'string'); + $resolver->setAllowedTypes('dbcharset', ['null', 'string']); + $resolver->setAllowedTypes('dumpfilehandle', 'resource'); + $resolver->setAllowedTypes('dumpfile', ['null', 'string']); + $resolver->setAllowedTypes('dbclientflags', 'int'); + } + + /** + * Set the best available database charset. + * + * @param string $charset The charset to try setting + * + * @return string The set charset + */ + public function setCharset($charset) + { + if ($charset === 'utf8' && $this->getConnection()->set_charset('utf8mb4') === true) { + return 'utf8mb4'; + } + if ($this->getConnection()->set_charset($charset) === true) { + return $charset; + } + if ($charset === 'utf8mb4' && $this->getConnection()->set_charset('utf8') === true) { + return 'utf8'; + } + + trigger_error( + sprintf( + __('Cannot set DB charset to %s', 'backwpup'), + $charset + ), + E_USER_WARNING + ); + + return false; + } + + /** + * Get the database connection. + * + * @return \mysqli + */ + protected function getConnection() + { + if ($this->mysqli === null) { + $this->mysqli = mysqli_init(); + } + + return $this->mysqli; + } + + /** + * Whether the database is connected. + * + * @return bool + */ + public function isConnected() + { + return $this->connected === true; + } + + /** + * Connect to the database. + * + * @param array $args Connection parameters + */ + protected function connect(array $args) + { + if ($this->isConnected()) { + return; + } + + $mysqli = $this->getConnection(); + + if (!$mysqli->options(MYSQLI_OPT_CONNECT_TIMEOUT, 5)) { + trigger_error(__('Setting of MySQLi connection timeout failed', 'backwpup'), E_USER_WARNING); // phpcs:ignore + } + + //connect to Database + try { + $mysqli->real_connect( + $args['dbhost'], + $args['dbuser'], + $args['dbpassword'], + $args['dbname'], + $args['dbport'], + $args['dbsocket'], + $args['dbclientflags'] + ); + } catch (\mysqli_sql_exception $e) { + throw new BackWPup_MySQLDump_Exception( + sprintf( + __('Cannot connect to MySQL database (%1$d: %2$s)', 'backwpup'), + $e->getCode(), + $e->getMessage() + ) + ); + } + + //set db name + $this->dbname = $args['dbname']; + + // We are now connected + $this->connected = true; + } + + /** + * Get the name of the database. + * + * @return string + */ + protected function getDbName() + { + return $this->dbname; + } + + /** + * Report a query error. + * + * @param \mysqli_sql_exception $exception The thrown exception + * @param string $query The query that caused the error + */ + protected function logQueryError(mysqli_sql_exception $exception, $query) + { + // phpcs:disable WordPress.PHP.DevelopmentFunctions.error_log_trigger_error, WordPress.XSS.EscapeOutput.OutputNotEscaped + trigger_error( + sprintf( + __('Database error: %1$s. Query: %2$s', 'backwpup'), + $exception->getMessage(), + $query + ), + E_USER_WARNING + ); + // phpcs:enable WordPress.PHP.DevelopmentFunctions.error_log_trigger_error. WordPress.XSS.EscapeOutput.OutputNotEscaped + } + + /** + * Send a query to the database. + * + * @param string $query The query to execute + * + * @return mixed The result of the query + */ + protected function query($sql) + { + $result = $this->getConnection()->query($sql); + backwpup_wpdb()->num_queries++; + + return $result; + } + + /** + * Start the dump. + */ + public function execute() + { + //increase time limit + @set_time_limit(300); + //write dump head + $this->dump_head(); + //write tables + foreach ($this->tables_to_dump as $table) { + $this->dump_table_head($table); + $this->dump_table($table); + $this->dump_table_footer($table); + } + //write footer + $this->dump_footer(); + } + + /** + * Write Dump Header. + * + * @param bool $wp_info Dump WordPress info in dump head + */ + public function dump_head($wp_info = false) + { + // get sql timezone + $res = $this->mysqli->query('SELECT @@time_zone'); + ++$GLOBALS[\wpdb::class]->num_queries; + $mysqltimezone = $res->fetch_row(); + $mysqltimezone = $mysqltimezone[0]; + $res->close(); + + //For SQL always use \n as MySQL wants this on all platforms. + $dbdumpheader = "-- ---------------------------------------------------------\n"; + $dbdumpheader .= '-- Backup with BackWPup ver.: ' . BackWPup::get_plugin_data('Version') . "\n"; + $dbdumpheader .= "-- http://backwpup.com/\n"; + if ($wp_info) { + $dbdumpheader .= '-- Blog Name: ' . get_bloginfo('name') . "\n"; + $dbdumpheader .= '-- Blog URL: ' . trailingslashit(get_bloginfo('url')) . "\n"; + $dbdumpheader .= '-- Blog ABSPATH: ' . trailingslashit(str_replace('\\', '/', ABSPATH)) . "\n"; + $dbdumpheader .= '-- Blog Charset: ' . get_bloginfo('charset') . "\n"; + $dbdumpheader .= '-- Table Prefix: ' . $GLOBALS[\wpdb::class]->prefix . "\n"; + } + $dbdumpheader .= '-- Database Name: ' . $this->dbname . "\n"; + $dbdumpheader .= '-- Backup on: ' . date('Y-m-d H:i.s', current_time('timestamp')) . "\n"; + $dbdumpheader .= "-- ---------------------------------------------------------\n\n"; + //for better import with mysql client + $dbdumpheader .= "/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;\n"; + $dbdumpheader .= "/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;\n"; + $dbdumpheader .= "/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;\n"; + $dbdumpheader .= '/*!40101 SET NAMES ' . $this->mysqli->character_set_name() . " */;\n"; + $dbdumpheader .= "/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;\n"; + $dbdumpheader .= "/*!40103 SET TIME_ZONE='" . $mysqltimezone . "' */;\n"; + $dbdumpheader .= "/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;\n"; + $dbdumpheader .= "/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;\n"; + $dbdumpheader .= "/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;\n"; + $dbdumpheader .= "/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;\n\n"; + $this->write($dbdumpheader); + } + + /** + * Write Dump Footer with dump of functions and procedures. + */ + public function dump_footer() + { + //dump Views + foreach ($this->views_to_dump as $view) { + $this->dump_view_table_head($view); + } + + //dump procedures and functions + $this->write("\n--\n-- Backup routines for database '" . $this->dbname . "'\n--\n"); + + // Temporarily set mysqli to throw exceptions + $driver = new \mysqli_driver(); + $mode = $driver->report_mode; + $driver->report_mode = MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT; + + // Dump Functions + $this->dump_functions(); + + // Dump Procedures + $this->dump_procedures(); + + // Dump Triggers + $this->dump_triggers(); + + // Restore report mode for other methods that do not support exceptions yet + // This should be changed ASAP to support SQL exceptions globally + $driver->report_mode = $mode; + + //for better import with mysql client + $dbdumpfooter = "\n/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;\n"; + $dbdumpfooter .= "/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;\n"; + $dbdumpfooter .= "/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;\n"; + $dbdumpfooter .= "/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;\n"; + $dbdumpfooter .= "/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;\n"; + $dbdumpfooter .= "/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;\n"; + $dbdumpfooter .= "/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;\n"; + $dbdumpfooter .= "/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;\n"; + $dbdumpfooter .= "\n-- Backup completed on " . date('Y-m-d H:i:s', current_time('timestamp')) . "\n"; + $this->write($dbdumpfooter); + } + + /** + * Dump table structure. + * + * @param string $table name of Table to dump + * + * @throws BackWPup_MySQLDump_Exception + * + * @return int Size of table + */ + public function dump_table_head($table) + { + //dump View + if ($this->table_types[$table] === 'VIEW') { + //Dump the view table structure + $fields = []; + $res = $this->mysqli->query('SELECT * FROM `' . $table . '` LIMIT 1'); + ++$GLOBALS[\wpdb::class]->num_queries; + if ($this->mysqli->error) { + trigger_error(sprintf(__('Database error %1$s for query %2$s', 'backwpup'), $this->mysqli->error, 'SELECT * FROM `' . $table . '` LIMIT 1'), E_USER_WARNING); + } else { + $fields = $res->fetch_fields(); + $res->close(); + } + if ($res) { + $tablecreate = "\n--\n-- Temporary table structure for view `" . $table . "`\n--\n\n"; + $tablecreate .= 'DROP TABLE IF EXISTS `' . $table . "`;\n"; + $tablecreate .= '/*!50001 DROP VIEW IF EXISTS `' . $table . "`*/;\n"; + $tablecreate .= "/*!40101 SET @saved_cs_client = @@character_set_client */;\n"; + $tablecreate .= "/*!40101 SET character_set_client = '" . $this->mysqli->character_set_name() . "' */;\n"; + $tablecreate .= 'CREATE TABLE `' . $table . "` (\n"; + + foreach ($fields as $field) { + $tablecreate .= ' `' . $field->orgname . "` tinyint NOT NULL,\n"; + } + $tablecreate = substr($tablecreate, 0, -2) . "\n"; + $tablecreate .= ");\n"; + $tablecreate .= "/*!40101 SET character_set_client = @saved_cs_client */;\n"; + $this->write($tablecreate); + } + + return 0; + } + + //dump normal Table + $tablecreate = "\n--\n-- Table structure for `" . $table . "`\n--\n\n"; + $tablecreate .= 'DROP TABLE IF EXISTS `' . $table . "`;\n"; + $tablecreate .= "/*!40101 SET @saved_cs_client = @@character_set_client */;\n"; + $tablecreate .= "/*!40101 SET character_set_client = '" . $this->mysqli->character_set_name() . "' */;\n"; + //Dump the table structure + $res = $this->mysqli->query('SHOW CREATE TABLE `' . $table . '`'); + ++$GLOBALS[\wpdb::class]->num_queries; + if ($this->mysqli->error) { + trigger_error(sprintf(__('Database error %1$s for query %2$s', 'backwpup'), $this->mysqli->error, 'SHOW CREATE TABLE `' . $table . '`'), E_USER_WARNING); + } else { + $createtable = $res->fetch_assoc(); + $res->close(); + $tablecreate .= $createtable['Create Table'] . ";\n"; + $tablecreate .= "/*!40101 SET character_set_client = @saved_cs_client */;\n"; + $this->write($tablecreate); + + if ($this->table_status[$table]['Engine'] !== 'MyISAM') { + $this->table_status[$table]['Rows'] = '~' . $this->table_status[$table]['Rows']; + } + + if ($this->table_status[$table]['Rows'] !== 0) { + //Dump Table data + $this->write("\n--\n-- Backup data for table `" . $table . "`\n--\n\nLOCK TABLES `" . $table . "` WRITE;\n/*!40000 ALTER TABLE `" . $table . "` DISABLE KEYS */;\n"); + } + + return $this->table_status[$table]['Rows']; + } + + return 0; + } + + /** + * Dump view structure. + * + * @param string $view name of Table to dump + * + * @throws BackWPup_MySQLDump_Exception + */ + public function dump_view_table_head($view): void + { + //Dump the view structure + $res = $this->mysqli->query('SHOW CREATE VIEW `' . $view . '`'); + ++$GLOBALS[\wpdb::class]->num_queries; + if ($this->mysqli->error) { + trigger_error(sprintf(__('Database error %1$s for query %2$s', 'backwpup'), $this->mysqli->error, 'SHOW CREATE VIEW `' . $view . '`'), E_USER_WARNING); + } else { + $createview = $res->fetch_assoc(); + $res->close(); + $tablecreate = "\n--\n-- View structure for `" . $view . "`\n--\n\n"; + $tablecreate .= 'DROP TABLE IF EXISTS `' . $view . "`;\n"; + $tablecreate .= 'DROP VIEW IF EXISTS `' . $view . "`;\n"; + $tablecreate .= "/*!40101 SET @saved_cs_client = @@character_set_client */;\n"; + $tablecreate .= "/*!40101 SET character_set_client = '" . $this->mysqli->character_set_name() . "' */;\n"; + $tablecreate .= $createview['Create View'] . ";\n"; + $tablecreate .= "/*!40101 SET character_set_client = @saved_cs_client */;\n"; + $this->write($tablecreate); + } + } + + /** + * Dump table footer. + * + * @param string $table name of Table to dump + * + * @return int Size of table + */ + public function dump_table_footer($table): void + { + if ($this->table_status[$table]['Rows'] !== 0) { + $this->write('/*!40000 ALTER TABLE `' . $table . "` ENABLE KEYS */;\nUNLOCK TABLES;\n"); + } + } + + /** + * Dump table Data. + * + * @param string $table name of Table to dump + * @param int $start Start of lengh paramter + * @param int $length how many + * + * @throws BackWPup_MySQLDump_Exception + * + * @return int done records in this backup + */ + public function dump_table($table, $start = 0, $length = 0) + { + if (!is_numeric($start) || $start < 0) { + throw new BackWPup_MySQLDump_Exception(sprintf(__('Start for table backup is not correctly set: %1$s', 'backwpup'), $start)); + } + + if (!is_numeric($length) || $length < 0) { + throw new BackWPup_MySQLDump_Exception(sprintf(__('Length for table backup is not correctly set: %1$s', 'backwpup'), $length)); + } + + $done_records = 0; + + if ($this->get_table_type_for($table) === 'VIEW') { + return $done_records; + } + + //get data from table + try { + $res = $this->do_table_query($table, $start, $length); + } catch (BackWPup_MySQLDump_Exception $e) { + trigger_error(sprintf(__('Database error %1$s for query %2$s', 'backwpup'), $e->getMessage(), 'SELECT * FROM `' . $table . '`'), E_USER_WARNING); + + return 0; + } + + $fieldsarray = []; + $fieldinfo = []; + $fields = $res->fetch_fields(); + $i = 0; + + foreach ($fields as $field) { + $fieldsarray[$i] = $field->orgname; + $fieldinfo[$fieldsarray[$i]] = $field; + ++$i; + } + $dump = ''; + + while ($data = $res->fetch_assoc()) { + $values = []; + + foreach ($data as $key => $value) { + if ($value === null) { // Make Value NULL to string NULL + $value = 'NULL'; + } elseif (in_array((int) $fieldinfo[$key]->type, [MYSQLI_TYPE_DECIMAL, MYSQLI_TYPE_NEWDECIMAL, MYSQLI_TYPE_BIT, MYSQLI_TYPE_TINY, MYSQLI_TYPE_SHORT, MYSQLI_TYPE_LONG, MYSQLI_TYPE_FLOAT, MYSQLI_TYPE_DOUBLE, MYSQLI_TYPE_LONGLONG, MYSQLI_TYPE_INT24, MYSQLI_TYPE_YEAR], true)) {//is value numeric no esc + $value = empty($value) ? 0 : $value; + } elseif (in_array((int) $fieldinfo[$key]->type, [MYSQLI_TYPE_TIMESTAMP, MYSQLI_TYPE_DATE, MYSQLI_TYPE_TIME, MYSQLI_TYPE_DATETIME, MYSQLI_TYPE_NEWDATE], true)) {//date/time types + $value = "'{$value}'"; + } elseif ($fieldinfo[$key]->flags & MYSQLI_BINARY_FLAG) {//is value binary + $hex = unpack('H*', $value); + $value = empty($value) ? "''" : "0x{$hex[1]}"; + } else { + $value = "'" . $this->escapeString($value) . "'"; + } + $values[] = $value; + } + //new query in dump on more than 50000 chars. + if (empty($dump)) { + $dump = 'INSERT INTO `' . $table . '` (`' . implode('`, `', $fieldsarray) . "`) VALUES \n"; + } + if (strlen($dump) <= 50000) { + $dump .= '(' . implode(', ', $values) . "),\n"; + } else { + $dump .= '(' . implode(', ', $values) . ");\n"; + $this->write($dump); + $dump = ''; + } + ++$done_records; + } + if (!empty($dump)) { + // Remove trailing , and newline. + $dump = substr($dump, 0, -2) . ";\n"; + $this->write($dump); + } + $res->close(); + + return $done_records; + } + + /** + * Dump functions. + * + * Dumps all functions found in the database. + */ + protected function dump_functions() + { + try { + $statusResult = $this->query('SHOW FUNCTION STATUS'); + } catch (\mysqli_sql_exception $e) { + $this->logQueryError($e, 'SHOW FUNCTION STATUS'); + + return; + } + + while ($function = $statusResult->fetch_assoc()) { + if ($this->getDbName() !== $function['Db']) { + continue; + } + + $query = sprintf( + 'SHOW CREATE FUNCTION `%1$s`.`%2$s`', + $function['Db'], + $function['Name'] + ); + + try { + $createResult = $this->query($query); + $createFunction = $createResult->fetch_assoc(); + $createResult->close(); + + if ($createFunction === null) { + continue; + } + + $sql = sprintf( + "\n--\n" . + "-- Function structure for %1\$s\n" . + "--\n\n" . + "/*!50003 DROP FUNCTION IF EXISTS `%1\$s` */;\n" . + "/*!50003 SET @saved_cs_client = @@character_set_client */ ;\n" . + "/*!50003 SET @saved_cs_results = @@character_set_results */ ;\n" . + "/*!50003 SET @saved_col_connection = @@collation_connection */ ;\n" . + "/*!50003 SET character_set_client = %2\$s */ ;\n" . + "/*!50003 SET character_set_results = %2\$s */ ;\n" . + "/*!50003 SET collation_connection = %3\$s */ ;\n" . + "/*!50003 SET @saved_sql_mode = @@sql_mode */ ;\n" . + "/*!50003 SET sql_mode = '%4\$s' */ ;\n" . + "DELIMITER ;;\n" . + "%5\$s ;;\n" . + "DELIMITER ;\n" . + "/*!50003 SET sql_mode = @saved_sql_mode */ ;\n" . + "/*!50003 SET character_set_client = @saved_cs_client */ ;\n" . + "/*!50003 SET character_set_results = @saved_cs_results */ ;\n" . + "/*!50003 SET collation_connection = @saved_col_connection */ ;\n", + $createFunction['Function'], + $createFunction['character_set_client'], + $createFunction['collation_connection'], + $createFunction['sql_mode'], + $createFunction['Create Function'] + ); + $this->write($sql); + } catch (\mysqli_sql_exception $e) { + $this->logQueryError($e, $query); + } + } + + $statusResult->close(); + } + + /** + * Dump procedures. + * + * Dumps all stored procedures found in the database. + */ + protected function dump_procedures() + { + try { + $statusResult = $this->query('SHOW PROCEDURE STATUS'); + } catch (mysqli_sql_exception $e) { + $this->logQueryError($e, 'SHOW PROCEDURE STATUS'); + + return; + } + + while ($procedure = $statusResult->fetch_assoc()) { + if ($this->getDbName() !== $procedure['Db']) { + continue; + } + + $query = sprintf( + 'SHOW CREATE PROCEDURE `%1$s`.`%2$s`', + $procedure['Db'], + $procedure['Name'] + ); + + try { + $createResult = $this->query($query); + $createProcedure = $createResult->fetch_assoc(); + $createResult->close(); + + if ($createProcedure === null) { + continue; + } + + $sql = sprintf( + "\n--\n" . + "-- Procedure structure for %1\$s\n" . + "--\n\n" . + "/*!50003 DROP PROCEDURE IF EXISTS `%1\$s` */;\n" . + "/*!50003 SET @saved_cs_client = @@character_set_client */ ;\n" . + "/*!50003 SET @saved_cs_results = @@character_set_results */ ;\n" . + "/*!50003 SET @saved_col_connection = @@collation_connection */ ;\n" . + "/*!50003 SET character_set_client = %2\$s */ ;\n" . + "/*!50003 SET character_set_results = %2\$s */ ;\n" . + "/*!50003 SET collation_connection = %3\$s */ ;\n" . + "/*!50003 SET @saved_sql_mode = @@sql_mode */ ;\n" . + "/*!50003 SET sql_mode = '%4\$s' */ ;\n" . + "DELIMITER ;;\n" . + "%5\$s ;;\n" . + "DELIMITER ;\n" . + "/*!50003 SET sql_mode = @saved_sql_mode */ ;\n" . + "/*!50003 SET character_set_client = @saved_cs_client */ ;\n" . + "/*!50003 SET character_set_results = @saved_cs_results */ ;\n" . + "/*!50003 SET collation_connection = @saved_col_connection */ ;\n", + $createProcedure['Procedure'], + $createProcedure['character_set_client'], + $createProcedure['collation_connection'], + $createProcedure['sql_mode'], + $createProcedure['Create Procedure'] + ); + $this->write($sql); + } catch (mysqli_sql_exception $e) { + $this->logQueryError($e, $query); + } + } + + $statusResult->close(); + } + + /** + * Dump triggers. + * + * Dumps all triggers found in the database. + */ + protected function dump_triggers() + { + $query = sprintf('SHOW TRIGGERS FROM `%1$s`', $this->getDbName()); + + try { + $statusResult = $this->query($query); + } catch (\mysqli_sql_exception $e) { + $this->logQueryError($e, $query); + + return; + } + + while ($trigger = $statusResult->fetch_assoc()) { + $query = sprintf( + 'SHOW CREATE TRIGGER `%1$s`.`%2$s`', + $this->getDbName(), + $trigger['Trigger'] + ); + + try { + $createResult = $this->query($query); + $createTrigger = $createResult->fetch_assoc(); + $createResult->close(); + + if ($createTrigger === null) { + continue; + } + + $sql = sprintf( + "\n--\n" . + "-- Trigger structure for %1\$s\n" . + "--\n\n" . + "/*!50032 DROP TRIGGER IF EXISTS `%1\$s` */;\n" . + "/*!50003 SET @saved_cs_client = @@character_set_client */ ;\n" . + "/*!50003 SET @saved_cs_results = @@character_set_results */ ;\n" . + "/*!50003 SET @saved_col_connection = @@collation_connection */ ;\n" . + "/*!50003 SET character_set_client = %2\$s */ ;\n" . + "/*!50003 SET character_set_results = %2\$s */ ;\n" . + "/*!50003 SET collation_connection = %3\$s */ ;\n" . + "/*!50003 SET @saved_sql_mode = @@sql_mode */ ;\n" . + "/*!50003 SET sql_mode = '%4\$s' */ ;\n" . + "DELIMITER ;;\n" . + "/*!50003 %5\$s */;;\n" . + "DELIMITER ;\n" . + "/*!50003 SET sql_mode = @saved_sql_mode */ ;\n" . + "/*!50003 SET character_set_client = @saved_cs_client */ ;\n" . + "/*!50003 SET character_set_results = @saved_cs_results */ ;\n" . + "/*!50003 SET collation_connection = @saved_col_connection */ ;\n", + $createTrigger['Trigger'], + $createTrigger['character_set_client'], + $createTrigger['collation_connection'], + $createTrigger['sql_mode'], + $createTrigger['SQL Original Statement'] + ); + $this->write($sql); + } catch (\mysqli_sql_exception $e) { + $this->logQueryError($e, $query); + } + } + + $statusResult->close(); + } + + /** + * Writes data to handle and compress. + * + * @param $data string to write + * + * @throws BackWPup_MySQLDump_Exception + */ + protected function write($data) + { + $written = fwrite($this->handle, $data); + + if (!$written) { + throw new BackWPup_MySQLDump_Exception(__('Error while writing file!', 'backwpup')); + } + } + + /** + * Closes all confections on shutdown. + */ + public function __destruct() + { + //close MySQL connection + if ($this->mysqli !== null) { + $this->mysqli->close(); + } + //close file handle + if (is_resource($this->handle)) { + fclose($this->handle); + } + } + + /** + * Get table type for given table. + * + * @param string $table the table to look up the type for + * + * @return string The table type if found + */ + protected function get_table_type_for($table) + { + if (isset($this->table_types[$table])) { + return $this->table_types[$table]; + } + + return null; + } + + /** + * Perform query to fetch table rows. + * + * @param string $table The table on which to perform the query + * @param int $start The record to start at + * @param int $length How many records to fetch + * + * @throws \BackWPup_MySQLDump_Exception In case of mysql error + * + * @return \mysqli_result The resulting query + */ + protected function do_table_query($table, $start, $length) + { + if ($length == 0 && $start == 0) { + $res = $this->mysqli->query('SELECT * FROM `' . $table . '` ', MYSQLI_USE_RESULT); + } else { + $res = $this->mysqli->query('SELECT * FROM `' . $table . '` LIMIT ' . $start . ', ' . $length, MYSQLI_USE_RESULT); + } + ++$GLOBALS[\wpdb::class]->num_queries; + if ($this->mysqli->error) { + throw new BackWPup_MySQLDump_Exception($this->mysqli->error); + } + + return $res; + } + + /** + * Escapes a string for MySQL. + * + * @param string $value The value to escape + * + * @return string The escaped string + */ + protected function escapeString($value) + { + return $this->mysqli->real_escape_string($value); + } } /** - * Exception Handler + * Exception Handler. */ -class BackWPup_MySQLDump_Exception extends Exception { } +class BackWPup_MySQLDump_Exception extends Exception +{ +} diff --git a/inc/class-option.php b/inc/class-option.php index 678daf24..87c23c18 100755 --- a/inc/class-option.php +++ b/inc/class-option.php @@ -3,538 +3,530 @@ use Base32\Base32; /** - * Class for options + * Class for options. */ -final class BackWPup_Option { - - /** - * - * add filter for Site option defaults - * - */ - public static function default_site_options() { - - //global - add_site_option( 'backwpup_version', '0.0.0' ); - //job default - add_site_option( 'backwpup_jobs', array() ); - //general - add_site_option( 'backwpup_cfg_showadminbar', false ); - add_site_option( 'backwpup_cfg_showfoldersize', false ); - add_site_option( 'backwpup_cfg_protectfolders', true ); - //job - add_site_option( 'backwpup_cfg_jobmaxexecutiontime', 30 ); - add_site_option( 'backwpup_cfg_jobstepretry', 3 ); - add_site_option( 'backwpup_cfg_jobrunauthkey', BackWPup::get_generated_hash( 8 ) ); - add_site_option( 'backwpup_cfg_loglevel', 'normal_translated' ); - add_site_option( 'backwpup_cfg_jobwaittimems', 0 ); - add_site_option( 'backwpup_cfg_jobdooutput', 0 ); - add_site_option( 'backwpup_cfg_windows', 0 ); - //Logs - add_site_option( 'backwpup_cfg_maxlogs', 30 ); - add_site_option( 'backwpup_cfg_gzlogs', 0 ); - $upload_dir = wp_upload_dir( null, false, true ); - $logs_dir = trailingslashit( str_replace( '\\', - '/', - $upload_dir['basedir'] ) ) . 'backwpup-' . BackWPup::get_plugin_data( 'hash' ) . '-logs/'; - $content_path = trailingslashit( str_replace( '\\', '/', WP_CONTENT_DIR ) ); - $logs_dir = str_replace( $content_path, '', $logs_dir ); - add_site_option( 'backwpup_cfg_logfolder', $logs_dir ); - //Network Auth - add_site_option( 'backwpup_cfg_httpauthuser', '' ); - add_site_option( 'backwpup_cfg_httpauthpassword', '' ); - - } - - /** - * - * Update a BackWPup option - * - * @param int $jobid the job id - * @param string $option Option key - * @param mixed $value the value to store - * - * @return bool if option save or not - */ - public static function update( $jobid, $option, $value ) { - - $jobid = (int) $jobid; - $option = sanitize_key( trim( $option ) ); - - if ( empty( $jobid ) || empty( $option ) ) { - return false; - } - - //Update option - $jobs_options = self::jobs_options( false ); - $jobs_options[ $jobid ][ $option ] = $value; - - return self::update_jobs_options( $jobs_options ); - } - - /** - * - * Load BackWPup Options - * - * @param bool $use_cache - * - * @return array of options - */ - private static function jobs_options( $use_cache = true ) { - - global $current_site; - - //remove from cache - if ( ! $use_cache ) { - if ( is_multisite() ) { - $network_id = $current_site->id; - $cache_key = "$network_id:backwpup_jobs"; - wp_cache_delete( $cache_key, 'site-options' ); - } else { - wp_cache_delete( 'backwpup_jobs', 'options' ); - $alloptions = wp_cache_get( 'alloptions', 'options' ); - if ( isset( $alloptions['backwpup_jobs'] ) ) { - unset( $alloptions['backwpup_jobs'] ); - wp_cache_set( 'alloptions', $alloptions, 'options' ); - } - } - } - - return get_site_option( 'backwpup_jobs', array() ); - } - - /** - * - * Update BackWPup Options - * - * @param array $options The options array to save - * - * @return bool updated or not - */ - private static function update_jobs_options( $options ) { - - return update_site_option( 'backwpup_jobs', $options ); - } - - /** - * - * Get a BackWPup Option - * - * @param int $jobid Option the job id - * @param string $option Option key - * @param mixed $default returned if no value, if null the the default BackWPup option will get - * @param bool $use_cache USe the cache - * - * @return bool|mixed false if nothing can get else the option value - */ - public static function get( $jobid, $option, $default = null, $use_cache = true ) { - - $jobid = (int) $jobid; - $option = sanitize_key( trim( $option ) ); - - if ( empty( $jobid ) || empty( $option ) ) { - return false; - } - - $jobs_options = self::jobs_options( $use_cache ); - - if ( isset( $jobs_options[ $jobid ] ) && isset( $jobs_options[ $jobid ]['archivename'] ) ) { - $jobs_options[ $jobid ]['archivenamenohash'] = $jobs_options[ $jobid ]['archivename']; - } - - if ( ! isset( $jobs_options[ $jobid ][ $option ] ) && $default !== null ) { - return $default; - } - - if ( ! isset( $jobs_options[ $jobid ][ $option ] ) ) { - if ( $option === 'archivename' ) { - return self::normalize_archive_name( self::defaults_job( $option ), $jobid ); - } - - return self::defaults_job( $option ); - } - - // Ensure archive name formatted properly - if ( $option === 'archivename' ) { - return self::normalize_archive_name( $jobs_options[ $jobid ][ $option ], $jobid, true ); - } - - if ( $option === 'archivenamenohash' ) { - return self::normalize_archive_name( $jobs_options[ $jobid ]['archivename'], $jobid, false ); - } - - $option_value = $jobs_options[ $jobid ][ $option ]; - - switch ( $option ) { - case 'archiveformat': - if ( $option_value === '.tar.bz2' ) { - $option_value = '.tar.gz'; - } - break; - case 'pluginlistfilecompression': - case 'wpexportfilecompression': - if ( $option_value === '.bz2' ) { - $option_value = '.gz'; - } - break; - } - - return $option_value; - } - - /** - * - * Get default option for BackWPup option - * - * @param string $key Option key - * - * @internal param int $id The job id - * - * @return bool|mixed - */ - public static function defaults_job( $key = '' ) { - - $key = sanitize_key( trim( $key ) ); - - //set defaults - $default['type'] = array( 'DBDUMP', 'FILE', 'WPPLUGIN' ); - $default['destinations'] = array(); - $default['name'] = __( 'New Job', 'backwpup' ); - $default['activetype'] = ''; - $default['logfile'] = ''; - $default['lastbackupdownloadurl'] = ''; - $default['cronselect'] = 'basic'; - $default['cron'] = '0 3 * * *'; - $default['mailaddresslog'] = sanitize_email( get_bloginfo( 'admin_email' ) ); - $default['mailaddresssenderlog'] = 'BackWPup ' . get_bloginfo( 'name' ) . ' <' . sanitize_email( get_bloginfo( 'admin_email' ) ) . '>'; - $default['mailerroronly'] = true; - $default['backuptype'] = 'archive'; - $default['archiveformat'] = '.zip'; - $default['archivename'] = '%Y-%m-%d_%H-%i-%s_%hash%'; - $default['archivenamenohash'] = '%Y-%m-%d_%H-%i-%s_%hash%'; - //defaults vor destinations - foreach ( BackWPup::get_registered_destinations() as $dest_key => $dest ) { - if ( ! empty( $dest['class'] ) ) { - $dest_object = BackWPup::get_destination( $dest_key ); - $default = array_merge( $default, $dest_object->option_defaults() ); - } - } - //defaults vor job types - foreach ( BackWPup::get_job_types() as $job_type ) { - $default = array_merge( $default, $job_type->option_defaults() ); - } - - //return all - if ( empty( $key ) ) { - return $default; - } - //return one default setting - if ( isset( $default[ $key ] ) ) { - return $default[ $key ]; - } else { - return false; - } - } - - /** - * - * BackWPup Job Options - * - * @param int $id The job id - * @param bool $use_cache - * - * @return array of all job options - */ - public static function get_job( $id, $use_cache = true ) { - - if ( ! is_numeric( $id ) ) { - return false; - } - - $id = intval( $id ); - $jobs_options = self::jobs_options( $use_cache ); - if ( isset( $jobs_options[ $id ]['archivename'] ) ) { - $jobs_options[ $id ]['archivename'] = self::normalize_archive_name( - $jobs_options[ $id ]['archivename'], - $id, - true - ); - } - - $options = wp_parse_args( $jobs_options[ $id ], self::defaults_job() ); - - if ( isset( $options['archiveformat'] ) && $options['archiveformat'] === '.tar.bz2' ) { - $options['archiveformat'] = '.tar.gz'; - } - if ( isset($options['pluginlistfilecompression'] ) && $options['pluginlistfilecompression'] === '.bz2' ) { - $options['pluginlistfilecompression'] = '.gz'; - } - if ( isset($options['wpexportfilecompression'] ) && $options['wpexportfilecompression'] === '.bz2' ) { - $options['wpexportfilecompression'] = '.gz'; - } - - return $options; - } - - - /** - * - * Delete a BackWPup Option - * - * @param int $jobid the job id - * @param string $option Option key - * - * @return bool deleted or not - */ - public static function delete( $jobid, $option ) { - - $jobid = (int) $jobid; - $option = sanitize_key( trim( $option ) ); - - if ( empty( $jobid ) || empty( $option ) ) { - return false; - } - - //delete option - $jobs_options = self::jobs_options( false ); - unset( $jobs_options[ $jobid ][ $option ] ); - - return self::update_jobs_options( $jobs_options ); - } - - /** - * - * Delete a BackWPup Job - * - * @param int $id The job id - * - * @return bool deleted or not - */ - public static function delete_job( $id ) { - - if ( ! is_numeric( $id ) ) { - return false; - } - - $id = intval( $id ); - $jobs_options = self::jobs_options( false ); - unset( $jobs_options[ $id ] ); - - return self::update_jobs_options( $jobs_options ); - } - - /** - * - * get the id's of jobs - * - * @param string|null $key Option key or null for getting all id's - * @param bool $value Value that the option must have to get the id - * - * @return array job id's - */ - public static function get_job_ids( $key = null, $value = false ) { - - $key = sanitize_key( trim( $key ) ); - $jobs_options = self::jobs_options( false ); - - if ( empty( $jobs_options ) ) { - return array(); - } - - //get option job ids - if ( empty( $key ) ) { - return array_keys( $jobs_options ); - } - - //get option ids for option with the defined value - $new_option_job_ids = array(); - foreach ( $jobs_options as $id => $option ) { - if ( isset( $option[ $key ] ) && $value == $option[ $key ] ) { - $new_option_job_ids[] = (int) $id; - } - } - sort( $new_option_job_ids ); - - return $new_option_job_ids; - } - - /** - * Gets the next available job id. - * - * @return int - */ - public static function next_job_id() { - - $ids = self::get_job_ids(); - sort( $ids ); - - return end( $ids ) + 1; - } - - /** - * Normalizes the archive name. - * - * The archive name should include the hash to identify this site, and the job id to identify this job. - * - * This allows backup files belonging to this job to be tracked. - * - * @param string $archive_name - * @param int $jobid - * - * @return string The normalized archive name - */ - public static function normalize_archive_name( $archive_name, $jobid, $substitute_hash = true ) { - - $hash = BackWPup::get_plugin_data( 'hash' ); - $generated_hash = self::get_generated_hash( $jobid ); - - // Does the string contain %hash%? - if ( strpos( $archive_name, '%hash%' ) !== false ) { - if ( $substitute_hash == true ) { - return str_replace( '%hash%', $generated_hash, $archive_name ); - } else { - // Nothing needs to be done since we don't have to substitute it. - return $archive_name; - } - } else { - // %hash% not included, so check for old style archive name pre-3.4.3 - // If name starts with 'backwpup', then we can try to parse - if ( substr( $archive_name, 0, 8 ) == 'backwpup' ) { - $parts = explode( '_', $archive_name ); - - // Decode hash part if hash not found (from 3.4.2) - if ( strpos( $parts[1], $hash ) === false ) { - $parts[1] = is_numeric($parts[1]) ? base_convert( $parts[1], 36, 16 ) : $parts[1]; - } - - // Search again - if ( strpos( $parts[1], $hash ) !== false ) { - $parts[1] = '%hash%'; - } else { - // Hash not included, so insert - array_splice( $parts, 1, 0, '%hash%' ); - } - $archive_name = implode( '_', $parts ); - if ( $substitute_hash == true ) { - return str_replace( '%hash%', $generated_hash, $archive_name ); - } else { - return $archive_name; - } - } else { - // But otherwise, just append the hash - if ( $substitute_hash == true ) { - return $archive_name . '_' . $generated_hash; - } else { - return $archive_name . '_%hash%'; - } - } - - } - - } - - /** - * Generate a hash including random bytes and job ID - * - * @return string - */ - public static function get_generated_hash( $jobid ) { - - return Base32::encode( pack( 'H*', - sprintf( '%02x%06s%02x', - mt_rand( 0, 255 ), - BackWPup::get_plugin_data( 'hash' ), - mt_rand( 0, 255 ) ) ) ) . - sprintf( '%02d', $jobid ); - } - - /** - * Return the decoded hash and the job ID. - * - * If the hash is not found in the given code, then false is returned. - * - * @param string $code The string to decode - * - * @return array|bool An array with hash and job ID, or false otherwise - */ - public static function decode_hash( $code ) { - - $hash = BackWPup::get_plugin_data( 'hash' ); - - // Try base 32 first - $decoded = bin2hex( Base32::decode( substr( $code, 0, 8 ) ) ); - - if ( substr( $decoded, 2, 6 ) == $hash ) { - return array( substr( $decoded, 2, 6 ), intval( substr( $code, - 2 ) ) ); - } - - // Try base 36 - $decoded = is_numeric($code) ? base_convert( $code, 36, 16 ) : $code; - if ( substr( $decoded, 2, 6 ) == $hash ) { - return array( substr( $decoded, 2, 6 ), intval( substr( $decoded, - 2 ) ) ); - } - - // Check style prior to 3.4.1 - if ( substr( $code, 0, 6 ) == $hash ) { - return array( substr( $code, 0, 6 ), intval( substr( $code, - 2 ) ) ); - } - - // Tried everything, now return failure - return false; - } - - /** - * Substitute date variables in archive name. - * - * @param string $archivename The name of the archive. - * - * @return string The archive name with substituted variables. - */ - public static function substitute_date_vars( $archivename ) { - - $current_time = current_time( 'timestamp' ); - $datevars = array( - '%d', - '%j', - '%m', - '%n', - '%Y', - '%y', - '%a', - '%A', - '%B', - '%g', - '%G', - '%h', - '%H', - '%i', - '%s', - ); - $datevalues = array( - date( 'd', $current_time ), - date( 'j', $current_time ), - date( 'm', $current_time ), - date( 'n', $current_time ), - date( 'Y', $current_time ), - date( 'y', $current_time ), - date( 'a', $current_time ), - date( 'A', $current_time ), - date( 'B', $current_time ), - date( 'g', $current_time ), - date( 'G', $current_time ), - date( 'h', $current_time ), - date( 'H', $current_time ), - date( 'i', $current_time ), - date( 's', $current_time ), - ); - // Temporarily replace %hash% with [hash] - $archivename = str_replace( '%hash%', '[hash]', $archivename ); - $archivename = str_replace( $datevars, - $datevalues, - $archivename ); - $archivename = str_replace( '[hash]', '%hash%', $archivename ); - - return BackWPup_Job::sanitize_file_name( $archivename ); - } - +final class BackWPup_Option +{ + /** + * add filter for Site option defaults. + */ + public static function default_site_options() + { + //global + add_site_option('backwpup_version', '0.0.0'); + //job default + add_site_option('backwpup_jobs', []); + //general + add_site_option('backwpup_cfg_showadminbar', false); + add_site_option('backwpup_cfg_showfoldersize', false); + add_site_option('backwpup_cfg_protectfolders', true); + //job + add_site_option('backwpup_cfg_jobmaxexecutiontime', 30); + add_site_option('backwpup_cfg_jobstepretry', 3); + add_site_option('backwpup_cfg_jobrunauthkey', BackWPup::get_generated_hash(8)); + add_site_option('backwpup_cfg_loglevel', 'normal_translated'); + add_site_option('backwpup_cfg_jobwaittimems', 0); + add_site_option('backwpup_cfg_jobdooutput', 0); + add_site_option('backwpup_cfg_windows', 0); + //Logs + add_site_option('backwpup_cfg_maxlogs', 30); + add_site_option('backwpup_cfg_gzlogs', 0); + $upload_dir = wp_upload_dir(null, false, true); + $logs_dir = trailingslashit(str_replace( + '\\', + '/', + $upload_dir['basedir'] + )) . 'backwpup-' . BackWPup::get_plugin_data('hash') . '-logs/'; + $content_path = trailingslashit(str_replace('\\', '/', WP_CONTENT_DIR)); + $logs_dir = str_replace($content_path, '', $logs_dir); + add_site_option('backwpup_cfg_logfolder', $logs_dir); + //Network Auth + add_site_option('backwpup_cfg_httpauthuser', ''); + add_site_option('backwpup_cfg_httpauthpassword', ''); + } + + /** + * Update a BackWPup option. + * + * @param int $jobid the job id + * @param string $option Option key + * @param mixed $value the value to store + * + * @return bool if option save or not + */ + public static function update($jobid, $option, $value) + { + $jobid = (int) $jobid; + $option = sanitize_key(trim($option)); + + if (empty($jobid) || empty($option)) { + return false; + } + + //Update option + $jobs_options = self::jobs_options(false); + $jobs_options[$jobid][$option] = $value; + + return self::update_jobs_options($jobs_options); + } + + /** + * Load BackWPup Options. + * + * @param bool $use_cache + * + * @return array of options + */ + private static function jobs_options($use_cache = true) + { + global $current_site; + + //remove from cache + if (!$use_cache) { + if (is_multisite()) { + $network_id = $current_site->id; + $cache_key = "{$network_id}:backwpup_jobs"; + wp_cache_delete($cache_key, 'site-options'); + } else { + wp_cache_delete('backwpup_jobs', 'options'); + $alloptions = wp_cache_get('alloptions', 'options'); + if (isset($alloptions['backwpup_jobs'])) { + unset($alloptions['backwpup_jobs']); + wp_cache_set('alloptions', $alloptions, 'options'); + } + } + } + + return get_site_option('backwpup_jobs', []); + } + + /** + * Update BackWPup Options. + * + * @param array $options The options array to save + * + * @return bool updated or not + */ + private static function update_jobs_options($options) + { + return update_site_option('backwpup_jobs', $options); + } + + /** + * Get a BackWPup Option. + * + * @param int $jobid Option the job id + * @param string $option Option key + * @param mixed $default returned if no value, if null the the default BackWPup option will get + * @param bool $use_cache USe the cache + * + * @return bool|mixed false if nothing can get else the option value + */ + public static function get($jobid, $option, $default = null, $use_cache = true) + { + $jobid = (int) $jobid; + $option = sanitize_key(trim($option)); + + if (empty($jobid) || empty($option)) { + return false; + } + + $jobs_options = self::jobs_options($use_cache); + + if (isset($jobs_options[$jobid], $jobs_options[$jobid]['archivename'])) { + $jobs_options[$jobid]['archivenamenohash'] = $jobs_options[$jobid]['archivename']; + } + + if (!isset($jobs_options[$jobid][$option]) && $default !== null) { + return $default; + } + + if (!isset($jobs_options[$jobid][$option])) { + if ($option === 'archivename') { + return self::normalize_archive_name(self::defaults_job($option), $jobid); + } + + return self::defaults_job($option); + } + + // Ensure archive name formatted properly + if ($option === 'archivename') { + return self::normalize_archive_name($jobs_options[$jobid][$option], $jobid, true); + } + + if ($option === 'archivenamenohash') { + return self::normalize_archive_name($jobs_options[$jobid]['archivename'], $jobid, false); + } + + $option_value = $jobs_options[$jobid][$option]; + + switch ($option) { + case 'archiveformat': + if ($option_value === '.tar.bz2') { + $option_value = '.tar.gz'; + } + break; + + case 'pluginlistfilecompression': + case 'wpexportfilecompression': + if ($option_value === '.bz2') { + $option_value = '.gz'; + } + break; + } + + return $option_value; + } + + /** + * Get default option for BackWPup option. + * + * @param string $key Option key + * + * @internal param int $id The job id + * + * @return bool|mixed + */ + public static function defaults_job($key = '') + { + $key = sanitize_key(trim($key)); + + //set defaults + $default = []; + $default['type'] = ['DBDUMP', 'FILE', 'WPPLUGIN']; + $default['destinations'] = []; + $default['name'] = __('New Job', 'backwpup'); + $default['activetype'] = ''; + $default['logfile'] = ''; + $default['lastbackupdownloadurl'] = ''; + $default['cronselect'] = 'basic'; + $default['cron'] = '0 3 * * *'; + $default['mailaddresslog'] = sanitize_email(get_bloginfo('admin_email')); + $default['mailaddresssenderlog'] = 'BackWPup ' . get_bloginfo('name') . ' <' . sanitize_email(get_bloginfo('admin_email')) . '>'; + $default['mailerroronly'] = true; + $default['backuptype'] = 'archive'; + $default['archiveformat'] = '.zip'; + $default['archivename'] = '%Y-%m-%d_%H-%i-%s_%hash%'; + $default['archivenamenohash'] = '%Y-%m-%d_%H-%i-%s_%hash%'; + //defaults vor destinations + foreach (BackWPup::get_registered_destinations() as $dest_key => $dest) { + if (!empty($dest['class'])) { + $dest_object = BackWPup::get_destination($dest_key); + $default = array_merge($default, $dest_object->option_defaults()); + } + } + //defaults vor job types + foreach (BackWPup::get_job_types() as $job_type) { + $default = array_merge($default, $job_type->option_defaults()); + } + + //return all + if (empty($key)) { + return $default; + } + //return one default setting + if (isset($default[$key])) { + return $default[$key]; + } + + return false; + } + + /** + * BackWPup Job Options. + * + * @param int $id The job id + * @param bool $use_cache + * + * @return array of all job options + */ + public static function get_job($id, $use_cache = true) + { + if (!is_numeric($id)) { + return false; + } + + $id = intval($id); + $jobs_options = self::jobs_options($use_cache); + if (isset($jobs_options[$id]['archivename'])) { + $jobs_options[$id]['archivename'] = self::normalize_archive_name( + $jobs_options[$id]['archivename'], + $id, + true + ); + } + + $options = wp_parse_args($jobs_options[$id], self::defaults_job()); + + if (isset($options['archiveformat']) && $options['archiveformat'] === '.tar.bz2') { + $options['archiveformat'] = '.tar.gz'; + } + if (isset($options['pluginlistfilecompression']) && $options['pluginlistfilecompression'] === '.bz2') { + $options['pluginlistfilecompression'] = '.gz'; + } + if (isset($options['wpexportfilecompression']) && $options['wpexportfilecompression'] === '.bz2') { + $options['wpexportfilecompression'] = '.gz'; + } + + return $options; + } + + /** + * Delete a BackWPup Option. + * + * @param int $jobid the job id + * @param string $option Option key + * + * @return bool deleted or not + */ + public static function delete($jobid, $option) + { + $jobid = (int) $jobid; + $option = sanitize_key(trim($option)); + + if (empty($jobid) || empty($option)) { + return false; + } + + //delete option + $jobs_options = self::jobs_options(false); + unset($jobs_options[$jobid][$option]); + + return self::update_jobs_options($jobs_options); + } + + /** + * Delete a BackWPup Job. + * + * @param int $id The job id + * + * @return bool deleted or not + */ + public static function delete_job($id) + { + if (!is_numeric($id)) { + return false; + } + + $id = intval($id); + $jobs_options = self::jobs_options(false); + unset($jobs_options[$id]); + + return self::update_jobs_options($jobs_options); + } + + /** + * get the id's of jobs. + * + * @param string|null $key Option key or null for getting all id's + * @param bool $value Value that the option must have to get the id + * + * @return array job id's + */ + public static function get_job_ids($key = null, $value = false) + { + $key = sanitize_key(trim($key)); + $jobs_options = self::jobs_options(false); + + if (empty($jobs_options)) { + return []; + } + + //get option job ids + if (empty($key)) { + return array_keys($jobs_options); + } + + //get option ids for option with the defined value + $new_option_job_ids = []; + + foreach ($jobs_options as $id => $option) { + if (isset($option[$key]) && $value == $option[$key]) { + $new_option_job_ids[] = (int) $id; + } + } + sort($new_option_job_ids); + + return $new_option_job_ids; + } + + /** + * Gets the next available job id. + * + * @return int + */ + public static function next_job_id() + { + $ids = self::get_job_ids(); + sort($ids); + + return end($ids) + 1; + } + + /** + * Normalizes the archive name. + * + * The archive name should include the hash to identify this site, and the job id to identify this job. + * + * This allows backup files belonging to this job to be tracked. + * + * @param string $archive_name + * @param int $jobid + * + * @return string The normalized archive name + */ + public static function normalize_archive_name($archive_name, $jobid, $substitute_hash = true) + { + $hash = BackWPup::get_plugin_data('hash'); + $generated_hash = self::get_generated_hash($jobid); + + // Does the string contain %hash%? + if (strpos($archive_name, '%hash%') !== false) { + if ($substitute_hash == true) { + return str_replace('%hash%', $generated_hash, $archive_name); + } + // Nothing needs to be done since we don't have to substitute it. + return $archive_name; + } + // %hash% not included, so check for old style archive name pre-3.4.3 + // If name starts with 'backwpup', then we can try to parse + if (substr($archive_name, 0, 8) == 'backwpup') { + $parts = explode('_', $archive_name); + + // Decode hash part if hash not found (from 3.4.2) + if (strpos($parts[1], $hash) === false) { + $parts[1] = is_numeric($parts[1]) ? base_convert($parts[1], 36, 16) : $parts[1]; + } + + // Search again + if (strpos($parts[1], $hash) !== false) { + $parts[1] = '%hash%'; + } else { + // Hash not included, so insert + array_splice($parts, 1, 0, '%hash%'); + } + $archive_name = implode('_', $parts); + if ($substitute_hash == true) { + return str_replace('%hash%', $generated_hash, $archive_name); + } + + return $archive_name; + } + // But otherwise, just append the hash + if ($substitute_hash == true) { + return $archive_name . '_' . $generated_hash; + } + + return $archive_name . '_%hash%'; + } + + /** + * Generate a hash including random bytes and job ID. + * + * @return string + */ + public static function get_generated_hash($jobid) + { + return Base32::encode(pack( + 'H*', + sprintf( + '%02x%06s%02x', + random_int(0, 255), + BackWPup::get_plugin_data('hash'), + random_int(0, 255) + ) + )) . + sprintf('%02d', $jobid); + } + + /** + * Return the decoded hash and the job ID. + * + * If the hash is not found in the given code, then false is returned. + * + * @param string $code The string to decode + * + * @return array|bool An array with hash and job ID, or false otherwise + */ + public static function decode_hash($code) + { + $hash = BackWPup::get_plugin_data('hash'); + + // Try base 32 first + $decoded = bin2hex(Base32::decode(substr($code, 0, 8))); + + if (substr($decoded, 2, 6) == $hash) { + return [substr($decoded, 2, 6), intval(substr($code, -2))]; + } + + // Try base 36 + $decoded = is_numeric($code) ? base_convert($code, 36, 16) : $code; + if (substr($decoded, 2, 6) == $hash) { + return [substr($decoded, 2, 6), intval(substr($decoded, -2))]; + } + + // Check style prior to 3.4.1 + if (substr($code, 0, 6) == $hash) { + return [substr($code, 0, 6), intval(substr($code, -2))]; + } + + // Tried everything, now return failure + return false; + } + + /** + * Substitute date variables in archive name. + * + * @param string $archivename the name of the archive + * + * @return string the archive name with substituted variables + */ + public static function substitute_date_vars($archivename) + { + $current_time = current_time('timestamp'); + $datevars = [ + '%d', + '%j', + '%m', + '%n', + '%Y', + '%y', + '%a', + '%A', + '%B', + '%g', + '%G', + '%h', + '%H', + '%i', + '%s', + ]; + $datevalues = [ + date('d', $current_time), + date('j', $current_time), + date('m', $current_time), + date('n', $current_time), + date('Y', $current_time), + date('y', $current_time), + date('a', $current_time), + date('A', $current_time), + date('B', $current_time), + date('g', $current_time), + date('G', $current_time), + date('h', $current_time), + date('H', $current_time), + date('i', $current_time), + date('s', $current_time), + ]; + // Temporarily replace %hash% with [hash] + $archivename = str_replace('%hash%', '[hash]', $archivename); + $archivename = str_replace( + $datevars, + $datevalues, + $archivename + ); + $archivename = str_replace('[hash]', '%hash%', $archivename); + + return BackWPup_Job::sanitize_file_name($archivename); + } } diff --git a/inc/class-page-about.php b/inc/class-page-about.php index c0eac60f..65afeac5 100644 --- a/inc/class-page-about.php +++ b/inc/class-page-about.php @@ -1,18 +1,15 @@ +class BackWPup_Page_About +{ + /** + * Enqueue style. + */ + public static function admin_print_styles() + { + ?> + } + + /** + * Enqueue script. + */ + public static function admin_print_scripts() + { + wp_enqueue_script('backwpupgeneral'); + } + + /** + * Print the markup. + */ + public static function page() + { + ?>
- +
- <?php esc_html_e( 'BackWPup banner', 'backwpup' ); ?> -

-

/wp-content/. Push them to an external storage service if you don’t want to save the backups on the same server. With a single backup archive you are able to restore an installation. Use the integrated restore feature to restore your site directly from your WordPress backend or the Restore Standalone App in case your site is destroyed completely.', 'backwpup' ); ?>

-

set up a backup job? You can use the wizards or plan your backup in expert mode.', 'backwpup' ), network_admin_url( 'admin.php').'?page=backwpupeditjob' , network_admin_url( 'admin.php').'?page=backwpupwizard' ) ); ?>

+ <?php esc_html_e('BackWPup banner', 'backwpup'); ?> +

+

/wp-content/. Push them to an external storage service if you don’t want to save the backups on the same server. With a single backup archive you are able to restore an installation. Use the integrated restore feature to restore your site directly from your WordPress backend or the Restore Standalone App in case your site is destroyed completely.', 'backwpup'); ?>

+

set up a backup job? You can use the wizards or plan your backup in expert mode.', 'backwpup'), network_admin_url('admin.php') . '?page=backwpupeditjob', network_admin_url('admin.php') . '?page=backwpupwizard')); ?>

- <?php esc_html_e( 'BackWPup banner', 'backwpup' ); ?> -

+ <?php esc_html_e('BackWPup banner', 'backwpup'); ?> +

/wp-content/. Push them to an external storage service if you don’t want to save the backups on the same server. With a single backup archive you are able to restore an installation. Use the integrated restore feature to restore your site directly from your WordPress backend or the Restore Standalone App in case your site is destroyed completely.', 'backwpup' ); ?>

-

+_e('Use your backup archives to save your entire WordPress installation including /wp-content/. Push them to an external storage service if you don’t want to save the backups on the same server. With a single backup archive you are able to restore an installation. Use the integrated restore feature to restore your site directly from your WordPress backend or the Restore Standalone App in case your site is destroyed completely.', 'backwpup'); ?>

+

@@ -386,323 +377,322 @@ public static function page() {
- +
-

-

set up a backup job, so you will never forget it. There is also an option to repair and optimize the database after each backup.', 'backwpup' ), network_admin_url( 'admin.php').'?page=backwpupeditjob' ) ); ?>

+

+

set up a backup job, so you will never forget it. There is also an option to repair and optimize the database after each backup.', 'backwpup'), network_admin_url('admin.php') . '?page=backwpupeditjob')); ?>

-

+

- +
- +

-

create a job to update a backup copy of your file system only when files are changed.', 'backwpup'), network_admin_url( 'admin.php' ) . '?page=backwpupeditjob' ) ); ?>

+

create a job to update a backup copy of your file system only when files are changed.', 'backwpup'), network_admin_url('admin.php') . '?page=backwpupeditjob')); ?>

-

+

- +
- +
-

-

+

+

-

+

- - - + + + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +
Premium support', 'backwpup' ); ?>Premium support', 'backwpup'); ?>
Automatic updates', 'backwpup' ); ?>Automatic updates', 'backwpup'); ?>
'backups', - 'singular' => 'backup', - 'ajax' => true, - ) ); - - $this->destinations = BackWPup::get_registered_destinations(); - - } - - public function ajax_user_can() { - - return current_user_can( 'backwpup_backups' ); - } - - public function prepare_items() { - - $per_page = $this->get_items_per_page( 'backwpupbackups_per_page' ); - if ( empty( $per_page ) || $per_page < 1 ) { - $per_page = 20; - } - - $jobdest = ''; - if ( isset( $_GET['jobdets-button-top'] ) ) { - $jobdest = sanitize_text_field( $_GET['jobdest-top'] ); - } - if ( isset( $_GET['jobdets-button-bottom'] ) ) { - $jobdest = sanitize_text_field( $_GET['jobdest-bottom'] ); - } - - if ( empty( $jobdest ) ) { - $jobdests = $this->get_destinations_list(); - if ( empty( $jobdests ) ) { - $jobdests = array( '_' ); - } - $jobdest = $jobdests[0]; - $_GET['jobdest-top'] = $jobdests[0]; - $_GET['jobdets-button-top'] = 'empty'; - } - - list( $this->jobid, $this->dest ) = explode( '_', $jobdest ); - - if ( ! empty( $this->destinations[ $this->dest ]['class'] ) ) { - /** @var BackWPup_Destinations $dest_object */ - $dest_object = BackWPup::get_destination( $this->dest ); - $this->items = $dest_object->file_get_list( $jobdest ); - } - - if ( ! $this->items ) { - $this->items = ''; - - return; - } - - // Sorting. - $order = filter_input( INPUT_GET, 'order', FILTER_SANITIZE_STRING ) ?: 'desc'; - $orderby = filter_input( INPUT_GET, 'orderby', FILTER_SANITIZE_STRING ) ?: 'time'; - $tmp = array(); - - if ( $orderby === 'time' ) { - if ( $order === 'asc' ) { - foreach ( $this->items as &$ma ) { - $tmp[] = &$ma["time"]; - } - array_multisort( $tmp, SORT_ASC, $this->items ); - } else { - foreach ( $this->items as &$ma ) { - $tmp[] = &$ma["time"]; - } - array_multisort( $tmp, SORT_DESC, $this->items ); - } - } elseif ( $orderby === 'file' ) { - if ( $order === 'asc' ) { - foreach ( $this->items as &$ma ) { - $tmp[] = &$ma["filename"]; - } - array_multisort( $tmp, SORT_ASC, $this->items ); - } else { - foreach ( $this->items as &$ma ) { - $tmp[] = &$ma["filename"]; - } - array_multisort( $tmp, SORT_DESC, $this->items ); - } - } elseif ( $orderby === 'folder' ) { - if ( $order === 'asc' ) { - foreach ( $this->items as &$ma ) { - $tmp[] = &$ma["folder"]; - } - array_multisort( $tmp, SORT_ASC, $this->items ); - } else { - foreach ( $this->items as &$ma ) { - $tmp[] = &$ma["folder"]; - } - array_multisort( $tmp, SORT_DESC, $this->items ); - } - } elseif ( $orderby === 'size' ) { - if ( $order === 'asc' ) { - foreach ( $this->items as &$ma ) { - $tmp[] = &$ma["filesize"]; - } - array_multisort( $tmp, SORT_ASC, $this->items ); - } else { - foreach ( $this->items as &$ma ) { - $tmp[] = &$ma["filesize"]; - } - array_multisort( $tmp, SORT_DESC, $this->items ); - } - } - - $this->set_pagination_args( array( - 'total_items' => count( $this->items ), - 'per_page' => $per_page, - 'jobdest' => $jobdest, - 'orderby' => $orderby, - 'order' => $order, - ) ); - - // Only display items on page. - $start = intval( ( $this->get_pagenum() - 1 ) * $per_page ); - $end = $start + $per_page; - if ( $end > count( $this->items ) ) { - $end = count( $this->items ); - } - - $i = - 1; - $paged_items = array(); - foreach ( $this->items as $item ) { - $i ++; - if ( $i < $start ) { - continue; - } - if ( $i >= $end ) { - break; - } - $paged_items[] = $item; - } - - $this->items = $paged_items; - - } + public function __construct() + { + parent::__construct([ + 'plural' => 'backups', + 'singular' => 'backup', + 'ajax' => true, + ]); - public function no_items() { + $this->destinations = BackWPup::get_registered_destinations(); + } - _e( 'No files could be found. (List will be generated during next backup.)', 'backwpup' ); - } + public function ajax_user_can() + { + return current_user_can('backwpup_backups'); + } - public function get_bulk_actions() { + public function prepare_items() + { + $per_page = $this->get_items_per_page('backwpupbackups_per_page'); + if (empty($per_page) || $per_page < 1) { + $per_page = 20; + } + + $jobdest = ''; + if (isset($_GET['jobdets-button-top'])) { + $jobdest = sanitize_text_field($_GET['jobdest-top']); + } + if (isset($_GET['jobdets-button-bottom'])) { + $jobdest = sanitize_text_field($_GET['jobdest-bottom']); + } + + if (empty($jobdest)) { + $jobdests = $this->get_destinations_list(); + if (empty($jobdests)) { + $jobdests = ['_']; + } + $jobdest = $jobdests[0]; + $_GET['jobdest-top'] = $jobdests[0]; + $_GET['jobdets-button-top'] = 'empty'; + } + + [$this->jobid, $this->dest] = explode('_', $jobdest); + + if (!empty($this->destinations[$this->dest]['class'])) { + /** @var BackWPup_Destinations $dest_object */ + $dest_object = BackWPup::get_destination($this->dest); + $this->items = $dest_object->file_get_list($jobdest); + } + + if (!$this->items) { + $this->items = ''; + + return; + } + + // Sorting. + $order = filter_input(INPUT_GET, 'order', FILTER_SANITIZE_STRING) ?: 'desc'; + $orderby = filter_input(INPUT_GET, 'orderby', FILTER_SANITIZE_STRING) ?: 'time'; + $tmp = []; + + if ($orderby === 'time') { + if ($order === 'asc') { + foreach ($this->items as &$ma) { + $tmp[] = &$ma['time']; + } + array_multisort($tmp, SORT_ASC, $this->items); + } else { + foreach ($this->items as &$ma) { + $tmp[] = &$ma['time']; + } + array_multisort($tmp, SORT_DESC, $this->items); + } + } elseif ($orderby === 'file') { + if ($order === 'asc') { + foreach ($this->items as &$ma) { + $tmp[] = &$ma['filename']; + } + array_multisort($tmp, SORT_ASC, $this->items); + } else { + foreach ($this->items as &$ma) { + $tmp[] = &$ma['filename']; + } + array_multisort($tmp, SORT_DESC, $this->items); + } + } elseif ($orderby === 'folder') { + if ($order === 'asc') { + foreach ($this->items as &$ma) { + $tmp[] = &$ma['folder']; + } + array_multisort($tmp, SORT_ASC, $this->items); + } else { + foreach ($this->items as &$ma) { + $tmp[] = &$ma['folder']; + } + array_multisort($tmp, SORT_DESC, $this->items); + } + } elseif ($orderby === 'size') { + if ($order === 'asc') { + foreach ($this->items as &$ma) { + $tmp[] = &$ma['filesize']; + } + array_multisort($tmp, SORT_ASC, $this->items); + } else { + foreach ($this->items as &$ma) { + $tmp[] = &$ma['filesize']; + } + array_multisort($tmp, SORT_DESC, $this->items); + } + } + + $this->set_pagination_args([ + 'total_items' => count($this->items), + 'per_page' => $per_page, + 'jobdest' => $jobdest, + 'orderby' => $orderby, + 'order' => $order, + ]); + + // Only display items on page. + $start = intval(($this->get_pagenum() - 1) * $per_page); + $end = $start + $per_page; + if ($end > count($this->items)) { + $end = count($this->items); + } + + $i = -1; + $paged_items = []; + + foreach ($this->items as $item) { + ++$i; + if ($i < $start) { + continue; + } + if ($i >= $end) { + break; + } + $paged_items[] = $item; + } - if ( ! $this->has_items() ) { - return array(); - } + $this->items = $paged_items; + } - $actions = array(); - $actions['delete'] = __( 'Delete', 'backwpup' ); + public function no_items() + { + _e('No files could be found. (List will be generated during next backup.)', 'backwpup'); + } - return $actions; - } + public function get_bulk_actions() + { + if (!$this->has_items()) { + return []; + } - public function extra_tablenav( $which ) { + $actions = []; + $actions['delete'] = __('Delete', 'backwpup'); - $destinations_list = $this->get_destinations_list(); + return $actions; + } - if ( count( $destinations_list ) < 1 ) { - return; - } + public function extra_tablenav($which) + { + $destinations_list = $this->get_destinations_list(); - if ( count( $destinations_list ) === 1 ) { - echo ''; + if (count($destinations_list) < 1) { + return; + } - return; - } + if (count($destinations_list) === 1) { + echo ''; - ?> + return; + } ?>
- - 'query-submit-' . $which ) ); ?> + 'query-submit-' . $which] + ); ?>
'; - $posts_columns['time'] = __( 'Time', 'backwpup' ); - $posts_columns['file'] = __( 'File', 'backwpup' ); - $posts_columns['folder'] = __( 'Folder', 'backwpup' ); - $posts_columns['size'] = __( 'Size', 'backwpup' ); - - return $posts_columns; - } - - public function get_sortable_columns() { - - return array( - 'file' => array( 'file', false ), - 'folder' => 'folder', - 'size' => 'size', - 'time' => array( 'time', false ), - ); - } + } - public function column_cb( $item ) { + public function get_columns() + { + $posts_columns = []; + $posts_columns['cb'] = ''; + $posts_columns['time'] = __('Time', 'backwpup'); + $posts_columns['file'] = __('File', 'backwpup'); + $posts_columns['folder'] = __('Folder', 'backwpup'); + $posts_columns['size'] = __('Size', 'backwpup'); + + return $posts_columns; + } - return ''; - } + public function get_sortable_columns() + { + return [ + 'file' => ['file', false], + 'folder' => 'folder', + 'size' => 'size', + 'time' => ['time', false], + ]; + } - public function get_destinations_list() { + public function column_cb($item) + { + return ''; + } - $jobdest = array(); - $jobids = BackWPup_Option::get_job_ids(); + public function get_destinations_list() + { + $jobdest = []; + $jobids = BackWPup_Option::get_job_ids(); - foreach ( $jobids as $jobid ) { - if ( BackWPup_Option::get( $jobid, 'backuptype' ) === 'sync' ) { - continue; - } - $dests = BackWPup_Option::get( $jobid, 'destinations' ); - foreach ( $dests as $dest ) { - if ( ! $this->destinations[ $dest ]['class'] ) { - continue; - } - $dest_class = BackWPup::get_destination( $dest ); - $can_do_dest = $dest_class->file_get_list( $jobid . '_' . $dest ); - if ( ! empty( $can_do_dest ) ) { - $jobdest[] = $jobid . '_' . $dest; - } - } - } + foreach ($jobids as $jobid) { + if (BackWPup_Option::get($jobid, 'backuptype') === 'sync') { + continue; + } + $dests = BackWPup_Option::get($jobid, 'destinations'); - return $jobdest; - } + foreach ($dests as $dest) { + if (!$this->destinations[$dest]['class']) { + continue; + } + $dest_class = BackWPup::get_destination($dest); + $can_do_dest = $dest_class->file_get_list($jobid . '_' . $dest); + if (!empty($can_do_dest)) { + $jobdest[] = $jobid . '_' . $dest; + } + } + } - public function column_file( $item ) { + return $jobdest; + } - $actions = array(); + public function column_file($item) + { + $actions = []; - $r = '' . esc_attr( $item['filename'] ) . '
'; - if ( ! empty( $item['info'] ) ) { - $r .= esc_attr( $item['info'] ) . '
'; - } + $r = '' . esc_attr($item['filename']) . '
'; + if (!empty($item['info'])) { + $r .= esc_attr($item['info']) . '
'; + } - if ( current_user_can( 'backwpup_backups_delete' ) ) { - $actions['delete'] = $this->delete_item_action( $item ); - } + if (current_user_can('backwpup_backups_delete')) { + $actions['delete'] = $this->delete_item_action($item); + } - if ( ! empty( $item['downloadurl'] ) && current_user_can( 'backwpup_backups_download' ) ) { - try { + if (!empty($item['downloadurl']) && current_user_can('backwpup_backups_download')) { + try { $actions['download'] = $this->download_item_action($item); if ($this->dest === 'HIDRIVE') { - $downloadUrl = wp_nonce_url($item['downloadurl'], 'backwpup_action_nonce'); if ($item['filesize'] > 10485760) { // 10 MB @@ -296,7 +301,6 @@ public function column_file( $item ) { $actions['download'] = 'Download'; } - } catch (BackWPup_Factory_Exception $e) { $actions['download'] = sprintf( '%2$s', @@ -304,172 +308,183 @@ public function column_file( $item ) { __('Download', 'backwpup') ); } - } - - // Add restore url to link list - if ( current_user_can( 'backwpup_restore' ) && ! empty( $item['restoreurl'] ) ) { - - $item['restoreurl'] = add_query_arg( - array( - 'step' => 1, - 'trigger_download' => 1, - ), - $item['restoreurl'] - ); - $actions['restore'] = sprintf( - '%2$s', - wp_nonce_url( $item['restoreurl'], 'restore-backup_' . $this->jobid ), - __( 'Restore', 'backwpup' ) - ); - } - - $r .= $this->row_actions( $actions ); - - return $r; - } - - public function column_folder( $item ) { + } + + // Add restore url to link list + if (current_user_can('backwpup_restore') && !empty($item['restoreurl'])) { + $item['restoreurl'] = add_query_arg( + [ + 'step' => 1, + 'trigger_download' => 1, + ], + $item['restoreurl'] + ); + $actions['restore'] = sprintf( + '%2$s', + wp_nonce_url($item['restoreurl'], 'restore-backup_' . $this->jobid), + __('Restore', 'backwpup') + ); + } + + $r .= $this->row_actions($actions); + + return $r; + } - return esc_attr( $item['folder'] ); - } + public function column_folder($item) + { + return esc_attr($item['folder']); + } - public function column_size( $item ) { + public function column_size($item) + { + if (!empty($item['filesize']) && $item['filesize'] != -1) { + return size_format($item['filesize'], 2); + } - if ( ! empty( $item['filesize'] ) && $item['filesize'] != - 1 ) { - return size_format( $item['filesize'], 2 ); - } else { - return __( '?', 'backwpup' ); - } - } + return __('?', 'backwpup'); + } - public function column_time( $item ) { + public function column_time($item) + { + return sprintf( + __('%1$s at %2$s', 'backwpup'), + date_i18n(get_option('date_format'), $item['time'], true), + date_i18n(get_option('time_format'), $item['time'], true) + ); + } - return sprintf( __( '%1$s at %2$s', 'backwpup' ), - date_i18n( get_option( 'date_format' ), $item['time'], true ), - date_i18n( get_option( 'time_format' ), $item['time'], true ) ); - } + public static function load() + { + global $current_user; - public static function load() { + //Create Table + self::$listtable = new BackWPup_Page_Backups(); - //Create Table - self::$listtable = new BackWPup_Page_Backups; + switch (self::$listtable->current_action()) { + case 'delete': //Delete Backup archives + check_admin_referer('bulk-backups'); + if (!current_user_can('backwpup_backups_delete')) { + wp_die(__('Sorry, you don\'t have permissions to do that.', 'backwpup')); + } - switch ( self::$listtable->current_action() ) { - case 'delete': //Delete Backup archives - check_admin_referer( 'bulk-backups' ); - if ( ! current_user_can( 'backwpup_backups_delete' ) ) { - wp_die( __( 'Sorry, you don\'t have permissions to do that.', 'backwpup' ) ); - } + $jobdest = ''; + if (isset($_GET['jobdest'])) { + $jobdest = sanitize_text_field($_GET['jobdest']); + } + if (isset($_GET['jobdest-top'])) { + $jobdest = sanitize_text_field($_GET['jobdest-top']); + } - $jobdest = ''; - if ( isset( $_GET['jobdest'] ) ) { - $jobdest = sanitize_text_field( $_GET['jobdest'] ); - } - if ( isset( $_GET['jobdest-top'] ) ) { - $jobdest = sanitize_text_field( $_GET['jobdest-top'] ); - } + $_GET['jobdest-top'] = $jobdest; + $_GET['jobdets-button-top'] = 'submit'; - $_GET['jobdest-top'] = $jobdest; - $_GET['jobdets-button-top'] = 'submit'; + if ($jobdest === '') { + return; + } - if ( $jobdest === '' ) { - return; - } + [$jobid, $dest] = explode('_', $jobdest); + /** @var BackWPup_Destinations $dest_class */ + $dest_class = BackWPup::get_destination($dest); + $files = $dest_class->file_get_list($jobdest); - list( $jobid, $dest ) = explode( '_', $jobdest ); - /** @var BackWPup_Destinations $dest_class */ - $dest_class = BackWPup::get_destination( $dest ); - $files = $dest_class->file_get_list( $jobdest ); - foreach ( $_GET['backupfiles'] as $backupfile ) { - foreach ( $files as $file ) { - if ( is_array( $file ) && $file['file'] == $backupfile ) { - $dest_class->file_delete( $jobdest, $backupfile ); - } - } - } - $files = $dest_class->file_get_list( $jobdest ); - if ( empty ( $files ) ) { - $_GET['jobdest-top'] = ''; - } - break; - default: - if ( isset( $_GET['jobid'] ) ) { - $jobid = absint( $_GET['jobid'] ); - if ( ! current_user_can( 'backwpup_backups_download' ) ) { - wp_die( __( 'Sorry, you don\'t have permissions to do that.', 'backwpup' ) ); - } - check_admin_referer( 'backwpup_action_nonce' ); - - $filename = untrailingslashit( BackWPup::get_plugin_data( 'temp' ) ) . '/' . basename( isset( $_GET['local_file'] ) ? $_GET['local_file'] : $_GET['file'] ); - if ( file_exists( $filename ) ) { - $downloader = new BackWPup_Download_File( - $filename, - function ( \BackWPup_Download_File_Interface $obj ) use ( $filename ) { - - $obj->clean_ob() - ->headers(); - - readfile( $filename ); - - // Delete the temporary file. - unlink( $filename ); - die(); - }, - 'backwpup_backups_download' - ); - $downloader->download(); - } else { - // If the file doesn't exist, fallback to old way of downloading - // This is for destinations without a downloader class - $dest = strtoupper( str_replace( 'download', '', self::$listtable->current_action() ) ); - if ( ! empty( $dest ) && strstr( self::$listtable->current_action(), 'download' ) ) { - /** @var BackWPup_Destinations $dest_class */ - $dest_class = BackWPup::get_destination( $dest ); - - try { - $dest_class->file_download( $jobid, trim( sanitize_text_field( $_GET['file'] ) ) ); - } catch ( BackWPup_Destination_Download_Exception $e ) { - header( 'HTTP/1.0 404 Not Found' ); - wp_die( - esc_html__( 'Ops! Unfortunately the file doesn\'t exists. May be was deleted?' ), - esc_html__( '404 - File Not Found.' ), - array( - 'back_link' => esc_html__( '« Go back', 'backwpup' ), - ) - ); - } - } - } - } - break; - } - - //Save per page - if ( isset( $_POST['screen-options-apply'] ) && isset( $_POST['wp_screen_options']['option'] ) && isset( $_POST['wp_screen_options']['value'] ) && $_POST['wp_screen_options']['option'] == 'backwpupbackups_per_page' ) { - check_admin_referer( 'screen-options-nonce', 'screenoptionnonce' ); - global $current_user; - if ( $_POST['wp_screen_options']['value'] > 0 && $_POST['wp_screen_options']['value'] < 1000 ) { - update_user_option( $current_user->ID, - 'backwpupbackups_per_page', - (int) $_POST['wp_screen_options']['value'] ); - wp_redirect( remove_query_arg( array( 'pagenum', 'apage', 'paged' ), wp_get_referer() ) ); - exit; - } - } + foreach ($_GET['backupfiles'] as $backupfile) { + foreach ($files as $file) { + if (is_array($file) && $file['file'] == $backupfile) { + $dest_class->file_delete($jobdest, $backupfile); + } + } + } + $files = $dest_class->file_get_list($jobdest); + if (empty($files)) { + $_GET['jobdest-top'] = ''; + } + break; - add_screen_option( 'per_page', - array( - 'label' => __( 'Backup Files', 'backwpup' ), - 'default' => 20, - 'option' => 'backwpupbackups_per_page', - ) ); + default: + if (isset($_GET['jobid'])) { + $jobid = absint($_GET['jobid']); + if (!current_user_can('backwpup_backups_download')) { + wp_die(__('Sorry, you don\'t have permissions to do that.', 'backwpup')); + } + check_admin_referer('backwpup_action_nonce'); + + $filename = untrailingslashit(BackWPup::get_plugin_data('temp')) . '/' . basename($_GET['local_file'] ?? $_GET['file']); + if (file_exists($filename)) { + $downloader = new BackWPup_Download_File( + $filename, + function (BackWPup_Download_File_Interface $obj) use ($filename) { + $obj->clean_ob() + ->headers() + ; + + readfile($filename); + + // Delete the temporary file. + unlink($filename); + + exit(); + }, + 'backwpup_backups_download' + ); + $downloader->download(); + } else { + // If the file doesn't exist, fallback to old way of downloading + // This is for destinations without a downloader class + $dest = strtoupper(str_replace('download', '', self::$listtable->current_action())); + if (!empty($dest) && strstr(self::$listtable->current_action(), 'download')) { + /** @var BackWPup_Destinations $dest_class */ + $dest_class = BackWPup::get_destination($dest); + + try { + $dest_class->file_download($jobid, trim(sanitize_text_field($_GET['file']))); + } catch (BackWPup_Destination_Download_Exception $e) { + header('HTTP/1.0 404 Not Found'); + wp_die( + esc_html__('Ops! Unfortunately the file doesn\'t exists. May be was deleted?'), + esc_html__('404 - File Not Found.'), + [ + 'back_link' => esc_html__('« Go back', 'backwpup'), + ] + ); + } + } + } + } + break; + } + + //Save per page + if (isset($_POST['screen-options-apply'], $_POST['wp_screen_options']['option'], $_POST['wp_screen_options']['value']) && $_POST['wp_screen_options']['option'] == 'backwpupbackups_per_page') { + check_admin_referer('screen-options-nonce', 'screenoptionnonce'); + + if ($_POST['wp_screen_options']['value'] > 0 && $_POST['wp_screen_options']['value'] < 1000) { + update_user_option( + $current_user->ID, + 'backwpupbackups_per_page', + (int) $_POST['wp_screen_options']['value'] + ); + wp_redirect(remove_query_arg(['pagenum', 'apage', 'paged'], wp_get_referer())); - self::$listtable->prepare_items(); - } + exit; + } + } + + add_screen_option( + 'per_page', + [ + 'label' => __('Backup Files', 'backwpup'), + 'default' => 20, + 'option' => 'backwpupbackups_per_page', + ] + ); - public static function admin_print_styles() { + self::$listtable->prepare_items(); + } - ?> + public static function admin_print_styles() + { + ?> + ]; + if (\BackWPup::is_pro()) { + $dependencies[] = 'decrypter'; + } + wp_enqueue_script( + 'backwpup-backup-downloader', + "{$plugin_scripts_url}/backup-downloader{$suffix}.js", + $dependencies, + filemtime("{$plugin_scripts_dir}/backup-downloader{$suffix}.js"), + true + ); + + if (\BackWPup::is_pro()) { + self::admin_print_pro_scripts($suffix, $plugin_url, $plugin_dir); + } + } + + public static function page() + { + ?>
-

+

@@ -555,29 +572,28 @@ public static function page() { jobid . '_' . $this->dest, - $this->get_pagenum(), - esc_attr( $item['file'] ) - ); - $url = wp_nonce_url( network_admin_url( 'admin.php' ) . $query, 'bulk-backups' ); - $js = sprintf( - 'if ( confirm(\'%s\') ) { return true; } return false;', - esc_js( - __( - 'You are about to delete this backup archive. \'Cancel\' to stop, \'OK\' to delete.', - "backwpup" - ) - ) - ); - - return sprintf( - '%3$s', - $url, - $js, - __( 'Delete', 'backwpup' ) - ); - } - - private function download_item_action( $item ) { - - $local_file = untrailingslashit( BackWPup::get_plugin_data( 'TEMP' ) ) . "/{$item['filename']}"; - - return sprintf( - 'jobid . '_' . $this->dest, + $this->get_pagenum(), + esc_attr($item['file']) + ); + $url = wp_nonce_url(network_admin_url('admin.php') . $query, 'bulk-backups'); + $js = sprintf( + 'if ( confirm(\'%s\') ) { return true; } return false;', + esc_js( + __( + 'You are about to delete this backup archive. \'Cancel\' to stop, \'OK\' to delete.', + 'backwpup' + ) + ) + ); + + return sprintf( + '%3$s', + $url, + $js, + __('Delete', 'backwpup') + ); + } + + private function download_item_action($item) + { + $local_file = untrailingslashit(BackWPup::get_plugin_data('TEMP')) . "/{$item['filename']}"; + + return sprintf( + '%7$s', - intval( $this->jobid ), - esc_attr( $this->dest ), - esc_attr( $item['file'] ), - esc_attr( $local_file ), - wp_create_nonce( 'backwpup_action_nonce' ), - wp_nonce_url( $item['downloadurl'], 'backwpup_action_nonce' ), - __( 'Download', 'backwpup' ) - ); - } + intval($this->jobid), + esc_attr($this->dest), + esc_attr($item['file']), + esc_attr($local_file), + wp_create_nonce('backwpup_action_nonce'), + wp_nonce_url($item['downloadurl'], 'backwpup_action_nonce'), + __('Download', 'backwpup') + ); + } } diff --git a/inc/class-page-backwpup.php b/inc/class-page-backwpup.php index aa9be96b..fa1fc230 100644 --- a/inc/class-page-backwpup.php +++ b/inc/class-page-backwpup.php @@ -1,411 +1,409 @@ tables_to_dump as $key => $table ) { - if ( $wpdb->prefix != substr( $table,0 , strlen( $wpdb->prefix ) ) ) - unset( $sql_dump->tables_to_dump[ $key ] ); - } - $sql_dump->execute(); - unset( $sql_dump ); - } catch ( Exception $e ) { - die( $e->getMessage() ); - } - die(); - } - } - - /** - * Enqueue script. - * - * @return void - */ - public static function admin_print_scripts() { - - wp_enqueue_script( 'backwpupgeneral' ); - - } - - /** - * Print the markup. - * - * @return void - */ - public static function page() { - // get wizards - $wizards = BackWPup::get_wizards(); - ?> +class BackWPup_Page_BackWPup +{ + /** + * Called on load action. + */ + public static function load() + { + global $wpdb; + + if (isset($_GET['action']) && $_GET['action'] == 'dbdumpdl') { + //check permissions + check_admin_referer('backwpupdbdumpdl'); + + if (!current_user_can('backwpup_jobs_edit')) { + exit(); + } + + //doing dump + header('Pragma: public'); + header('Expires: 0'); + header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); + header('Content-Type: application/octet-stream; charset=' . get_bloginfo('charset')); + header('Content-Disposition: attachment; filename=' . DB_NAME . '.sql;'); + + try { + $sql_dump = new BackWPup_MySQLDump(); + + foreach ($sql_dump->tables_to_dump as $key => $table) { + if ($wpdb->prefix != substr($table, 0, strlen($wpdb->prefix))) { + unset($sql_dump->tables_to_dump[$key]); + } + } + $sql_dump->execute(); + unset($sql_dump); + } catch (Exception $e) { + exit($e->getMessage()); + } + + exit(); + } + } + + /** + * Enqueue script. + */ + public static function admin_print_scripts() + { + wp_enqueue_script('backwpupgeneral'); + } + + /** + * Print the markup. + */ + public static function page() + { + // get wizards + $wizards = BackWPup::get_wizards(); ?>
-

+

+ if (BackWPup::is_pro()) { ?>
-

-

/wp-content/. Push them to an external storage service if you don’t want to save the backups on the same server.','backwpup'); ?>

-

-

-

-

expert mode for full control over all options.','backwpup'), network_admin_url( 'admin.php') . '?page=backwpupeditjob' ); echo ' '; _e( 'Please note: You are solely responsible for the security of your data; the authors of this plugin are not.', 'backwpup' ); ?>

+

+

/wp-content/. Push them to an external storage service if you don’t want to save the backups on the same server.', 'backwpup'); ?>

+

+

+

+

expert mode for full control over all options.', 'backwpup'), network_admin_url('admin.php') . '?page=backwpupeditjob'); echo ' '; _e('Please note: You are solely responsible for the security of your data; the authors of this plugin are not.', 'backwpup'); ?>

-

-

First steps box to plan and schedule backup jobs.','backwpup' ); echo ' '; _e('Use your backup archives to save your entire WordPress installation including /wp-content/. Push them to an external storage service if you don’t want to save the backups on the same server.','backwpup'); ?>

-

-

-

-

Add a new backup job and plan what you want to save.','backwpup'), network_admin_url( 'admin.php') . '?page=backwpupeditjob' ); ?> -
Please note: You are solely responsible for the security of your data; the authors of this plugin are not.', 'backwpup' ); ?>

+

+

First steps box to plan and schedule backup jobs.', 'backwpup'); echo ' '; _e('Use your backup archives to save your entire WordPress installation including /wp-content/. Push them to an external storage service if you don’t want to save the backups on the same server.', 'backwpup'); ?>

+

+

+

+

Add a new backup job and plan what you want to save.', 'backwpup'), network_admin_url('admin.php') . '?page=backwpupeditjob'); ?> +
Please note: You are solely responsible for the security of your data; the authors of this plugin are not.', 'backwpup'); ?>

+ if (current_user_can('backwpup_jobs_edit') && current_user_can('backwpup_logs') && current_user_can('backwpup_jobs_start')) { + ?>
-

+

    - -
  • -
  • + +
  • +
  • -
  • -
  • +
  • +
  • -
  • -
  • +
  • +
- + if (current_user_can('backwpup_jobs_start')) {?>
-

+

-
+
-

+

' . sprintf( __('RSS Error: %s', 'backwpup' ), $rss->get_error_message() ) . '

'; - } elseif ( ! $rss->get_item_quantity() ) { - echo '
  • ' . esc_html__( 'An error has occurred, which probably means the feed is down. Try again later.', 'backwpup' ) . '
'; - $rss->__destruct(); - unset( $rss ); - } else { - echo '
    '; - $first = TRUE; - foreach ( $rss->get_items( 0, 4 ) as $item ) { - $link = $item->get_link(); - while ( stristr($link, 'http') != $link ) { - $link = substr($link, 1); - } - $link = esc_url(strip_tags($link)); - $title = esc_attr(strip_tags($item->get_title())); - if ( empty($title) ) { - $title = __( 'Untitled', 'backwpup' ); - } - - $desc = str_replace( array("\n", "\r"), ' ', esc_attr( strip_tags( @html_entity_decode( $item->get_description(), ENT_QUOTES, get_option( 'blog_charset' ) ) ) ) ); - $excerpt = wp_html_excerpt( $desc, 360 ); - - // Append ellipsis. Change existing [...] to […]. - if ( '[...]' == substr( $excerpt, -5 ) ) - $excerpt = substr( $excerpt, 0, -5 ) . '[…]'; - elseif ( '[…]' != substr( $excerpt, -10 ) && $desc != $excerpt ) - $excerpt .= ' […]'; - - $excerpt = esc_html( $excerpt ); - - if ( $first ) { - $summary = "
    $excerpt
    "; - } else { - $summary = ''; - } - - $date = ''; - if ( $first ) { - $date = $item->get_date( 'U' ); - - if ( $date ) { - $date = ' ' . date_i18n( get_option( 'date_format' ), $date ) . ''; - } - } - - echo "
  • $title{$date}{$summary}
  • "; - $first = FALSE; - } - echo '
'; - $rss->__destruct(); - unset($rss); - } - ?> + $rss = fetch_feed(_x('https://backwpup.com/feed/', 'BackWPup News RSS Feed URL', 'backwpup')); + + if (is_wp_error($rss)) { + echo '

' . sprintf(__('RSS Error: %s', 'backwpup'), $rss->get_error_message()) . '

'; + } elseif (!$rss->get_item_quantity()) { + echo '
  • ' . esc_html__('An error has occurred, which probably means the feed is down. Try again later.', 'backwpup') . '
'; + $rss->__destruct(); + unset($rss); + } else { + echo '
    '; + $first = true; + + foreach ($rss->get_items(0, 4) as $item) { + $link = $item->get_link(); + + while (stristr($link, 'http') != $link) { + $link = substr($link, 1); + } + $link = esc_url(strip_tags($link)); + $title = esc_attr(strip_tags($item->get_title())); + if (empty($title)) { + $title = __('Untitled', 'backwpup'); + } + + $desc = str_replace(["\n", "\r"], ' ', esc_attr(strip_tags(@html_entity_decode($item->get_description(), ENT_QUOTES, get_option('blog_charset'))))); + $excerpt = wp_html_excerpt($desc, 360); + + // Append ellipsis. Change existing [...] to […]. + if ('[...]' == substr($excerpt, -5)) { + $excerpt = substr($excerpt, 0, -5) . '[…]'; + } elseif ('[…]' != substr($excerpt, -10) && $desc != $excerpt) { + $excerpt .= ' […]'; + } + + $excerpt = esc_html($excerpt); + + if ($first) { + $summary = "
    {$excerpt}
    "; + } else { + $summary = ''; + } + + $date = ''; + if ($first) { + $date = $item->get_date('U'); + + if ($date) { + $date = ' ' . date_i18n(get_option('date_format'), $date) . ''; + } + } + + echo "
  • {$title}{$date}{$summary}
  • "; + $first = false; + } + echo '
'; + $rss->__destruct(); + unset($rss); + } ?>
info[ 'cap' ] ) ) - continue; - //get info of wizard - echo '
'; - echo '

' . $wizard_class->info[ 'name' ] . '

'; - echo '

' . $wizard_class->info[ 'description' ] . '

'; - $conf_names = $wizard_class->get_pre_configurations(); - if ( ! empty ( $conf_names ) ) { - echo ''; - } else { - echo ''; - } - wp_nonce_field( 'wizard' ); - echo ''; - echo ''; - echo '
'; - echo '
'; - } - } ?> + if (BackWPup::is_pro()) { + /** @var BackWPup_Pro_Wizards $wizard_class */ + foreach ($wizards as $wizard_class) { + //check permissions + if (!current_user_can($wizard_class->info['cap'])) { + continue; + } + //get info of wizard + echo '
'; + echo '

' . $wizard_class->info['name'] . '

'; + echo '

' . $wizard_class->info['description'] . '

'; + $conf_names = $wizard_class->get_pre_configurations(); + if (!empty($conf_names)) { + echo ''; + } else { + echo ''; + } + wp_nonce_field('wizard'); + echo ''; + echo ''; + echo '
'; + echo '
'; + } + } ?>
+ self::mb_next_jobs(); + self::mb_last_logs(); ?>
- +
-

+

-

<?php esc_html_e( 'BackWPup banner', 'backwpup' ); ?>

-

+

<?php esc_html_e('BackWPup banner', 'backwpup'); ?>

+

    -
  • dedicated support at backwpup.com.', 'Pro teaser box', 'backwpup' ); ?>
  • -
  • -
  • -
  • %s', _x( 'And more…', 'Pro teaser box, link text', 'backwpup' ) ); ?>
  • +
  • dedicated support at backwpup.com.', 'Pro teaser box', 'backwpup'); ?>
  • +
  • +
  • +
  • %s', _x('And more…', 'Pro teaser box, link text', 'backwpup')); ?>
-

+

+ } + + /** + * Displaying next jobs. + */ + private static function mb_next_jobs() + { + if (!current_user_can('backwpup_jobs')) { + return; + } ?> - + - - + + job[ 'jobid' ] ) && ! in_array($job_object->job[ 'jobid' ], $mainsactive, true ) ) - $mainsactive[ ] = $job_object->job[ 'jobid' ]; - foreach ( $mainsactive as $jobid ) { - $name = BackWPup_Option::get( $jobid, 'name' ); - if ( ! empty( $job_object ) && $job_object->job[ 'jobid' ] == $jobid ) { - $runtime = current_time( 'timestamp' ) - $job_object->job[ 'lastrun' ]; - if ( ! $alternate ) { - echo ''; - $alternate = TRUE; - } else { - echo ''; - $alternate = FALSE; - } - echo ''; - echo '"; - } - else { - if ( ! $alternate ) { - echo ''; - $alternate = TRUE; - } else { - echo ''; - $alternate = FALSE; - } - if ( $nextrun = wp_next_scheduled( 'backwpup_cron', array( 'arg' => $jobid ) ) + ( get_option( 'gmt_offset' ) * 3600 ) ) - echo ''; - else - echo ''; - - echo ''; - } - } - if ( empty( $mainsactive ) and ! empty( $job_object ) ) { - echo ''; - } - ?> + //get next jobs + $mainsactive = BackWPup_Option::get_job_ids('activetype', 'wpcron'); + sort($mainsactive); + $alternate = true; + // add working job if it not in active jobs + $job_object = BackWPup_Job::get_working_data(); + if (!empty($job_object) && !empty($job_object->job['jobid']) && !in_array($job_object->job['jobid'], $mainsactive, true)) { + $mainsactive[] = $job_object->job['jobid']; + } + + foreach ($mainsactive as $jobid) { + $name = BackWPup_Option::get($jobid, 'name'); + if (!empty($job_object) && $job_object->job['jobid'] == $jobid) { + $runtime = current_time('timestamp') - $job_object->job['lastrun']; + if (!$alternate) { + echo ''; + $alternate = true; + } else { + echo ''; + $alternate = false; + } + echo ''; + echo ''; + } else { + if (!$alternate) { + echo ''; + $alternate = true; + } else { + echo ''; + $alternate = false; + } + if ($nextrun = wp_next_scheduled('backwpup_cron', ['arg' => $jobid]) + (get_option('gmt_offset') * 3600)) { + echo ''; + } else { + echo ''; + } + + echo ''; + } + } + if (empty($mainsactive) and !empty($job_object)) { + echo ''; + } ?>
' . sprintf( '' . esc_html__( 'working since %d seconds', 'backwpup' ) . '', $runtime ) . '' . esc_html ( $job_object->job[ 'name' ] ) . '
'; - echo "" . esc_html__( 'Abort', 'backwpup' ) . ""; - echo "
' . sprintf( __( '%1$s at %2$s', 'backwpup' ), date_i18n( get_option( 'date_format' ), $nextrun, TRUE ), date_i18n( get_option( 'time_format' ), $nextrun, TRUE ) ) . '' . esc_html__( 'Not scheduled!', 'backwpup' ) . '' . esc_html($name) . '
' . esc_html__( 'none', 'backwpup' ) . '
' . sprintf('' . esc_html__('working since %d seconds', 'backwpup') . '', $runtime) . '' . esc_html($job_object->job['name']) . '
'; + echo '' . esc_html__('Abort', 'backwpup') . ''; + echo '
' . sprintf(__('%1$s at %2$s', 'backwpup'), date_i18n(get_option('date_format'), $nextrun, true), date_i18n(get_option('time_format'), $nextrun, true)) . '' . esc_html__('Not scheduled!', 'backwpup') . '' . esc_html($name) . '
' . esc_html__('none', 'backwpup') . '
+ } + + /** + * Displaying last logs. + */ + private static function mb_last_logs() + { + if (!current_user_can('backwpup_logs')) { + return; + } ?> - + - + isReadable() && $file->isFile() && strpos( $file->getFilename(), 'backwpup_log_' ) !== false && strpos( $file->getFilename(), '.html' ) !== false ) { - $logfiles[ $file->getMTime() ] = clone $file; - } - } - krsort( $logfiles, SORT_NUMERIC ); - } - catch ( UnexpectedValueException $e ) { - echo ''; - } - } - - if ( count( $logfiles ) > 0 ) { - $count = 0; - $alternate = TRUE; - foreach ( $logfiles as $logfile ) { - $logdata = BackWPup_Job::read_logheader( $logfile->getPathname() ); - if ( ! $alternate ) { - echo ''; - $alternate = TRUE; - } - else { - echo ''; - $alternate = FALSE; - } - echo ''; - $log_name = str_replace( array( '.html', '.gz' ), '', $logfile->getBasename() ); - echo ''; - echo ''; - $count ++; - if ( $count >= 5 ) - break; - } - } - else { - echo ''; - } - ?> + //get log files + $logfiles = []; + $log_folder = get_site_option('backwpup_cfg_logfolder'); + $log_folder = BackWPup_File::get_absolute_path($log_folder); + if (is_readable($log_folder)) { + try { + $dir = new BackWPup_Directory($log_folder); + + foreach ($dir as $file) { + if ($file->isReadable() && $file->isFile() && strpos($file->getFilename(), 'backwpup_log_') !== false && strpos($file->getFilename(), '.html') !== false) { + $logfiles[$file->getMTime()] = clone $file; + } + } + krsort($logfiles, SORT_NUMERIC); + } catch (UnexpectedValueException $e) { + echo ''; + } + } + + if (count($logfiles) > 0) { + $count = 0; + $alternate = true; + + foreach ($logfiles as $logfile) { + $logdata = BackWPup_Job::read_logheader($logfile->getPathname()); + if (!$alternate) { + echo ''; + $alternate = true; + } else { + echo ''; + $alternate = false; + } + echo ''; + $log_name = str_replace(['.html', '.gz'], '', $logfile->getBasename()); + echo ''; + echo ''; + ++$count; + if ($count >= 5) { + break; + } + } + } else { + echo ''; + } ?>
' . - sprintf( __( 'Could not open log folder: %s', 'backwpup' ), $log_folder ) . - '
' . sprintf( __( '%1$s at %2$s', 'backwpup' ), date_i18n( get_option( 'date_format' ) , $logdata[ 'logtime' ] ), date_i18n( get_option( 'time_format' ), $logdata[ 'logtime' ] ) ) . '' . esc_html( $logdata['name'] ) . ''; - if ( $logdata['errors'] ) { - printf( '' . _n( "%d ERROR", "%d ERRORS", $logdata['errors'], 'backwpup' ) . '
', $logdata[ 'errors' ] ); - } - if ( $logdata['warnings'] ) { - printf( '' . _n( "%d WARNING", "%d WARNINGS", $logdata['warnings'], 'backwpup' ) . '
', $logdata['warnings'] ); - } - if ( ! $logdata['errors'] && ! $logdata['warnings'] ) { - echo '' . __( 'OK', 'backwpup' ) . ''; - } - echo '
' . __( 'none', 'backwpup' ) . '
' . + sprintf(__('Could not open log folder: %s', 'backwpup'), $log_folder) . + '
' . sprintf(__('%1$s at %2$s', 'backwpup'), date_i18n(get_option('date_format'), $logdata['logtime']), date_i18n(get_option('time_format'), $logdata['logtime'])) . '' . esc_html($logdata['name']) . ''; + if ($logdata['errors']) { + printf('' . _n('%d ERROR', '%d ERRORS', $logdata['errors'], 'backwpup') . '
', $logdata['errors']); + } + if ($logdata['warnings']) { + printf('' . _n('%d WARNING', '%d WARNINGS', $logdata['warnings'], 'backwpup') . '
', $logdata['warnings']); + } + if (!$logdata['errors'] && !$logdata['warnings']) { + echo '' . __('OK', 'backwpup') . ''; + } + echo '
' . __('none', 'backwpup') . '
edit_auth( $jobid ); - } - } - - /** - * Save Form data - */ - public static function save_post_form($tab, $jobid) { - - if ( ! current_user_can( 'backwpup_jobs_edit' ) ) { - return __( 'Sorry, you don\'t have permissions to do that.', 'backwpup' ); - } - - $job_types = BackWPup::get_job_types(); - - switch ( $tab ) { - case 'job': - BackWPup_Option::update( $jobid, 'jobid', $jobid ); - - //set type of backup - $backuptype = 'archive'; - if ( class_exists( 'BackWPup_Pro', false ) && $_POST['backuptype'] === 'sync' ) { - $backuptype = 'sync'; - } - BackWPup_Option::update( $jobid, 'backuptype', $backuptype ); - - $type_post = isset( $_POST['type'] ) ? (array) $_POST['type'] : array(); - //check existing type - foreach ( $type_post as $key => $value ) { - if ( ! isset( $job_types[ $value ] ) ) { - unset( $type_post[ $key ] ); - } - } - sort( $type_post ); - BackWPup_Option::update( $jobid, 'type', $type_post ); - - //test if job type makes backup - $makes_file = false; - /* @var BackWPup_JobTypes $job_type */ - foreach ( $job_types as $type_id => $job_type ) { - if ( in_array( $type_id, $type_post, true ) ) { - if ( $job_type->creates_file() ) { - $makes_file = true; - break; - } - } - } - - if ( $makes_file ) { - $destinations_post = isset( $_POST['destinations'] ) ? (array) $_POST['destinations'] : array(); - } else { - $destinations_post = array(); - } - - $destinations = BackWPup::get_registered_destinations(); - foreach ( $destinations_post as $key => $dest_id ) { - //remove all destinations that not exists - if ( ! isset( $destinations[ $dest_id ] ) ) { - unset( $destinations_post[ $key ] ); - continue; - } - //if sync remove all not sync destinations - if ( $backuptype === 'sync' ) { - if ( ! $destinations[ $dest_id ]['can_sync'] ) { - unset( $destinations_post[ $key ] ); - } - } - } - sort( $destinations_post ); - BackWPup_Option::update( $jobid, 'destinations', $destinations_post ); - - $name = sanitize_text_field( trim( $_POST['name'] ) ); - if ( ! $name || $name === __( 'New Job', 'backwpup' ) ) { - $name = sprintf( __( 'Job with ID %d', 'backwpup' ), $jobid ); - } - BackWPup_Option::update( $jobid, 'name', $name ); - - $emails = explode( ',', sanitize_text_field( $_POST['mailaddresslog'] ) ); - foreach ( $emails as $key => $email ) { - $emails[ $key ] = sanitize_email( trim( $email ) ); - if ( ! is_email( $emails[ $key ] ) ) { - unset( $emails[ $key ] ); - } - } - $mailaddresslog = implode( ', ', $emails ); - BackWPup_Option::update( $jobid, 'mailaddresslog', $mailaddresslog ); - - $mailaddresssenderlog = trim( $_POST['mailaddresssenderlog'] ); - BackWPup_Option::update( $jobid, 'mailaddresssenderlog', $mailaddresssenderlog ); - - BackWPup_Option::update( $jobid, 'mailerroronly', ! empty( $_POST['mailerroronly'] ) ); - - $archiveformat = in_array( $_POST['archiveformat'], array( - '.zip', - '.tar', - '.tar.gz' - ), true ) ? $_POST['archiveformat'] : '.zip'; - BackWPup_Option::update( $jobid, 'archiveformat', $archiveformat ); - BackWPup_Option::update( $jobid, 'archiveencryption', ! empty( $_POST['archiveencryption'] ) ); - - BackWPup_Option::update( $jobid, 'archivename', BackWPup_Job::sanitize_file_name( BackWPup_Option::normalize_archive_name( $_POST['archivename'], $jobid, false ) ) ); - break; - case 'cron': - $activetype = in_array( $_POST['activetype'], array( - '', - 'wpcron', - 'easycron', - 'link' - ), true ) ? $_POST['activetype'] : ''; - BackWPup_Option::update( $jobid, 'activetype', $activetype ); - - $cronselect = $_POST['cronselect'] === 'advanced' ? 'advanced' : 'basic'; - BackWPup_Option::update( $jobid, 'cronselect', $cronselect ); - - //save advanced - if ( $cronselect === 'advanced' ) { - if ( empty( $_POST['cronminutes'] ) || $_POST['cronminutes'][0] === '*' ) { - if ( ! empty( $_POST['cronminutes'][1] ) ) { - $_POST['cronminutes'] = array( '*/' . $_POST['cronminutes'][1] ); - } else { - $_POST['cronminutes'] = array( '*' ); - } - } - if ( empty( $_POST['cronhours'] ) || $_POST['cronhours'][0] === '*' ) { - if ( ! empty( $_POST['cronhours'][1] ) ) { - $_POST['cronhours'] = array( '*/' . $_POST['cronhours'][1] ); - } else { - $_POST['cronhours'] = array( '*' ); - } - } - if ( empty( $_POST['cronmday'] ) || $_POST['cronmday'][0] === '*' ) { - if ( ! empty( $_POST['cronmday'][1] ) ) { - $_POST['cronmday'] = array( '*/' . $_POST['cronmday'][1] ); - } else { - $_POST['cronmday'] = array( '*' ); - } - } - if ( empty( $_POST['cronmon'] ) || $_POST['cronmon'][0] === '*' ) { - if ( ! empty( $_POST['cronmon'][1] ) ) { - $_POST['cronmon'] = array( '*/' . $_POST['cronmon'][1] ); - } else { - $_POST['cronmon'] = array( '*' ); - } - } - if ( empty( $_POST['cronwday'] ) || $_POST['cronwday'][0] === '*' ) { - if ( ! empty( $_POST['cronwday'][1] ) ) { - $_POST['cronwday'] = array( '*/' . $_POST['cronwday'][1] ); - } else { - $_POST['cronwday'] = array( '*' ); - } - } - $cron = implode( ",", $_POST['cronminutes'] ) . ' ' . implode( ",", $_POST['cronhours'] ) . ' ' . implode( ",", $_POST['cronmday'] ) . ' ' . implode( ",", $_POST['cronmon'] ) . ' ' . implode( ",", $_POST['cronwday'] ); - BackWPup_Option::update( $jobid, 'cron', $cron ); - } else { - //Save basic - if ( $_POST['cronbtype'] === 'mon' ) { - BackWPup_Option::update( $jobid, 'cron', absint( $_POST['moncronminutes'] ) . ' ' . absint( $_POST['moncronhours'] ) . ' ' . absint( $_POST['moncronmday'] ) . ' * *' ); - } - if ( $_POST['cronbtype'] === 'week' ) { - BackWPup_Option::update( $jobid, 'cron', absint( $_POST['weekcronminutes'] ) . ' ' . absint( $_POST['weekcronhours'] ) . ' * * ' . absint( $_POST['weekcronwday'] ) ); - } - if ( $_POST['cronbtype'] === 'day' ) { - BackWPup_Option::update( $jobid, 'cron', absint( $_POST['daycronminutes'] ) . ' ' . absint( $_POST['daycronhours'] ) . ' * * *' ); - } - if ( $_POST['cronbtype'] === 'hour' ) { - BackWPup_Option::update( $jobid, 'cron', absint( $_POST['hourcronminutes'] ) . ' * * * *' ); - } - } - //reschedule - $activetype = BackWPup_Option::get( $jobid, 'activetype' ); - wp_clear_scheduled_hook( 'backwpup_cron', array( 'arg' => $jobid ) ); - if ( $activetype === 'wpcron' ) { - $cron_next = BackWPup_Cron::cron_next( BackWPup_Option::get( $jobid, 'cron' ) ); - wp_schedule_single_event( $cron_next, 'backwpup_cron', array( 'arg' => $jobid ) ); - } - $easy_cron_job_id = BackWPup_Option::get( $jobid, 'easycronjobid' ); - if ( $activetype === 'easycron' ) { - BackWPup_EasyCron::update( $jobid ); - } elseif ( $easy_cron_job_id ) { - BackWPup_EasyCron::delete( $jobid ); - } - break; - default: - if ( strstr( $tab, 'dest-' ) ) { - $dest_class = BackWPup::get_destination( str_replace( 'dest-', '', $tab ) ); - $dest_class->edit_form_post_save( $jobid ); - } - if ( strstr( $tab, 'jobtype-' ) ) { - $id = strtoupper( str_replace( 'jobtype-', '', $tab ) ); - $job_types[ $id ]->edit_form_post_save( $jobid ); - } - } - - //saved message - $messages = BackWPup_Admin::get_messages(); - if ( empty( $messages['error'] ) ) { - $url = BackWPup_Job::get_jobrun_url( 'runnowlink', $jobid ); - BackWPup_Admin::message( sprintf( __( 'Changes for job %s saved.', 'backwpup' ), BackWPup_Option::get( $jobid, 'name' ) ) . ' ' . __( 'Jobs overview', 'backwpup' ) . ' | ' . __( 'Run now', 'backwpup' ) . '' ); - } - } - - /** - * - * Output css - * - * @return void - */ - public static function admin_print_styles() { - - ?> +class BackWPup_Page_Editjob +{ + public static function auth() + { + if (isset($_GET['tab'])) { + $_GET['tab'] = sanitize_title_with_dashes($_GET['tab']); + if (substr($_GET['tab'], 0, 5) != 'dest-' && substr($_GET['tab'], 0, 8) != 'jobtype-' && !in_array($_GET['tab'], ['job', 'cron'], true)) { + $_GET['tab'] = 'job'; + } + } else { + $_GET['tab'] = 'job'; + } + + if (substr($_GET['tab'], 0, 5) == 'dest-') { + $jobid = (int) $_GET['jobid']; + $id = strtoupper(str_replace('dest-', '', $_GET['tab'])); + $dest_class = BackWPup::get_destination($id); + $dest_class->edit_auth($jobid); + } + } + + /** + * Save Form data. + */ + public static function save_post_form($tab, $jobid) + { + if (!current_user_can('backwpup_jobs_edit')) { + return __('Sorry, you don\'t have permissions to do that.', 'backwpup'); + } + + $job_types = BackWPup::get_job_types(); + + switch ($tab) { + case 'job': + BackWPup_Option::update($jobid, 'jobid', $jobid); + + //set type of backup + $backuptype = 'archive'; + if (class_exists(\BackWPup_Pro::class, false) && $_POST['backuptype'] === 'sync') { + $backuptype = 'sync'; + } + BackWPup_Option::update($jobid, 'backuptype', $backuptype); + + $type_post = isset($_POST['type']) ? (array) $_POST['type'] : []; + //check existing type + foreach ($type_post as $key => $value) { + if (!isset($job_types[$value])) { + unset($type_post[$key]); + } + } + sort($type_post); + BackWPup_Option::update($jobid, 'type', $type_post); + + //test if job type makes backup + $makes_file = false; + + /** @var BackWPup_JobTypes $job_type */ + foreach ($job_types as $type_id => $job_type) { + if (in_array($type_id, $type_post, true)) { + if ($job_type->creates_file()) { + $makes_file = true; + break; + } + } + } + + if ($makes_file) { + $destinations_post = isset($_POST['destinations']) ? (array) $_POST['destinations'] : []; + } else { + $destinations_post = []; + } + + $destinations = BackWPup::get_registered_destinations(); + + foreach ($destinations_post as $key => $dest_id) { + //remove all destinations that not exists + if (!isset($destinations[$dest_id])) { + unset($destinations_post[$key]); + + continue; + } + //if sync remove all not sync destinations + if ($backuptype === 'sync') { + if (!$destinations[$dest_id]['can_sync']) { + unset($destinations_post[$key]); + } + } + } + sort($destinations_post); + BackWPup_Option::update($jobid, 'destinations', $destinations_post); + + $name = sanitize_text_field(trim($_POST['name'])); + if (!$name || $name === __('New Job', 'backwpup')) { + $name = sprintf(__('Job with ID %d', 'backwpup'), $jobid); + } + BackWPup_Option::update($jobid, 'name', $name); + + $emails = explode(',', sanitize_text_field($_POST['mailaddresslog'])); + + foreach ($emails as $key => $email) { + $emails[$key] = sanitize_email(trim($email)); + if (!is_email($emails[$key])) { + unset($emails[$key]); + } + } + $mailaddresslog = implode(', ', $emails); + BackWPup_Option::update($jobid, 'mailaddresslog', $mailaddresslog); + + $mailaddresssenderlog = trim($_POST['mailaddresssenderlog']); + BackWPup_Option::update($jobid, 'mailaddresssenderlog', $mailaddresssenderlog); + + BackWPup_Option::update($jobid, 'mailerroronly', !empty($_POST['mailerroronly'])); + + $archiveformat = in_array($_POST['archiveformat'], [ + '.zip', + '.tar', + '.tar.gz', + ], true) ? $_POST['archiveformat'] : '.zip'; + BackWPup_Option::update($jobid, 'archiveformat', $archiveformat); + BackWPup_Option::update($jobid, 'archiveencryption', !empty($_POST['archiveencryption'])); + + BackWPup_Option::update($jobid, 'archivename', BackWPup_Job::sanitize_file_name(BackWPup_Option::normalize_archive_name($_POST['archivename'], $jobid, false))); + break; + + case 'cron': + $activetype = in_array($_POST['activetype'], [ + '', + 'wpcron', + 'easycron', + 'link', + ], true) ? $_POST['activetype'] : ''; + BackWPup_Option::update($jobid, 'activetype', $activetype); + + $cronselect = $_POST['cronselect'] === 'advanced' ? 'advanced' : 'basic'; + BackWPup_Option::update($jobid, 'cronselect', $cronselect); + + //save advanced + if ($cronselect === 'advanced') { + if (empty($_POST['cronminutes']) || $_POST['cronminutes'][0] === '*') { + if (!empty($_POST['cronminutes'][1])) { + $_POST['cronminutes'] = ['*/' . $_POST['cronminutes'][1]]; + } else { + $_POST['cronminutes'] = ['*']; + } + } + if (empty($_POST['cronhours']) || $_POST['cronhours'][0] === '*') { + if (!empty($_POST['cronhours'][1])) { + $_POST['cronhours'] = ['*/' . $_POST['cronhours'][1]]; + } else { + $_POST['cronhours'] = ['*']; + } + } + if (empty($_POST['cronmday']) || $_POST['cronmday'][0] === '*') { + if (!empty($_POST['cronmday'][1])) { + $_POST['cronmday'] = ['*/' . $_POST['cronmday'][1]]; + } else { + $_POST['cronmday'] = ['*']; + } + } + if (empty($_POST['cronmon']) || $_POST['cronmon'][0] === '*') { + if (!empty($_POST['cronmon'][1])) { + $_POST['cronmon'] = ['*/' . $_POST['cronmon'][1]]; + } else { + $_POST['cronmon'] = ['*']; + } + } + if (empty($_POST['cronwday']) || $_POST['cronwday'][0] === '*') { + if (!empty($_POST['cronwday'][1])) { + $_POST['cronwday'] = ['*/' . $_POST['cronwday'][1]]; + } else { + $_POST['cronwday'] = ['*']; + } + } + $cron = implode(',', $_POST['cronminutes']) . ' ' . implode(',', $_POST['cronhours']) . ' ' . implode(',', $_POST['cronmday']) . ' ' . implode(',', $_POST['cronmon']) . ' ' . implode(',', $_POST['cronwday']); + BackWPup_Option::update($jobid, 'cron', $cron); + } else { + //Save basic + if ($_POST['cronbtype'] === 'mon') { + BackWPup_Option::update($jobid, 'cron', absint($_POST['moncronminutes']) . ' ' . absint($_POST['moncronhours']) . ' ' . absint($_POST['moncronmday']) . ' * *'); + } + if ($_POST['cronbtype'] === 'week') { + BackWPup_Option::update($jobid, 'cron', absint($_POST['weekcronminutes']) . ' ' . absint($_POST['weekcronhours']) . ' * * ' . absint($_POST['weekcronwday'])); + } + if ($_POST['cronbtype'] === 'day') { + BackWPup_Option::update($jobid, 'cron', absint($_POST['daycronminutes']) . ' ' . absint($_POST['daycronhours']) . ' * * *'); + } + if ($_POST['cronbtype'] === 'hour') { + BackWPup_Option::update($jobid, 'cron', absint($_POST['hourcronminutes']) . ' * * * *'); + } + } + //reschedule + $activetype = BackWPup_Option::get($jobid, 'activetype'); + wp_clear_scheduled_hook('backwpup_cron', ['arg' => $jobid]); + if ($activetype === 'wpcron') { + $cron_next = BackWPup_Cron::cron_next(BackWPup_Option::get($jobid, 'cron')); + wp_schedule_single_event($cron_next, 'backwpup_cron', ['arg' => $jobid]); + } + $easy_cron_job_id = BackWPup_Option::get($jobid, 'easycronjobid'); + if ($activetype === 'easycron') { + BackWPup_EasyCron::update($jobid); + } elseif ($easy_cron_job_id) { + BackWPup_EasyCron::delete($jobid); + } + break; + + default: + if (strstr($tab, 'dest-')) { + $dest_class = BackWPup::get_destination(str_replace('dest-', '', $tab)); + $dest_class->edit_form_post_save($jobid); + } + if (strstr($tab, 'jobtype-')) { + $id = strtoupper(str_replace('jobtype-', '', $tab)); + $job_types[$id]->edit_form_post_save($jobid); + } + } + + //saved message + $messages = BackWPup_Admin::get_messages(); + if (empty($messages['error'])) { + $url = BackWPup_Job::get_jobrun_url('runnowlink', $jobid); + BackWPup_Admin::message(sprintf(__('Changes for job %s saved.', 'backwpup'), BackWPup_Option::get($jobid, 'name')) . ' ' . __('Jobs overview', 'backwpup') . ' | ' . __('Run now', 'backwpup') . ''); + } + } + + /** + * Output css. + */ + public static function admin_print_styles() + { + ?> admin_print_styles(); - } - elseif ( substr( $_GET[ 'tab' ], 0, 8 ) == 'jobtype-' ) { - $job_type = BackWPup::get_job_types(); - $id = strtoupper( str_replace( 'jobtype-', '', $_GET[ 'tab' ] ) ); - $job_type[ $id ]->admin_print_styles( ); - } - } - - /** - * - * Output js - * - * @return void - */ - public static function admin_print_scripts() { - - wp_enqueue_script( 'backwpupgeneral' ); - - //add js for the first tabs - if ( $_GET[ 'tab' ] == 'job' ) { - if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) { - wp_enqueue_script( 'backwpuptabjob', BackWPup::get_plugin_data( 'URL' ) . '/assets/js/page_edit_tab_job.js', array('jquery'), time(), TRUE ); - } else { - wp_enqueue_script( 'backwpuptabjob', BackWPup::get_plugin_data( 'URL' ) . '/assets/js/page_edit_tab_job.min.js', array('jquery'), BackWPup::get_plugin_data( 'Version' ), TRUE ); - } - } - elseif ( $_GET[ 'tab' ] == 'cron' ) { - if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) { - wp_enqueue_script( 'backwpuptabcron', BackWPup::get_plugin_data( 'URL' ) . '/assets/js/page_edit_tab_cron.js', array('jquery'), time(), TRUE ); - } else { - wp_enqueue_script( 'backwpuptabcron', BackWPup::get_plugin_data( 'URL' ) . '/assets/js/page_edit_tab_cron.min.js', array('jquery'), BackWPup::get_plugin_data( 'Version' ), TRUE ); - } - } - //add js for all other tabs - elseif ( strstr( $_GET[ 'tab' ], 'dest-' ) ) { - $dest_object = BackWPup::get_destination( str_replace( 'dest-', '', $_GET[ 'tab' ] ) ); - $dest_object->admin_print_scripts(); - } - elseif ( strstr( $_GET[ 'tab' ], 'jobtype-' ) ) { - $job_type = BackWPup::get_job_types(); - $id = strtoupper( str_replace( 'jobtype-', '', $_GET[ 'tab' ] ) ); - $job_type[ $id ]->admin_print_scripts( ); - } - } - - public static function page() { - - if ( ! empty( $_GET[ 'jobid' ] ) ) { - $jobid = (int)$_GET[ 'jobid' ]; - } - else { - //generate jobid if not exists - $jobid = BackWPup_Option::next_job_id(); - } - - $destinations = BackWPup::get_registered_destinations(); - $job_types = BackWPup::get_job_types(); - - // Is encryption disabled? - $disable_encryption = true; - if ( (get_site_option( 'backwpup_cfg_encryption' ) === 'symmetric' && get_site_option( 'backwpup_cfg_encryptionkey' )) - || (get_site_option( 'backwpup_cfg_encryption' ) === 'asymmetric' && get_site_option( 'backwpup_cfg_publickey' )) - ) { - $disable_encryption = false; - } - - $archive_format_option = BackWPup_Option::get( $jobid, 'archiveformat' ); - ?> + //add css for all other tabs + if (substr($_GET['tab'], 0, 5) == 'dest-') { + $dest_object = BackWPup::get_destination(str_replace('dest-', '', $_GET['tab'])); + $dest_object->admin_print_styles(); + } elseif (substr($_GET['tab'], 0, 8) == 'jobtype-') { + $job_type = BackWPup::get_job_types(); + $id = strtoupper(str_replace('jobtype-', '', $_GET['tab'])); + $job_type[$id]->admin_print_styles(); + } + } + + /** + * Output js. + */ + public static function admin_print_scripts() + { + wp_enqueue_script('backwpupgeneral'); + + //add js for the first tabs + if ($_GET['tab'] == 'job') { + if (defined('SCRIPT_DEBUG') && SCRIPT_DEBUG) { + wp_enqueue_script('backwpuptabjob', BackWPup::get_plugin_data('URL') . '/assets/js/page_edit_tab_job.js', ['jquery'], time(), true); + } else { + wp_enqueue_script('backwpuptabjob', BackWPup::get_plugin_data('URL') . '/assets/js/page_edit_tab_job.min.js', ['jquery'], BackWPup::get_plugin_data('Version'), true); + } + } elseif ($_GET['tab'] == 'cron') { + if (defined('SCRIPT_DEBUG') && SCRIPT_DEBUG) { + wp_enqueue_script('backwpuptabcron', BackWPup::get_plugin_data('URL') . '/assets/js/page_edit_tab_cron.js', ['jquery'], time(), true); + } else { + wp_enqueue_script('backwpuptabcron', BackWPup::get_plugin_data('URL') . '/assets/js/page_edit_tab_cron.min.js', ['jquery'], BackWPup::get_plugin_data('Version'), true); + } + } + //add js for all other tabs + elseif (strstr($_GET['tab'], 'dest-')) { + $dest_object = BackWPup::get_destination(str_replace('dest-', '', $_GET['tab'])); + $dest_object->admin_print_scripts(); + } elseif (strstr($_GET['tab'], 'jobtype-')) { + $job_type = BackWPup::get_job_types(); + $id = strtoupper(str_replace('jobtype-', '', $_GET['tab'])); + $job_type[$id]->admin_print_scripts(); + } + } + + public static function page() + { + if (!empty($_GET['jobid'])) { + $jobid = (int) $_GET['jobid']; + } else { + //generate jobid if not exists + $jobid = BackWPup_Option::next_job_id(); + } + + $destinations = BackWPup::get_registered_destinations(); + $job_types = BackWPup::get_job_types(); + + // Is encryption disabled? + $disable_encryption = true; + if ((get_site_option('backwpup_cfg_encryption') === 'symmetric' && get_site_option('backwpup_cfg_encryptionkey')) + || (get_site_option('backwpup_cfg_encryption') === 'asymmetric' && get_site_option('backwpup_cfg_publickey')) + ) { + $disable_encryption = false; + } + + $archive_format_option = BackWPup_Option::get($jobid, 'archiveformat'); ?>
' . sprintf( esc_html__( '%1$s › Job: %2$s', 'backwpup' ), BackWPup::get_plugin_data( 'name' ), '' . esc_html( BackWPup_Option::get( $jobid, 'name' ) ) . '' ). ''; - - //default tabs - $tabs = array( 'job' => array( 'name' => esc_html__( 'General', 'backwpup' ), 'display' => TRUE ), 'cron' => array( 'name' => __( 'Schedule', 'backwpup' ), 'display' => TRUE ) ); - //add jobtypes to tabs - $job_job_types = BackWPup_Option::get( $jobid, 'type' ); - foreach ( $job_types as $typeid => $typeclass ) { - $tabid = 'jobtype-' . strtolower( $typeid ); - $tabs[ $tabid ][ 'name' ] = $typeclass->info[ 'name' ]; - $tabs[ $tabid ][ 'display' ] = TRUE; - if ( ! in_array( $typeid, $job_job_types, true ) ) - $tabs[ $tabid ][ 'display' ] = FALSE; - - } - //add destinations to tabs - $jobdests = BackWPup_Option::get( $jobid, 'destinations' ); - foreach ( $destinations as $destid => $dest ) { - $tabid = 'dest-' . strtolower( $destid ); - $tabs[ $tabid ][ 'name' ] = sprintf( __( 'To: %s', 'backwpup' ), $dest[ 'info' ][ 'name' ] ); - $tabs[ $tabid ][ 'display' ] = TRUE; - if ( ! in_array( $destid, $jobdests, true ) ) - $tabs[ $tabid ][ 'display' ] = FALSE; - } - //display tabs - echo ''; - //display messages - BackWPup_Admin::display_messages(); - echo '
'; - echo ''; - echo ''; - echo ''; - echo ''; - echo ''; - echo ''; - wp_nonce_field( 'backwpupeditjob_page' ); - wp_nonce_field( 'backwpup_ajax_nonce', 'backwpupajaxnonce', FALSE ); - - switch ( $_GET[ 'tab' ] ) { - case 'job': - ?> + echo '

' . sprintf(esc_html__('%1$s › Job: %2$s', 'backwpup'), BackWPup::get_plugin_data('name'), '' . esc_html(BackWPup_Option::get($jobid, 'name')) . '') . '

'; + + //default tabs + $tabs = ['job' => ['name' => esc_html__('General', 'backwpup'), 'display' => true], 'cron' => ['name' => __('Schedule', 'backwpup'), 'display' => true]]; + //add jobtypes to tabs + $job_job_types = BackWPup_Option::get($jobid, 'type'); + + foreach ($job_types as $typeid => $typeclass) { + $tabid = 'jobtype-' . strtolower($typeid); + $tabs[$tabid]['name'] = $typeclass->info['name']; + $tabs[$tabid]['display'] = true; + if (!in_array($typeid, $job_job_types, true)) { + $tabs[$tabid]['display'] = false; + } + } + //add destinations to tabs + $jobdests = BackWPup_Option::get($jobid, 'destinations'); + + foreach ($destinations as $destid => $dest) { + $tabid = 'dest-' . strtolower($destid); + $tabs[$tabid]['name'] = sprintf(__('To: %s', 'backwpup'), $dest['info']['name']); + $tabs[$tabid]['display'] = true; + if (!in_array($destid, $jobdests, true)) { + $tabs[$tabid]['display'] = false; + } + } + //display tabs + echo ''; + //display messages + BackWPup_Admin::display_messages(); + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + wp_nonce_field('backwpupeditjob_page'); + wp_nonce_field('backwpup_ajax_nonce', 'backwpupajaxnonce', false); + + switch ($_GET['tab']) { + case 'job': + ?>
-

+

- +
- +
-

+

- +
- + $typeclass ) { - $addclass = ''; - if ( $typeclass->creates_file() ) { - $addclass .= ' filetype'; - } - echo '

'; - if ( ! empty( $typeclass->info[ 'help' ] ) ) { - echo '
' . esc_attr( $typeclass->info[ 'help' ] ) . ''; - } - echo '

'; - } - ?>
+ foreach ($job_types as $id => $typeclass) { + $addclass = ''; + if ($typeclass->creates_file()) { + $addclass .= ' filetype'; + } + echo '

'; + if (!empty($typeclass->info['help'])) { + echo '
' . esc_attr($typeclass->info['help']) . ''; + } + echo '

'; + } + ?>
-

+

- + - + - + - + - + - +
- +

@@ -433,387 +433,401 @@ public static function page() {
- -

Note: In order for backup file tracking to work, %hash% must be included anywhere in the archive name.', 'backwpup' ) ?>

+ +

Note: In order for backup file tracking to work, %hash% must be included anywhere in the archive name.', 'backwpup'); ?>

' . esc_html__( 'Preview: ', 'backwpup' ) . '' . esc_attr( $archivename ) . '' . esc_attr( $archive_format_option ) . '

'; - echo '

'; - echo "" . esc_attr__( 'Replacement patterns:', 'backwpup' ) . "
"; - echo esc_attr__( '%d = Two digit day of the month, with leading zeros', 'backwpup' ) . '
'; - echo esc_attr__( '%j = Day of the month, without leading zeros', 'backwpup' ) . '
'; - echo esc_attr__( '%m = Two-digit representation of the month, with leading zeros', 'backwpup' ) . '
'; - echo esc_attr__( '%n = Representation of the month (without leading zeros)', 'backwpup' ) . '
'; - echo esc_attr__( '%Y = Four digit representation of the year', 'backwpup' ) . '
'; - echo esc_attr__( '%y = Two digit representation of the year', 'backwpup' ) . '
'; - echo esc_attr__( '%a = Lowercase ante meridiem (am) and post meridiem (pm)', 'backwpup' ) . '
'; - echo esc_attr__( '%A = Uppercase ante meridiem (AM) and post meridiem (PM)', 'backwpup' ) . '
'; - echo esc_attr__( '%B = Swatch Internet Time', 'backwpup' ) . '
'; - echo esc_attr__( '%g = Hour in 12-hour format, without leading zeros', 'backwpup' ) . '
'; - echo esc_attr__( '%G = Hour in 24-hour format, without leading zeros', 'backwpup' ) . '
'; - echo esc_attr__( '%h = Two-digit hour in 12-hour format, with leading zeros', 'backwpup' ) . '
'; - echo esc_attr__( '%H = Two-digit hour in 24-hour format, with leading zeros', 'backwpup' ) . '
'; - echo esc_attr__( '%i = Two digit representation of the minute', 'backwpup' ) . '
'; - echo esc_attr__( '%s = Two digit representation of the second', 'backwpup' ) . '
'; - echo '

'; - ?> + $archivename = BackWPup_Option::substitute_date_vars( + BackWPup_Option::get($jobid, 'archivenamenohash') + ); + echo '

' . esc_html__('Preview: ', 'backwpup') . '' . esc_attr($archivename) . '' . esc_attr($archive_format_option) . '

'; + echo '

'; + echo '' . esc_attr__('Replacement patterns:', 'backwpup') . '
'; + echo esc_attr__('%d = Two digit day of the month, with leading zeros', 'backwpup') . '
'; + echo esc_attr__('%j = Day of the month, without leading zeros', 'backwpup') . '
'; + echo esc_attr__('%m = Two-digit representation of the month, with leading zeros', 'backwpup') . '
'; + echo esc_attr__('%n = Representation of the month (without leading zeros)', 'backwpup') . '
'; + echo esc_attr__('%Y = Four digit representation of the year', 'backwpup') . '
'; + echo esc_attr__('%y = Two digit representation of the year', 'backwpup') . '
'; + echo esc_attr__('%a = Lowercase ante meridiem (am) and post meridiem (pm)', 'backwpup') . '
'; + echo esc_attr__('%A = Uppercase ante meridiem (AM) and post meridiem (PM)', 'backwpup') . '
'; + echo esc_attr__('%B = Swatch Internet Time', 'backwpup') . '
'; + echo esc_attr__('%g = Hour in 12-hour format, without leading zeros', 'backwpup') . '
'; + echo esc_attr__('%G = Hour in 24-hour format, without leading zeros', 'backwpup') . '
'; + echo esc_attr__('%h = Two-digit hour in 12-hour format, with leading zeros', 'backwpup') . '
'; + echo esc_attr__('%H = Two-digit hour in 24-hour format, with leading zeros', 'backwpup') . '
'; + echo esc_attr__('%i = Two digit representation of the minute', 'backwpup') . '
'; + echo esc_attr__('%s = Two digit representation of the second', 'backwpup') . '
'; + echo '

'; + ?>
- +

'; - } else { - echo '

'; - echo '
' . esc_html(__( 'ZipArchive PHP class is missing, so BackWPUp will use PclZip instead.', 'backwpup' )) . '

'; - } - echo '

'; - if ( function_exists( 'gzopen' ) ) { - echo '

'; - } else { - echo '

'; - echo '
' . esc_html(sprintf( __( 'Disabled due to missing %s PHP function.', 'backwpup' ), 'gzopen()' )) . '

'; - } - ?> + if (class_exists(\ZipArchive::class)) { + echo '

'; + } else { + echo '

'; + echo '
' . esc_html(__('ZipArchive PHP class is missing, so BackWPUp will use PclZip instead.', 'backwpup')) . '

'; + } + echo '

'; + if (function_exists('gzopen')) { + echo '

'; + } else { + echo '

'; + echo '
' . esc_html(sprintf(__('Disabled due to missing %s PHP function.', 'backwpup'), 'gzopen()')) . '

'; + } + ?>
- +
- + + ?> - +

- +

- +
-

+

- +
- + $dest ) { - $syncclass = ''; - if ( ! $dest[ 'can_sync' ] ) { - $syncclass = 'nosync'; - } - echo '

'; - } - ?>
+ foreach ($destinations as $id => $dest) { + $syncclass = ''; + if (!$dest['can_sync']) { + $syncclass = 'nosync'; + } + echo '

'; + } + ?>
-

+

- + - + - +
- -

+ +

- +
+ break; + + case 'cron': + ?>
-

+

- + - +
- +
+ value="" />

+ value="wpcron" />
-
WP-CLI to run jobs from commandline.', 'backwpup' ); - ?> + _e('Use WP-CLI to run jobs from commandline.', 'backwpup'); + ?>
-

- BackWPup_Option::get( $jobid, 'cron' ), 'crontype' => BackWPup_Option::get( $jobid, 'cronselect' ) ) ); ?> +

+ BackWPup_Option::get($jobid, 'cron'), 'crontype' => BackWPup_Option::get($jobid, 'cronselect')]); ?> - + - > - + $cronstr = []; + [$cronstr['minutes'], $cronstr['hours'], $cronstr['mday'], $cronstr['mon'], $cronstr['wday']] = explode(' ', BackWPup_Option::get($jobid, 'cron'), 5); + if (strstr($cronstr['minutes'], '*/')) { + $minutes = explode('/', $cronstr['minutes']); + } else { + $minutes = explode(',', $cronstr['minutes']); + } + if (strstr($cronstr['hours'], '*/')) { + $hours = explode('/', $cronstr['hours']); + } else { + $hours = explode(',', $cronstr['hours']); + } + if (strstr($cronstr['mday'], '*/')) { + $mday = explode('/', $cronstr['mday']); + } else { + $mday = explode(',', $cronstr['mday']); + } + if (strstr($cronstr['mon'], '*/')) { + $mon = explode('/', $cronstr['mon']); + } else { + $mon = explode(',', $cronstr['mon']); + } + if (strstr($cronstr['wday'], '*/')) { + $wday = explode('/', $cronstr['wday']); + } else { + $wday = explode(',', $cronstr['wday']); + } + ?> + > + - > - + > +
- +
+ value="basic" />

+ value="advanced" />
'; - if ( strstr( $_GET[ 'tab' ], 'dest-' ) ) { - $dest_object = BackWPup::get_destination( str_replace( 'dest-', '', $_GET[ 'tab' ] ) ); - $dest_object->edit_tab( $jobid ); - } - if ( strstr( $_GET[ 'tab' ], 'jobtype-' ) ) { - $id = strtoupper( str_replace( 'jobtype-', '', $_GET[ 'tab' ] ) ); - $job_types[ $id ]->edit_tab( $jobid ); - } - echo '
'; - } - echo '

'; - submit_button( __( 'Save changes', 'backwpup' ), 'primary', 'save', FALSE, array( 'tabindex' => '2', 'accesskey' => 'p' ) ); - echo '

'; - ?> + break; + + default: + echo '
'; + if (strstr($_GET['tab'], 'dest-')) { + $dest_object = BackWPup::get_destination(str_replace('dest-', '', $_GET['tab'])); + $dest_object->edit_tab($jobid); + } + if (strstr($_GET['tab'], 'jobtype-')) { + $id = strtoupper(str_replace('jobtype-', '', $_GET['tab'])); + $job_types[$id]->edit_tab($jobid); + } + echo '
'; + } + echo '

'; + submit_button(__('Save changes', 'backwpup'), 'primary', 'save', false, ['tabindex' => '2', 'accesskey' => 'p']); + echo '

'; ?>
edit_inline_js(); - } - if ( strstr( $_GET[ 'tab' ], 'jobtype-' ) ) { - $id = strtoupper( str_replace( 'jobtype-', '', sanitize_text_field( $_GET[ 'tab' ] ) ) ); - $job_types[ $id ]->edit_inline_js(); - } - - } - - /** - * @static - * - * @param string $args - * - * @return mixed - */ - public static function ajax_cron_text( $args = '' ) { - - if ( is_array( $args ) ) { - extract( $args ); - $ajax = FALSE; - } else { - if ( ! current_user_can( 'backwpup_jobs_edit' ) ) - wp_die( -1 ); - check_ajax_referer( 'backwpup_ajax_nonce' ); - if ( empty( $_POST[ 'cronminutes' ] ) || $_POST[ 'cronminutes' ][ 0 ] == '*' ) { - if ( ! empty( $_POST[ 'cronminutes' ][ 1 ] ) ) - $_POST[ 'cronminutes' ] = array( '*/' . $_POST[ 'cronminutes' ][ 1 ] ); - else - $_POST[ 'cronminutes' ] = array( '*' ); - } - if ( empty( $_POST[ 'cronhours' ] ) || $_POST[ 'cronhours' ][ 0 ] == '*' ) { - if ( ! empty( $_POST[ 'cronhours' ][ 1 ] ) ) - $_POST[ 'cronhours' ] = array( '*/' . $_POST[ 'cronhours' ][ 1 ] ); - else - $_POST[ 'cronhours' ] = array( '*' ); - } - if ( empty( $_POST[ 'cronmday' ] ) || $_POST[ 'cronmday' ][ 0 ] == '*' ) { - if ( ! empty( $_POST[ 'cronmday' ][ 1 ] ) ) - $_POST[ 'cronmday' ] = array( '*/' . $_POST[ 'cronmday' ][ 1 ] ); - else - $_POST[ 'cronmday' ] = array( '*' ); - } - if ( empty( $_POST[ 'cronmon' ] ) || $_POST[ 'cronmon' ][ 0 ] == '*' ) { - if ( ! empty( $_POST[ 'cronmon' ][ 1 ] ) ) - $_POST[ 'cronmon' ] = array( '*/' . $_POST[ 'cronmon' ][ 1 ] ); - else - $_POST[ 'cronmon' ] = array( '*' ); - } - if ( empty( $_POST[ 'cronwday' ] ) || $_POST[ 'cronwday' ][ 0 ] == '*' ) { - if ( ! empty( $_POST[ 'cronwday' ][ 1 ] ) ) - $_POST[ 'cronwday' ] = array( '*/' . $_POST[ 'cronwday' ][ 1 ] ); - else - $_POST[ 'cronwday' ] = array( '*' ); - } - $crontype = $_POST[ 'crontype' ]; - $cronstamp = implode( ",", $_POST[ 'cronminutes' ] ) . ' ' . implode( ",", $_POST[ 'cronhours' ] ) . ' ' . implode( ",", $_POST[ 'cronmday' ] ) . ' ' . implode( ",", $_POST[ 'cronmon' ] ) . ' ' . implode( ",", $_POST[ 'cronwday' ] ); - $ajax = TRUE; - } - echo '

'; - - if ( $crontype == 'advanced' ) { - echo str_replace( '\"','"', __( 'Working as Cron schedule:', 'backwpup' ) ); - echo ' ' . esc_attr( $cronstamp ). '
'; - } - - list( $cronstr[ 'minutes' ], $cronstr[ 'hours' ], $cronstr[ 'mday' ], $cronstr[ 'mon' ], $cronstr[ 'wday' ] ) = explode( ' ', $cronstamp, 5 ); - if ( FALSE !== strpos( $cronstr[ 'minutes' ], '*/' ) || $cronstr[ 'minutes' ] == '*' ) { - $repeatmins = str_replace( '*/', '', $cronstr[ 'minutes' ] ); - if ( $repeatmins == '*' || empty( $repeatmins ) ) - $repeatmins = 5; - echo '' . sprintf( __( 'ATTENTION: Job runs every %d minutes!', 'backwpup' ), $repeatmins ) . '
'; - } - $cron_next = BackWPup_Cron::cron_next( $cronstamp ) + ( get_option( 'gmt_offset' ) * 3600 ); - if ( PHP_INT_MAX === $cron_next ) { - echo '' . __( 'ATTENTION: Can\'t calculate cron!', 'backwpup' ) . '
'; - } - else { - _e( 'Next runtime:', 'backwpup' ); - echo ' ' . date_i18n( 'D, j M Y, H:i', $cron_next, TRUE ) . ''; - } - echo "

"; - - if ( $ajax ) - die(); - else - return; - } + //add inline js + if (strstr($_GET['tab'], 'dest-')) { + $dest_object = BackWPup::get_destination(str_replace('dest-', '', sanitize_text_field($_GET['tab']))); + $dest_object->edit_inline_js(); + } + if (strstr($_GET['tab'], 'jobtype-')) { + $id = strtoupper(str_replace('jobtype-', '', sanitize_text_field($_GET['tab']))); + $job_types[$id]->edit_inline_js(); + } + } + + /** + * @static + * + * @param string $args + * + * @return mixed + */ + public static function ajax_cron_text($args = '') + { + if (is_array($args)) { + extract($args); + $ajax = false; + } else { + if (!current_user_can('backwpup_jobs_edit')) { + wp_die(-1); + } + check_ajax_referer('backwpup_ajax_nonce'); + if (empty($_POST['cronminutes']) || $_POST['cronminutes'][0] == '*') { + if (!empty($_POST['cronminutes'][1])) { + $_POST['cronminutes'] = ['*/' . $_POST['cronminutes'][1]]; + } else { + $_POST['cronminutes'] = ['*']; + } + } + if (empty($_POST['cronhours']) || $_POST['cronhours'][0] == '*') { + if (!empty($_POST['cronhours'][1])) { + $_POST['cronhours'] = ['*/' . $_POST['cronhours'][1]]; + } else { + $_POST['cronhours'] = ['*']; + } + } + if (empty($_POST['cronmday']) || $_POST['cronmday'][0] == '*') { + if (!empty($_POST['cronmday'][1])) { + $_POST['cronmday'] = ['*/' . $_POST['cronmday'][1]]; + } else { + $_POST['cronmday'] = ['*']; + } + } + if (empty($_POST['cronmon']) || $_POST['cronmon'][0] == '*') { + if (!empty($_POST['cronmon'][1])) { + $_POST['cronmon'] = ['*/' . $_POST['cronmon'][1]]; + } else { + $_POST['cronmon'] = ['*']; + } + } + if (empty($_POST['cronwday']) || $_POST['cronwday'][0] == '*') { + if (!empty($_POST['cronwday'][1])) { + $_POST['cronwday'] = ['*/' . $_POST['cronwday'][1]]; + } else { + $_POST['cronwday'] = ['*']; + } + } + $crontype = $_POST['crontype']; + $cronstamp = implode(',', $_POST['cronminutes']) . ' ' . implode(',', $_POST['cronhours']) . ' ' . implode(',', $_POST['cronmday']) . ' ' . implode(',', $_POST['cronmon']) . ' ' . implode(',', $_POST['cronwday']); + $ajax = true; + } + echo '

'; + + if ($crontype == 'advanced') { + echo str_replace('\"', '"', __('Working as Cron schedule:', 'backwpup')); + echo ' ' . esc_attr($cronstamp) . '
'; + } + + $cronstr = []; + [$cronstr['minutes'], $cronstr['hours'], $cronstr['mday'], $cronstr['mon'], $cronstr['wday']] = explode(' ', $cronstamp, 5); + if (false !== strpos($cronstr['minutes'], '*/') || $cronstr['minutes'] == '*') { + $repeatmins = str_replace('*/', '', $cronstr['minutes']); + if ($repeatmins == '*' || empty($repeatmins)) { + $repeatmins = 5; + } + echo '' . sprintf(__('ATTENTION: Job runs every %d minutes!', 'backwpup'), $repeatmins) . '
'; + } + $cron_next = BackWPup_Cron::cron_next($cronstamp) + (get_option('gmt_offset') * 3600); + if (PHP_INT_MAX === $cron_next) { + echo '' . __('ATTENTION: Can\'t calculate cron!', 'backwpup') . '
'; + } else { + _e('Next runtime:', 'backwpup'); + echo ' ' . date_i18n('D, j M Y, H:i', $cron_next, true) . ''; + } + echo '

'; + + if ($ajax) { + exit(); + } + } } - diff --git a/inc/class-page-jobs.php b/inc/class-page-jobs.php index 6dabdf3d..9ed7d0c4 100644 --- a/inc/class-page-jobs.php +++ b/inc/class-page-jobs.php @@ -1,471 +1,493 @@ 'jobs', - 'singular' => 'job', - 'ajax' => TRUE - ) ); - } - - /** - * @return bool|void - */ - public function ajax_user_can() { - - return current_user_can( 'backwpup' ); - } - - public function prepare_items() { - - $this->items = BackWPup_Option::get_job_ids(); - $this->job_object = BackWPup_Job::get_working_data(); - $this->job_types = BackWPup::get_job_types(); - $this->destinations = BackWPup::get_registered_destinations(); - - if ( ! isset( $_GET[ 'order' ] ) || ! isset( $_GET[ 'orderby' ] ) ) { - return; - } - - if ( strtolower( $_GET[ 'order' ] ) === 'asc' ) { - $order = SORT_ASC; - } else { - $order = SORT_DESC; - } - - if ( empty( $_GET[ 'orderby' ] ) || ! in_array( strtolower( $_GET[ 'orderby' ] ), array( 'jobname', 'type', 'dest', 'next', 'last' ), true ) ) { - $orderby = 'jobname'; - } else { - $orderby = strtolower( $_GET[ 'orderby' ] ); - } - - //sorting - $job_configs = array(); - $i = 0; - foreach( $this->items as $item ) { - $job_configs[ $i ][ 'jobid' ] = $item; - $job_configs[ $i ][ 'jobname' ] = BackWPup_Option::get( $item, 'name' ); - $job_configs[ $i ][ 'type' ] = BackWPup_Option::get( $item, 'type' ); - $job_configs[ $i ][ 'dest' ] = BackWPup_Option::get( $item, 'destinations' ); - if ( $order === SORT_ASC ) { - sort( $job_configs[ $i ][ 'type' ] ); - sort( $job_configs[ $i ][ 'dest' ] ); - } else { - rsort( $job_configs[ $i ][ 'type' ] ); - rsort( $job_configs[ $i ][ 'dest' ] ); - } - $job_configs[ $i ][ 'type' ] = array_shift( $job_configs[ $i ][ 'type' ] ); - $job_configs[ $i ][ 'dest' ] = array_shift( $job_configs[ $i ][ 'dest' ] ); - $job_configs[ $i ][ 'next' ] = (int) wp_next_scheduled( 'backwpup_cron', array( 'arg' => $item ) ); - $job_configs[ $i ][ 'last' ] = BackWPup_Option::get( $item, 'lastrun' ); - $i++; - } - - $tmp = array(); - foreach ( $job_configs as &$ma ) { - $tmp[] = &$ma[ $orderby ]; - } - array_multisort( $tmp, $order, $job_configs ); - - $this->items = array(); - foreach( $job_configs as $item ) { - $this->items[] = $item[ 'jobid' ]; - } - - } - - public function no_items() { - - _e( 'No Jobs.', 'backwpup' ); - } - - /** - * @return array - */ - public function get_bulk_actions() { - - if ( ! $this->has_items() ) { - return array(); - } - - $actions = array(); - $actions[ 'delete' ] = __( 'Delete', 'backwpup' ); - - return apply_filters( 'backwpup_page_jobs_get_bulk_actions', $actions ); - } - - /** - * @return array - */ - public function get_columns() { - - $jobs_columns = array(); - $jobs_columns[ 'cb' ] = ''; - $jobs_columns[ 'jobname' ] = __( 'Job Name', 'backwpup' ); - $jobs_columns[ 'type' ] = __( 'Type', 'backwpup' ); - $jobs_columns[ 'dest' ] = __( 'Destinations', 'backwpup' ); - $jobs_columns[ 'next' ] = __( 'Next Run', 'backwpup' ); - $jobs_columns[ 'last' ] = __( 'Last Run', 'backwpup' ); - - return $jobs_columns; - } - - /** - * @return array - */ - public function get_sortable_columns() { - - return array( - 'jobname' => 'jobname', - 'type' => 'type', - 'dest' => 'dest', - 'next' => 'next', - 'last' => 'last', - ); - } - - /** - * The cb Column - * - * @param $item - * @return string - */ - public function column_cb( $item ) { - - return ''; - } - - /** - * The jobname Column - * - * @param $item - * @return string - */ - public function column_jobname( $item ) { - - $job_normal_hide =''; - if ( is_object( $this->job_object ) ) { - $job_normal_hide = ' style="display:none;"'; - } - - $r = '' . esc_html( BackWPup_Option::get( $item, 'name' ) ) . ''; - $actions = array(); - if ( current_user_can( 'backwpup_jobs_edit' ) ) { - $actions[ 'edit' ] = "" . esc_html__( 'Edit', 'backwpup' ) . ""; - $actions[ 'copy' ] = "" . esc_html__( 'Copy', 'backwpup' ) . ""; - $actions[ 'delete' ] = "" . esc_html__( 'Delete', 'backwpup' ) . ""; - } - if ( current_user_can( 'backwpup_jobs_start' ) ) { - $url = BackWPup_Job::get_jobrun_url( 'runnowlink', $item ); - $actions[ 'runnow' ] = "" . esc_html__( 'Run now', 'backwpup' ) . ""; - } - if ( current_user_can( 'backwpup_logs' ) && BackWPup_Option::get( $item, 'logfile' ) ) { - $logfile = basename( BackWPup_Option::get( $item, 'logfile' ) ); - if ( is_object( $this->job_object ) && $this->job_object->job[ 'jobid' ] == $item ) { - $logfile = basename( $this->job_object->logfile ); - } - $log_name = str_replace( array( '.html', '.gz' ), '', basename( $logfile ) ); - $actions[ 'lastlog' ] = '' . __( 'Last log', 'backwpup' ) . ''; - } - $actions = apply_filters( 'backwpup_page_jobs_actions', $actions, $item, FALSE ); - $r .= '
' . $this->row_actions( $actions ) . '
'; - if ( is_object( $this->job_object ) ) { - $actionsrun = array(); - $actionsrun = apply_filters( 'backwpup_page_jobs_actions', $actionsrun, $item, TRUE ); - $r .= '
' . $this->row_actions( $actionsrun ) . '
'; - } - - return $r; - } - - /** - * The type Column - * - * @param $item - * @return string - */ - public function column_type( $item ) { - - $r = ''; - if ( $types = BackWPup_Option::get( $item, 'type' ) ) { - foreach ( $types as $type ) { - if ( isset( $this->job_types[ $type ] ) ) { - $r .= $this->job_types[ $type ]->info[ 'name' ] . '
'; - } - else { - $r .= $type . '
'; - } - } - } - - return $r; - } - - /** - * The destination Column - * - * @param $item - * @return string - */ - public function column_dest( $item ) { - - $r = ''; - $backup_to = FALSE; - foreach ( BackWPup_Option::get( $item, 'type' ) as $typeid ) { - if ( isset( $this->job_types[ $typeid ] ) && $this->job_types[ $typeid ]->creates_file() ) { - $backup_to = TRUE; - break; - } - } - if ( $backup_to ) { - foreach ( BackWPup_Option::get( $item, 'destinations' ) as $destid ) { - if ( isset( $this->destinations[ $destid ][ 'info' ][ 'name' ] ) ) { - $r .= $this->destinations[ $destid ][ 'info' ][ 'name' ] . '
'; - } else { - $r .= $destid . '
'; - } - } - } - else { - $r .= '' . __( 'Not needed or set', 'backwpup' ) . '
'; - } - - return $r; - } - - /** - * The next Column - * - * @param $item - * @return string - */ - public function column_next( $item ) { - - $r = ''; - - $job_normal_hide =''; - if ( is_object( $this->job_object ) ) { - $job_normal_hide = ' style="display:none;"'; - } - if ( is_object( $this->job_object ) && $this->job_object->job[ 'jobid' ] == $item ) { - $runtime = current_time( 'timestamp' ) - $this->job_object->start_time; - $r .= '
' . sprintf( esc_html__( 'Running for: %s seconds', 'backwpup' ), '' . $runtime . '' ) .'
'; - } - if ( is_object( $this->job_object ) && $this->job_object->job[ 'jobid' ] == $item ) { - $r .='
'; - } - if ( BackWPup_Option::get( $item, 'activetype' ) == 'wpcron' ) { - if ( $nextrun = wp_next_scheduled( 'backwpup_cron', array( 'arg' => $item ) ) + ( get_option( 'gmt_offset' ) * 3600 ) ) { - $r .= '' . sprintf( __( '%1$s at %2$s by WP-Cron', 'backwpup' ) , date_i18n( get_option( 'date_format' ), $nextrun, TRUE ) , date_i18n( get_option( 'time_format' ), $nextrun, TRUE ) ) . '
'; - } else { - $r .= __( 'Not scheduled!', 'backwpup' ) . '
'; - } - } - elseif ( BackWPup_Option::get( $item, 'activetype' ) == 'easycron' ) { - $easycron_status = BackWPup_EasyCron::status( $item ); - if ( !empty( $easycron_status ) ) { - $nextrun = BackWPup_Cron::cron_next( $easycron_status[ 'cron_expression' ] ) + ( get_option( 'gmt_offset' ) * 3600 ); - $r .= '' . sprintf( __( '%1$s at %2$s by EasyCron', 'backwpup' ) , date_i18n( get_option( 'date_format' ), $nextrun, TRUE ) , date_i18n( get_option( 'time_format' ), $nextrun, TRUE ) ) . '
'; - } else { - $r .= __( 'Not scheduled!', 'backwpup' ) . '
'; - } - } - elseif ( BackWPup_Option::get( $item, 'activetype' ) == 'link' ) { - $r .= __( 'External link', 'backwpup' ) . '
'; - } - else { - $r .= __( 'Inactive', 'backwpup' ); - } - if ( is_object( $this->job_object ) && $this->job_object->job[ 'jobid' ] == $item ) { - $r .= '
'; - } - return $r; - } - - /** - * The last Column - * - * @param $item - * @return string - */ - public function column_last( $item ) { - - $r = ''; - - if ( BackWPup_Option::get( $item, 'lastrun' ) ) { - $lastrun = BackWPup_Option::get( $item, 'lastrun' ); - $r .= sprintf( __( '%1$s at %2$s', 'backwpup' ), date_i18n( get_option( 'date_format' ), $lastrun, TRUE ), date_i18n( get_option( 'time_format' ), $lastrun, TRUE ) ); - if ( BackWPup_Option::get( $item, 'lastruntime' ) ) { - $r .= '
' . sprintf( __( 'Runtime: %d seconds', 'backwpup' ), BackWPup_Option::get( $item, 'lastruntime' ) ); - } - } - else { - $r .= __( 'not yet', 'backwpup' ); - } - $r .= "
"; - if ( current_user_can( 'backwpup_backups_download' ) ) { - $download_url = BackWPup_Option::get( $item, 'lastbackupdownloadurl' ); - if ( ! empty( $download_url ) ) { - $r .= "" . esc_html__( 'Download', 'backwpup' ) . " | "; - } - } - if ( current_user_can( 'backwpup_logs' ) && BackWPup_Option::get( $item, 'logfile' ) ) { - $logfile = basename( BackWPup_Option::get( $item, 'logfile' ) ); - if ( is_object( $this->job_object ) && $this->job_object->job[ 'jobid' ] == $item ) { - $logfile = basename( $this->job_object->logfile ); - } - $log_name = str_replace( array( '.html', '.gz' ), '', basename( $logfile ) ); - $r .= '' . esc_html__( 'Log', 'backwpup' ) . ''; - - } - $r .= ""; - - return $r; - } - - public static function load() { - - //Create Table - self::$listtable = new self; +class BackWPup_Page_Jobs extends WP_List_Table +{ + public static $logfile; + + private static $listtable; + private $job_object; + private $job_types; + private $destinations; + + public function __construct() + { + parent::__construct([ + 'plural' => 'jobs', + 'singular' => 'job', + 'ajax' => true, + ]); + } + + /** + * @return bool|void + */ + public function ajax_user_can() + { + return current_user_can('backwpup'); + } + + public function prepare_items() + { + $this->items = BackWPup_Option::get_job_ids(); + $this->job_object = BackWPup_Job::get_working_data(); + $this->job_types = BackWPup::get_job_types(); + $this->destinations = BackWPup::get_registered_destinations(); + + if (!isset($_GET['order']) || !isset($_GET['orderby'])) { + return; + } + + if (strtolower($_GET['order']) === 'asc') { + $order = SORT_ASC; + } else { + $order = SORT_DESC; + } + + if (empty($_GET['orderby']) || !in_array(strtolower($_GET['orderby']), ['jobname', 'type', 'dest', 'next', 'last'], true)) { + $orderby = 'jobname'; + } else { + $orderby = strtolower($_GET['orderby']); + } + + //sorting + $job_configs = []; + $i = 0; + + foreach ($this->items as $item) { + $job_configs[$i]['jobid'] = $item; + $job_configs[$i]['jobname'] = BackWPup_Option::get($item, 'name'); + $job_configs[$i]['type'] = BackWPup_Option::get($item, 'type'); + $job_configs[$i]['dest'] = BackWPup_Option::get($item, 'destinations'); + if ($order === SORT_ASC) { + sort($job_configs[$i]['type']); + sort($job_configs[$i]['dest']); + } else { + rsort($job_configs[$i]['type']); + rsort($job_configs[$i]['dest']); + } + $job_configs[$i]['type'] = array_shift($job_configs[$i]['type']); + $job_configs[$i]['dest'] = array_shift($job_configs[$i]['dest']); + $job_configs[$i]['next'] = (int) wp_next_scheduled('backwpup_cron', ['arg' => $item]); + $job_configs[$i]['last'] = BackWPup_Option::get($item, 'lastrun'); + ++$i; + } + + $tmp = []; + + foreach ($job_configs as &$ma) { + $tmp[] = &$ma[$orderby]; + } + array_multisort($tmp, $order, $job_configs); + + $this->items = []; + + foreach ($job_configs as $item) { + $this->items[] = $item['jobid']; + } + } + + public function no_items() + { + _e('No Jobs.', 'backwpup'); + } + + /** + * @return array + */ + public function get_bulk_actions() + { + if (!$this->has_items()) { + return []; + } + + $actions = []; + $actions['delete'] = __('Delete', 'backwpup'); + + return apply_filters('backwpup_page_jobs_get_bulk_actions', $actions); + } + + /** + * @return array + */ + public function get_columns() + { + $jobs_columns = []; + $jobs_columns['cb'] = ''; + $jobs_columns['jobname'] = __('Job Name', 'backwpup'); + $jobs_columns['type'] = __('Type', 'backwpup'); + $jobs_columns['dest'] = __('Destinations', 'backwpup'); + $jobs_columns['next'] = __('Next Run', 'backwpup'); + $jobs_columns['last'] = __('Last Run', 'backwpup'); + + return $jobs_columns; + } + + /** + * @return array + */ + public function get_sortable_columns() + { + return [ + 'jobname' => 'jobname', + 'type' => 'type', + 'dest' => 'dest', + 'next' => 'next', + 'last' => 'last', + ]; + } + + /** + * The cb Column. + * + * @param $item + * + * @return string + */ + public function column_cb($item) + { + return ''; + } + + /** + * The jobname Column. + * + * @param $item + * + * @return string + */ + public function column_jobname($item) + { + $job_normal_hide = ''; + if (is_object($this->job_object)) { + $job_normal_hide = ' style="display:none;"'; + } + + $r = '' . esc_html(BackWPup_Option::get($item, 'name')) . ''; + $actions = []; + if (current_user_can('backwpup_jobs_edit')) { + $actions['edit'] = '' . esc_html__('Edit', 'backwpup') . ''; + $actions['copy'] = '' . esc_html__('Copy', 'backwpup') . ''; + $actions['delete'] = '' . esc_html__('Delete', 'backwpup') . ''; + } + if (current_user_can('backwpup_jobs_start')) { + $url = BackWPup_Job::get_jobrun_url('runnowlink', $item); + $actions['runnow'] = '' . esc_html__('Run now', 'backwpup') . ''; + } + if (current_user_can('backwpup_logs') && BackWPup_Option::get($item, 'logfile')) { + $logfile = basename(BackWPup_Option::get($item, 'logfile')); + if (is_object($this->job_object) && $this->job_object->job['jobid'] == $item) { + $logfile = basename($this->job_object->logfile); + } + $log_name = str_replace(['.html', '.gz'], '', basename($logfile)); + $actions['lastlog'] = '' . __('Last log', 'backwpup') . ''; + } + $actions = apply_filters('backwpup_page_jobs_actions', $actions, $item, false); + $r .= '
' . $this->row_actions($actions) . '
'; + if (is_object($this->job_object)) { + $actionsrun = []; + $actionsrun = apply_filters('backwpup_page_jobs_actions', $actionsrun, $item, true); + $r .= '
' . $this->row_actions($actionsrun) . '
'; + } + + return $r; + } + + /** + * The type Column. + * + * @param $item + * + * @return string + */ + public function column_type($item) + { + $r = ''; + if ($types = BackWPup_Option::get($item, 'type')) { + foreach ($types as $type) { + if (isset($this->job_types[$type])) { + $r .= $this->job_types[$type]->info['name'] . '
'; + } else { + $r .= $type . '
'; + } + } + } + + return $r; + } + + /** + * The destination Column. + * + * @param $item + * + * @return string + */ + public function column_dest($item) + { + $r = ''; + $backup_to = false; + + foreach (BackWPup_Option::get($item, 'type') as $typeid) { + if (isset($this->job_types[$typeid]) && $this->job_types[$typeid]->creates_file()) { + $backup_to = true; + break; + } + } + if ($backup_to) { + foreach (BackWPup_Option::get($item, 'destinations') as $destid) { + if (isset($this->destinations[$destid]['info']['name'])) { + $r .= $this->destinations[$destid]['info']['name'] . '
'; + } else { + $r .= $destid . '
'; + } + } + } else { + $r .= '' . __('Not needed or set', 'backwpup') . '
'; + } + + return $r; + } + + /** + * The next Column. + * + * @param $item + * + * @return string + */ + public function column_next($item) + { + $r = ''; + + $job_normal_hide = ''; + if (is_object($this->job_object)) { + $job_normal_hide = ' style="display:none;"'; + } + if (is_object($this->job_object) && $this->job_object->job['jobid'] == $item) { + $runtime = current_time('timestamp') - $this->job_object->start_time; + $r .= '
' . sprintf(esc_html__('Running for: %s seconds', 'backwpup'), '' . $runtime . '') . '
'; + } + if (is_object($this->job_object) && $this->job_object->job['jobid'] == $item) { + $r .= '
'; + } + if (BackWPup_Option::get($item, 'activetype') == 'wpcron') { + if ($nextrun = wp_next_scheduled('backwpup_cron', ['arg' => $item]) + (get_option('gmt_offset') * 3600)) { + $r .= '' . sprintf(__('%1$s at %2$s by WP-Cron', 'backwpup'), date_i18n(get_option('date_format'), $nextrun, true), date_i18n(get_option('time_format'), $nextrun, true)) . '
'; + } else { + $r .= __('Not scheduled!', 'backwpup') . '
'; + } + } elseif (BackWPup_Option::get($item, 'activetype') == 'easycron') { + $easycron_status = BackWPup_EasyCron::status($item); + if (!empty($easycron_status)) { + $nextrun = BackWPup_Cron::cron_next($easycron_status['cron_expression']) + (get_option('gmt_offset') * 3600); + $r .= '' . sprintf(__('%1$s at %2$s by EasyCron', 'backwpup'), date_i18n(get_option('date_format'), $nextrun, true), date_i18n(get_option('time_format'), $nextrun, true)) . '
'; + } else { + $r .= __('Not scheduled!', 'backwpup') . '
'; + } + } elseif (BackWPup_Option::get($item, 'activetype') == 'link') { + $r .= __('External link', 'backwpup') . '
'; + } else { + $r .= __('Inactive', 'backwpup'); + } + if (is_object($this->job_object) && $this->job_object->job['jobid'] == $item) { + $r .= '
'; + } + + return $r; + } + + /** + * The last Column. + * + * @param $item + * + * @return string + */ + public function column_last($item) + { + $r = ''; + + if (BackWPup_Option::get($item, 'lastrun')) { + $lastrun = BackWPup_Option::get($item, 'lastrun'); + $r .= sprintf(__('%1$s at %2$s', 'backwpup'), date_i18n(get_option('date_format'), $lastrun, true), date_i18n(get_option('time_format'), $lastrun, true)); + if (BackWPup_Option::get($item, 'lastruntime')) { + $r .= '
' . sprintf(__('Runtime: %d seconds', 'backwpup'), BackWPup_Option::get($item, 'lastruntime')); + } + } else { + $r .= __('not yet', 'backwpup'); + } + $r .= '
'; + if (current_user_can('backwpup_backups_download')) { + $download_url = BackWPup_Option::get($item, 'lastbackupdownloadurl'); + if (!empty($download_url)) { + $r .= '' . esc_html__('Download', 'backwpup') . ' | '; + } + } + if (current_user_can('backwpup_logs') && BackWPup_Option::get($item, 'logfile')) { + $logfile = basename(BackWPup_Option::get($item, 'logfile')); + if (is_object($this->job_object) && $this->job_object->job['jobid'] == $item) { + $logfile = basename($this->job_object->logfile); + } + $log_name = str_replace(['.html', '.gz'], '', basename($logfile)); + $r .= '' . esc_html__('Log', 'backwpup') . ''; + } + $r .= ''; + + return $r; + } + + public static function load() + { + //Create Table + self::$listtable = new self(); + + switch (self::$listtable->current_action()) { + case 'delete': //Delete Job + if (!current_user_can('backwpup_jobs_edit')) { + break; + } + if (is_array($_GET['jobs'])) { + check_admin_referer('bulk-jobs'); + + foreach ($_GET['jobs'] as $jobid) { + wp_clear_scheduled_hook('backwpup_cron', ['arg' => absint($jobid)]); + BackWPup_Option::delete_job(absint($jobid)); + } + } + break; + + case 'copy': //Copy Job + if (!current_user_can('backwpup_jobs_edit')) { + break; + } + $old_job_id = absint($_GET['jobid']); + check_admin_referer('copy-job_' . $old_job_id); + //create new + $newjobid = BackWPup_Option::get_job_ids(); + sort($newjobid); + $newjobid = end($newjobid) + 1; + $old_options = BackWPup_Option::get_job($old_job_id); + + foreach ($old_options as $key => $option) { + if ($key === 'jobid') { + $option = $newjobid; + } + if ($key === 'name') { + $option = __('Copy of', 'backwpup') . ' ' . $option; + } + if ($key === 'activetype') { + $option = ''; + } + if ($key === 'archivename') { + $option = str_replace($old_job_id, $newjobid, $option); + } + if ($key === 'logfile' || $key === 'lastbackupdownloadurl' || $key === 'lastruntime' || $key === 'lastrun') { + continue; + } + BackWPup_Option::update($newjobid, $key, $option); + } + break; + + case 'runnow': + $jobid = absint($_GET['jobid']); + if ($jobid) { + if (!current_user_can('backwpup_jobs_start')) { + wp_die(__('Sorry, you don\'t have permissions to do that.', 'backwpup')); + } + check_admin_referer('backwpup_job_run-runnowlink'); + + //check temp folder + $temp_folder_message = BackWPup_File::check_folder(BackWPup::get_plugin_data('TEMP'), true); + BackWPup_Admin::message($temp_folder_message, true); + //check log folder + $log_folder = get_site_option('backwpup_cfg_logfolder'); + $log_folder = BackWPup_File::get_absolute_path($log_folder); + $log_folder_message = BackWPup_File::check_folder($log_folder); + BackWPup_Admin::message($log_folder_message, true); + //check backup destinations + $job_types = BackWPup::get_job_types(); + $job_conf_types = BackWPup_Option::get($jobid, 'type'); + $creates_file = false; + + foreach ($job_types as $id => $job_type_class) { + if (in_array($id, $job_conf_types, true) && $job_type_class->creates_file()) { + $creates_file = true; + break; + } + } + if ($creates_file) { + $job_conf_dests = BackWPup_Option::get($jobid, 'destinations'); + $destinations = 0; + + foreach (BackWPup::get_registered_destinations() as $id => $dest) { + if (!in_array($id, $job_conf_dests, true) || empty($dest['class'])) { + continue; + } - switch ( self::$listtable->current_action() ) { - case 'delete': //Delete Job - if ( ! current_user_can( 'backwpup_jobs_edit' ) ) { - break; - } - if ( is_array( $_GET[ 'jobs' ] ) ) { - check_admin_referer( 'bulk-jobs' ); - foreach ( $_GET[ 'jobs' ] as $jobid ) { - wp_clear_scheduled_hook( 'backwpup_cron', array( 'arg' => absint( $jobid ) ) ); - BackWPup_Option::delete_job( absint( $jobid ) ); - } - } - break; - case 'copy': //Copy Job - if ( ! current_user_can( 'backwpup_jobs_edit' ) ) { - break; - } - $old_job_id = absint( $_GET[ 'jobid' ] ); - check_admin_referer( 'copy-job_' . $old_job_id ); - //create new - $newjobid = BackWPup_Option::get_job_ids(); - sort( $newjobid ); - $newjobid = end( $newjobid ) + 1; - $old_options = BackWPup_Option::get_job( $old_job_id ); - foreach ( $old_options as $key => $option ) { - if ( $key === "jobid" ) - $option = $newjobid; - if ( $key === "name" ) - $option = __( 'Copy of', 'backwpup' ) . ' ' . $option; - if ( $key === "activetype" ) - $option = ''; - if ( $key === "archivename" ) - $option = str_replace( $old_job_id, $newjobid, $option ); - if ( $key === "logfile" || $key === "lastbackupdownloadurl" || $key === "lastruntime" || $key === "lastrun" ) - continue; - BackWPup_Option::update( $newjobid, $key, $option ); - } - break; - case 'runnow': - $jobid = absint( $_GET[ 'jobid' ] ); - if ( $jobid ) { - if ( ! current_user_can( 'backwpup_jobs_start' ) ) { - wp_die( __( 'Sorry, you don\'t have permissions to do that.', 'backwpup') ); - } - check_admin_referer( 'backwpup_job_run-runnowlink' ); - - //check temp folder - $temp_folder_message = BackWPup_File::check_folder( BackWPup::get_plugin_data( 'TEMP' ), TRUE ); - BackWPup_Admin::message( $temp_folder_message, TRUE ); - //check log folder - $log_folder = get_site_option( 'backwpup_cfg_logfolder' ); - $log_folder = BackWPup_File::get_absolute_path( $log_folder ); - $log_folder_message = BackWPup_File::check_folder( $log_folder ); - BackWPup_Admin::message( $log_folder_message, TRUE ); - //check backup destinations - $job_types = BackWPup::get_job_types(); - $job_conf_types = BackWPup_Option::get( $jobid, 'type' ); - $creates_file = FALSE; - foreach ( $job_types as $id => $job_type_class ) { - if ( in_array( $id, $job_conf_types, true ) && $job_type_class->creates_file( ) ) { - $creates_file = TRUE; - break; - } - } - if ( $creates_file ) { - $job_conf_dests = BackWPup_Option::get( $jobid, 'destinations' ); - $destinations = 0; - /* @var BackWPup_Destinations $dest_class */ - foreach ( BackWPup::get_registered_destinations() as $id => $dest ) { - if ( ! in_array( $id, $job_conf_dests, true ) || empty( $dest[ 'class' ] ) ) { - continue; - } - $dest_class = BackWPup::get_destination( $id ); - $job_settings = BackWPup_Option::get_job( $jobid ); - if ( ! $dest_class->can_run( $job_settings ) ) { - BackWPup_Admin::message( sprintf( __( 'The job "%s" destination "%s" is not configured properly','backwpup' ), esc_attr( BackWPup_Option::get( $jobid, 'name' ) ), $id ), TRUE ); - } - $destinations++; - } - if ( $destinations < 1 ) { - BackWPup_Admin::message( sprintf( __( 'The job "%s" needs properly configured destinations to run!','backwpup' ), esc_attr( BackWPup_Option::get( $jobid, 'name' ) ) ), TRUE ); - } - } - - //only start job if messages empty - $log_messages = BackWPup_Admin::get_messages(); - if ( empty ( $log_messages ) ) { - $old_log_file = BackWPup_Option::get( $jobid, 'logfile' ); - BackWPup_Job::get_jobrun_url( 'runnow', $jobid ); - usleep( 250000 ); //wait a quarter second - $new_log_file = BackWPup_Option::get( $jobid, 'logfile', null, false ); - //sleep as long as job not started - $i=0; - while ( $old_log_file === $new_log_file ) { - usleep( 250000 ); //wait a quarter second for next try - $new_log_file = BackWPup_Option::get( $jobid, 'logfile', null, false ); - //wait maximal 10 sec. - if ( $i >= 40 ) { - BackWPup_Admin::message( sprintf( __( 'Job "%s" has started, but not responded for 10 seconds. Please check information.', 'backwpup' ), esc_attr( BackWPup_Option::get( $jobid, 'name' ) ), network_admin_url( 'admin.php' ) . '?page=backwpupsettings#backwpup-tab-information' ), true ); - break 2; - } - $i++; - } - BackWPup_Admin::message( sprintf( __( 'Job "%s" started.', 'backwpup' ), esc_attr( BackWPup_Option::get( $jobid, 'name' ) ) ) ); - } - } - break; - case 'abort': //Abort Job - if ( ! current_user_can( 'backwpup_jobs_start' ) ) - break; - check_admin_referer( 'abort-job' ); - if ( ! file_exists( BackWPup::get_plugin_data( 'running_file' ) ) ) - break; - //abort - BackWPup_Job::user_abort(); - BackWPup_Admin::message( __( 'Job will be terminated.', 'backwpup' ) ) ; - break; - default: - do_action( 'backwpup_page_jobs_load', self::$listtable->current_action() ); - break; - } - - self::$listtable->prepare_items(); - } - - public static function admin_print_styles() { - - ?> + /** @var BackWPup_Destinations $dest_class */ + $dest_class = BackWPup::get_destination($id); + $job_settings = BackWPup_Option::get_job($jobid); + if (!$dest_class->can_run($job_settings)) { + BackWPup_Admin::message(sprintf(__('The job "%s" destination "%s" is not configured properly', 'backwpup'), esc_attr(BackWPup_Option::get($jobid, 'name')), $id), true); + } + ++$destinations; + } + if ($destinations < 1) { + BackWPup_Admin::message(sprintf(__('The job "%s" needs properly configured destinations to run!', 'backwpup'), esc_attr(BackWPup_Option::get($jobid, 'name'))), true); + } + } + + //only start job if messages empty + $log_messages = BackWPup_Admin::get_messages(); + if (empty($log_messages)) { + $old_log_file = BackWPup_Option::get($jobid, 'logfile'); + BackWPup_Job::get_jobrun_url('runnow', $jobid); + usleep(250000); //wait a quarter second + $new_log_file = BackWPup_Option::get($jobid, 'logfile', null, false); + //sleep as long as job not started + $i = 0; + + while ($old_log_file === $new_log_file) { + usleep(250000); //wait a quarter second for next try + $new_log_file = BackWPup_Option::get($jobid, 'logfile', null, false); + //wait maximal 10 sec. + if ($i >= 40) { + BackWPup_Admin::message(sprintf(__('Job "%s" has started, but not responded for 10 seconds. Please check information.', 'backwpup'), esc_attr(BackWPup_Option::get($jobid, 'name')), network_admin_url('admin.php') . '?page=backwpupsettings#backwpup-tab-information'), true); + break 2; + } + ++$i; + } + BackWPup_Admin::message(sprintf(__('Job "%s" started.', 'backwpup'), esc_attr(BackWPup_Option::get($jobid, 'name')))); + } + } + break; + + case 'abort': //Abort Job + if (!current_user_can('backwpup_jobs_start')) { + break; + } + check_admin_referer('abort-job'); + if (!file_exists(BackWPup::get_plugin_data('running_file'))) { + break; + } + //abort + BackWPup_Job::user_abort(); + BackWPup_Admin::message(__('Job will be terminated.', 'backwpup')); + break; + + default: + do_action('backwpup_page_jobs_load', self::$listtable->current_action()); + break; + } + + self::$listtable->prepare_items(); + } + + public static function admin_print_styles() + { + ?> '; - echo '

' . esc_html( sprintf( __( '%s › Jobs', 'backwpup' ), BackWPup::get_plugin_data( 'name' ) ) ) . ' ' . esc_html__( 'Add new', 'backwpup' ) . '

'; - BackWPup_Admin::display_messages(); - $job_object = BackWPup_Job::get_working_data(); - if ( current_user_can( 'backwpup_jobs_start' ) && is_object( $job_object ) ) { - - //read existing logfile - $logfiledata = file_get_contents( $job_object->logfile ); - preg_match( '/]*>/si', $logfiledata, $match ); - if ( ! empty( $match[ 0 ] ) ) - $startpos = strpos( $logfiledata, $match[ 0 ] ) + strlen( $match[ 0 ] ); - else - $startpos = 0; - $endpos = stripos( $logfiledata, '' ); - if ( empty( $endpos ) ) - $endpos = strlen( $logfiledata ); - $length = strlen( $logfiledata ) - ( strlen( $logfiledata ) - $endpos ) - $startpos; - - ?> + } + + public static function admin_print_scripts() + { + wp_enqueue_script('backwpupgeneral'); + } + + public static function page() + { + echo '
'; + echo '

' . esc_html(sprintf(__('%s › Jobs', 'backwpup'), BackWPup::get_plugin_data('name'))) . ' ' . esc_html__('Add new', 'backwpup') . '

'; + BackWPup_Admin::display_messages(); + $job_object = BackWPup_Job::get_working_data(); + if (current_user_can('backwpup_jobs_start') && is_object($job_object)) { + //read existing logfile + $logfiledata = file_get_contents($job_object->logfile); + preg_match('/]*>/si', $logfiledata, $match); + if (!empty($match[0])) { + $startpos = strpos($logfiledata, $match[0]) + strlen($match[0]); + } else { + $startpos = 0; + } + $endpos = stripos($logfiledata, ''); + if (empty($endpos)) { + $endpos = strlen($logfiledata); + } + $length = strlen($logfiledata) - (strlen($logfiledata) - $endpos) - $startpos; ?>
-

job[ 'name' ] ) ); ?>

- warnings; ?> - errors; ?> -
- -
+

job['name'])); ?>

+ warnings; ?> + errors; ?> +
+ +
- +
-
step_percent); ?>%
-
steps_data[ $job_object->step_working ][ 'NAME' ]); ?>
+
step_percent); ?>%
+
steps_data[$job_object->step_working]['NAME']); ?>
substep_percent); ?>%
lastmsg); ?>
- + //display jos Table?>
display(); - ?> + echo wp_nonce_field('backwpup_ajax_nonce', 'backwpupajaxnonce', false); + self::$listtable->display(); ?>
logfile ) ) { ?> + if (!empty($job_object->logfile)) { ?> warnings; - $errors = $job_object->errors; - $step_percent = $job_object->step_percent; - $substep_percent = $job_object->substep_percent; - $runtime = current_time( 'timestamp' ) - $job_object->start_time; - $onstep = $job_object->steps_data[ $job_object->step_working ][ 'NAME' ]; - $lastmsg = $job_object->lastmsg; - $lasterrormsg = $job_object->lasterrormsg; - } else { - $logheader = BackWPup_Job::read_logheader( $logfile ); - $warnings = $logheader[ 'warnings' ]; - $runtime = $logheader[ 'runtime' ]; - $errors = $logheader[ 'errors' ]; - $step_percent = 100; - $substep_percent = 100; - $onstep = '

' . esc_html__( 'Job completed' , 'backwpup' ) . '

'; - if ( $errors > 0 ) - $lastmsg = '

' . esc_html__( 'ERROR:', 'backwpup' ) . ' ' . sprintf( esc_html__( 'Job has ended with errors in %s seconds. You must resolve the errors for correct execution.', 'backwpup' ), $logheader[ 'runtime' ] ) . '

'; - elseif ( $warnings > 0 ) - $lastmsg = '

' . esc_html__( 'WARNING:', 'backwpup' ) . ' ' . sprintf( esc_html__( 'Job has done with warnings in %s seconds. Please resolve them for correct execution.', 'backwpup' ), $logheader[ 'runtime' ] ) . '

'; - else - $lastmsg = '

' . sprintf( esc_html__( 'Job done in %s seconds.', 'backwpup' ), $logheader[ 'runtime' ] ) . '

'; - $lasterrormsg = ''; - $done = 1; - } - - if ( '.gz' == substr( $logfile, -3 ) ) - $logfiledata = file_get_contents( 'compress.zlib://' . $logfile, FALSE, NULL, $logpos ); - else - $logfiledata = file_get_contents( $logfile, FALSE, NULL, $logpos ); - - preg_match( '/]*>/si', $logfiledata, $match ); - if ( ! empty( $match[ 0 ] ) ) - $startpos = strpos( $logfiledata, $match[ 0 ] ) + strlen( $match[ 0 ] ); - else - $startpos = 0; - - $endpos = stripos( $logfiledata, '' ); - if ( FALSE === $endpos ) - $endpos = strlen( $logfiledata ); - - $length = strlen( $logfiledata ) - ( strlen( $logfiledata ) - $endpos ) - $startpos; - - //check if restart must done on ALTERNATE_WP_CRON - if ( is_object( $job_object ) && defined( 'ALTERNATE_WP_CRON' ) && ALTERNATE_WP_CRON ) { - $restart = BackWPup_Job::get_jobrun_url( 'restartalt' ); - if ( $job_object->pid === 0 && $job_object->uniqid === '' ) { - $restart_url = $restart[ 'url' ]; - } - $last_update = microtime( TRUE ) - $job_object->timestamp_last_update; - if ( empty( $job_object->pid ) && $last_update > 10 ) { - $restart_url = $restart[ 'url' ]; - } - } - - wp_send_json( array( - 'log_pos' => strlen( $logfiledata ) + $logpos, - 'log_text' => substr( $logfiledata, $startpos, $length ), - 'warning_count' => $warnings, - 'error_count' => $errors, - 'running_time' => $runtime, - 'step_percent' => $step_percent, - 'on_step' => $onstep, - 'last_msg' => $lastmsg, - 'last_error_msg' => $lasterrormsg, - 'sub_step_percent'=> $substep_percent, - 'restart_url' => $restart_url, - 'job_done' => $done - ) ); - } + } + + /** + * Function to generate json data. + */ + public static function ajax_working() + { + check_ajax_referer('backwpupworking_ajax_nonce'); + + if (!current_user_can('backwpup_jobs_start')) { + exit('-1'); + } + + $log_folder = get_site_option('backwpup_cfg_logfolder'); + $log_folder = BackWPup_File::get_absolute_path($log_folder); + $logfile = isset($_GET['logfile']) ? $log_folder . basename(trim($_GET['logfile'])) : null; + $logpos = isset($_GET['logpos']) ? absint($_GET['logpos']) : 0; + $restart_url = ''; + + //check if logfile renamed + if (file_exists($logfile . '.gz')) { + $logfile .= '.gz'; + } + + if (!is_readable($logfile) || strstr($_GET['logfile'], 'backwpup_log_') === false) { + exit('0'); + } + + $job_object = BackWPup_Job::get_working_data(); + $done = 0; + if (is_object($job_object)) { + $warnings = $job_object->warnings; + $errors = $job_object->errors; + $step_percent = $job_object->step_percent; + $substep_percent = $job_object->substep_percent; + $runtime = current_time('timestamp') - $job_object->start_time; + $onstep = $job_object->steps_data[$job_object->step_working]['NAME']; + $lastmsg = $job_object->lastmsg; + $lasterrormsg = $job_object->lasterrormsg; + } else { + $logheader = BackWPup_Job::read_logheader($logfile); + $warnings = $logheader['warnings']; + $runtime = $logheader['runtime']; + $errors = $logheader['errors']; + $step_percent = 100; + $substep_percent = 100; + $onstep = '

' . esc_html__('Job completed', 'backwpup') . '

'; + if ($errors > 0) { + $lastmsg = '

' . esc_html__('ERROR:', 'backwpup') . ' ' . sprintf(esc_html__('Job has ended with errors in %s seconds. You must resolve the errors for correct execution.', 'backwpup'), $logheader['runtime']) . '

'; + } elseif ($warnings > 0) { + $lastmsg = '

' . esc_html__('WARNING:', 'backwpup') . ' ' . sprintf(esc_html__('Job has done with warnings in %s seconds. Please resolve them for correct execution.', 'backwpup'), $logheader['runtime']) . '

'; + } else { + $lastmsg = '

' . sprintf(esc_html__('Job done in %s seconds.', 'backwpup'), $logheader['runtime']) . '

'; + } + $lasterrormsg = ''; + $done = 1; + } + + if ('.gz' == substr($logfile, -3)) { + $logfiledata = file_get_contents('compress.zlib://' . $logfile, false, null, $logpos); + } else { + $logfiledata = file_get_contents($logfile, false, null, $logpos); + } + + preg_match('/]*>/si', $logfiledata, $match); + if (!empty($match[0])) { + $startpos = strpos($logfiledata, $match[0]) + strlen($match[0]); + } else { + $startpos = 0; + } + + $endpos = stripos($logfiledata, ''); + if (false === $endpos) { + $endpos = strlen($logfiledata); + } + + $length = strlen($logfiledata) - (strlen($logfiledata) - $endpos) - $startpos; + + //check if restart must done on ALTERNATE_WP_CRON + if (is_object($job_object) && defined('ALTERNATE_WP_CRON') && ALTERNATE_WP_CRON) { + $restart = BackWPup_Job::get_jobrun_url('restartalt'); + if ($job_object->pid === 0 && $job_object->uniqid === '') { + $restart_url = $restart['url']; + } + $last_update = microtime(true) - $job_object->timestamp_last_update; + if (empty($job_object->pid) && $last_update > 10) { + $restart_url = $restart['url']; + } + } + + wp_send_json([ + 'log_pos' => strlen($logfiledata) + $logpos, + 'log_text' => substr($logfiledata, $startpos, $length), + 'warning_count' => $warnings, + 'error_count' => $errors, + 'running_time' => $runtime, + 'step_percent' => $step_percent, + 'on_step' => $onstep, + 'last_msg' => $lastmsg, + 'last_error_msg' => $lasterrormsg, + 'sub_step_percent' => $substep_percent, + 'restart_url' => $restart_url, + 'job_done' => $done, + ]); + } } - diff --git a/inc/class-page-logs.php b/inc/class-page-logs.php index ffe83936..f56e387f 100644 --- a/inc/class-page-logs.php +++ b/inc/class-page-logs.php @@ -1,391 +1,384 @@ 'logs', - 'singular' => 'log', - 'ajax' => true, - ) ); - - $this->log_folder = get_site_option( 'backwpup_cfg_logfolder' ); - $this->log_folder = BackWPup_File::get_absolute_path( $this->log_folder ); - $this->log_folder = untrailingslashit( $this->log_folder ); - } - - /** - * User can - * - * @return bool True if user has the right capabilities, false otherwise - */ - function ajax_user_can() { - - return current_user_can( 'backwpup_logs' ); - } - - /** - * Prepare Items - * - * @return void - */ - function prepare_items() { - - $this->job_types = BackWPup::get_job_types(); - - $per_page = $this->get_items_per_page( 'backwpuplogs_per_page' ); - if ( empty( $per_page ) || $per_page < 1 ) { - $per_page = 20; - } - - // Load logs. - $logfiles = array(); - - if ( is_readable( $this->log_folder ) ) { - $dir = new BackWPup_Directory( $this->log_folder ); - foreach ( $dir as $file ) { - if ( $file->isFile() && $file->isReadable() && strpos( $file->getFilename(), 'backwpup_log_' ) !== false && strpos( $file->getFilename(), '.html' ) !== false ) { - $logfiles[$file->getMTime()] = $file->getFilename(); - } - } - } - - // Ordering. - $order = isset( $_GET['order'] ) ? $_GET['order'] : 'desc'; - $orderby = isset( $_GET['orderby'] ) ? $_GET['orderby'] : 'time'; - - if ( $orderby == 'time' ) { - if ( $order == 'asc' ) { - ksort( $logfiles ); - } else { - krsort( $logfiles ); - } - } - - // By page. - $start = intval( ( $this->get_pagenum() - 1 ) * $per_page ); - $end = $start + $per_page; - if ( $end > count( $logfiles ) ) { - $end = count( $logfiles ); - } - - $this->items = array(); - $i = - 1; - - foreach ( $logfiles as $mtime => $logfile ) { - $i ++; - if ( $i < $start ) { - continue; - } - if ( $i >= $end ) { - break; - } - $this->items[ $mtime ] = BackWPup_Job::read_logheader( $this->log_folder . '/' . $logfile ); - $this->items[ $mtime ]['file'] = $logfile; - } - - $this->set_pagination_args( array( - 'total_items' => count( $logfiles ), - 'per_page' => $per_page, - 'orderby' => $orderby, - 'order' => $order, - ) ); - } - - /** - * @inheritdoc - */ - function get_sortable_columns() { - - return array( - 'time' => array( 'time', false ), - ); - } - - function no_items() { - - _e( 'No Logs.', 'backwpup' ); - } - - /** - * @inheritdoc - */ - function get_bulk_actions() { - - if ( ! $this->has_items() ) { - return array(); - } - - $actions = array(); - $actions['delete'] = __( 'Delete', 'backwpup' ); - - return $actions; - } - - /** - * @inheritdoc - */ - function get_columns() { - - $posts_columns = array(); - $posts_columns['cb'] = ''; - $posts_columns['time'] = __( 'Time', 'backwpup' ); - $posts_columns['job'] = __( 'Job', 'backwpup' ); - $posts_columns['status'] = __( 'Status', 'backwpup' ); - $posts_columns['type'] = __( 'Type', 'backwpup' ); - $posts_columns['size'] = __( 'Size', 'backwpup' ); - $posts_columns['runtime'] = __( 'Runtime', 'backwpup' ); - - return $posts_columns; - } - - /** - * @inheritdoc - */ - function column_cb( $item ) { - - return ''; - } - - /** - * The job id Column - * - * @param $item - * - * @return string - */ - function column_time( $item ) { - - $r = sprintf( __( '%1$s at %2$s', 'backwpup' ), date_i18n( get_option( 'date_format' ), $item['logtime'], true ), date_i18n( get_option( 'time_format' ), $item['logtime'], true ) ); - - return $r; - } - - /** - * The type Column - * - * @param $item - * - * @return string - */ - function column_type( $item ) { - - $r = ''; - if ( $types = explode( '+', $item['type'] ) ) { - foreach ( $types as $type ) { - if ( isset( $this->job_types[ $type ] ) ) { - $r .= $this->job_types[ $type ]->info['name'] . '
'; - } else { - $r .= $type . '
'; - } - } - } - - return $r; - } - - /** - * The log Column - * - * @param $item - * - * @return string - */ - function column_job( $item ) { - - $log_name = str_replace( array( '.html', '.gz' ), '', basename( $item['file'] ) ); - $r = "" . esc_html( ! empty( $item['name'] ) ? $item['name'] : $item['file'] ) . ""; - $actions = array(); - $actions['view'] = '' . __( 'View', 'backwpup' ) . ''; - if ( current_user_can( 'backwpup_logs_delete' ) ) { - $actions['delete'] = "get_pagenum() . '&logfiles[]=' . $item['file'], 'bulk-logs' ) . "\" onclick=\"return showNotice.warn();\">" . __( 'Delete', 'backwpup' ) . ""; - } - $actions['download'] = "" . __( 'Download', 'backwpup' ) . ""; - $r .= $this->row_actions( $actions ); - - return $r; - } - - /** - * The status Column - * - * @param $item - * - * @return string - */ - function column_status( $item ) { - - $r = ''; - if ( $item['errors'] ) { - $r .= sprintf( '' . _n( "1 ERROR", "%d ERRORS", $item['errors'], 'backwpup' ) . '
', $item['errors'] ); - } - if ( $item['warnings'] ) { - $r .= sprintf( '' . _n( "1 WARNING", "%d WARNINGS", $item['warnings'], 'backwpup' ) . '
', $item['warnings'] ); - } - if ( ! $item['errors'] && ! $item['warnings'] ) { - $r .= '' . __( 'O.K.', 'backwpup' ) . ''; - } - - return $r; - } - - /** - * The size Column - * - * @param $item - * - * @return string - */ - function column_size( $item ) { - - if ( ! empty( $item['backupfilesize'] ) ) { - return size_format( $item['backupfilesize'], 2 ); - } else { - return __( 'Log only', 'backwpup' ); - } - } - - /** - * The runtime Column - * - * @param $item - * - * @return string - */ - function column_runtime( $item ) { - - return $item['runtime'] . ' ' . __( 'seconds', 'backwpup' ); - } - - /** - * Load - * - * @return void - */ - public static function load() { - - //Create Table - self::$listtable = new BackWPup_Page_Logs; - - switch ( self::$listtable->current_action() ) { - // Delete Log - case 'delete': - if ( ! current_user_can( 'backwpup_logs_delete' ) ) { - break; - } - if ( is_array( $_GET['logfiles'] ) ) { - check_admin_referer( 'bulk-logs' ); - foreach ( $_GET['logfiles'] as $logfile ) { - $logfile = basename( $logfile ); - if ( is_writeable( self::$listtable->log_folder . '/' . $logfile ) && ! is_dir( self::$listtable->log_folder . '/' . $logfile ) && ! is_link( self::$listtable->log_folder . '/' . $logfile ) ) { - unlink( self::$listtable->log_folder . '/' . $logfile ); - } - } - } - break; - - // Download Log - case 'download': - $log_file = trailingslashit( self::$listtable->log_folder ) . basename( trim( $_GET['file'] ) ); - $log_file = realpath( BackWPup_Sanitize_Path::sanitize_path( $log_file ) ); - - if ( ! $log_file - || ! is_readable( $log_file ) - || is_dir( $log_file ) - || is_link( $log_file ) - ) { - header( 'HTTP/1.0 404 Not Found' ); - die(); - } - - $capability = 'backwpup_logs'; - - $download_handler = new BackWpup_Download_Handler( - new BackWPup_Download_File( - $log_file, - function ( \BackWPup_Download_File_Interface $obj ) { - - $obj->clean_ob() - ->headers(); - - // phpcs:ignore - echo backwpup_wpfilesystem()->get_contents( $obj->filepath() ); - die(); - }, - $capability - ), - 'download_backwpup_logs', - $capability, - 'download' - ); - - $download_handler->handle(); - break; - } - - - //Save per page - if ( isset( $_POST['screen-options-apply'] ) - && isset( $_POST['wp_screen_options']['option'] ) - && isset( $_POST['wp_screen_options']['value'] ) - && $_POST['wp_screen_options']['option'] == 'backwpuplogs_per_page' - ) { - check_admin_referer( 'screen-options-nonce', 'screenoptionnonce' ); - - global $current_user; - - if ( $_POST['wp_screen_options']['value'] > 0 && $_POST['wp_screen_options']['value'] < 1000 ) { - update_user_option( $current_user->ID, 'backwpuplogs_per_page', (int) $_POST['wp_screen_options']['value'] ); - wp_redirect( remove_query_arg( array( 'pagenum', 'apage', 'paged' ), wp_get_referer() ) ); - exit; - } - } - - add_screen_option( 'per_page', array( - 'label' => __( 'Logs', 'backwpup' ), - 'default' => 20, - 'option' => 'backwpuplogs_per_page', - ) ); - - self::$listtable->prepare_items(); - } - - /** - * Output css - * - * @return void - */ - public static function admin_print_styles() { - - ?> +class BackWPup_Page_Logs extends WP_List_Table +{ + /** + * Log Folder. + * + * @var string The log folder + */ + public $log_folder = ''; + + /** + * BackWPup_Page_Logs. + * + * @var \BackWPup_Page_Logs The instance + */ + private static $listtable; + + /** + * Job Types. + * + * @var array A list of job types + */ + private $job_types; + + /** + * BackWPup_Page_Logs constructor. + */ + public function __construct() + { + parent::__construct([ + 'plural' => 'logs', + 'singular' => 'log', + 'ajax' => true, + ]); + + $this->log_folder = get_site_option('backwpup_cfg_logfolder'); + $this->log_folder = BackWPup_File::get_absolute_path($this->log_folder); + $this->log_folder = untrailingslashit($this->log_folder); + } + + /** + * User can. + * + * @return bool True if user has the right capabilities, false otherwise + */ + public function ajax_user_can() + { + return current_user_can('backwpup_logs'); + } + + /** + * Prepare Items. + */ + public function prepare_items() + { + $this->job_types = BackWPup::get_job_types(); + + $per_page = $this->get_items_per_page('backwpuplogs_per_page'); + if (empty($per_page) || $per_page < 1) { + $per_page = 20; + } + + // Load logs. + $logfiles = []; + + if (is_readable($this->log_folder)) { + $dir = new BackWPup_Directory($this->log_folder); + + foreach ($dir as $file) { + if ($file->isFile() && $file->isReadable() && strpos($file->getFilename(), 'backwpup_log_') !== false && strpos($file->getFilename(), '.html') !== false) { + $logfiles[$file->getMTime()] = $file->getFilename(); + } + } + } + + // Ordering. + $order = $_GET['order'] ?? 'desc'; + $orderby = $_GET['orderby'] ?? 'time'; + + if ($orderby == 'time') { + if ($order == 'asc') { + ksort($logfiles); + } else { + krsort($logfiles); + } + } + + // By page. + $start = intval(($this->get_pagenum() - 1) * $per_page); + $end = $start + $per_page; + if ($end > count($logfiles)) { + $end = count($logfiles); + } + + $this->items = []; + $i = -1; + + foreach ($logfiles as $mtime => $logfile) { + ++$i; + if ($i < $start) { + continue; + } + if ($i >= $end) { + break; + } + $this->items[$mtime] = BackWPup_Job::read_logheader($this->log_folder . '/' . $logfile); + $this->items[$mtime]['file'] = $logfile; + } + + $this->set_pagination_args([ + 'total_items' => count($logfiles), + 'per_page' => $per_page, + 'orderby' => $orderby, + 'order' => $order, + ]); + } + + /** + * {@inheritdoc} + */ + public function get_sortable_columns() + { + return [ + 'time' => ['time', false], + ]; + } + + public function no_items() + { + _e('No Logs.', 'backwpup'); + } + + /** + * {@inheritdoc} + */ + public function get_bulk_actions() + { + if (!$this->has_items()) { + return []; + } + + $actions = []; + $actions['delete'] = __('Delete', 'backwpup'); + + return $actions; + } + + /** + * {@inheritdoc} + */ + public function get_columns() + { + $posts_columns = []; + $posts_columns['cb'] = ''; + $posts_columns['time'] = __('Time', 'backwpup'); + $posts_columns['job'] = __('Job', 'backwpup'); + $posts_columns['status'] = __('Status', 'backwpup'); + $posts_columns['type'] = __('Type', 'backwpup'); + $posts_columns['size'] = __('Size', 'backwpup'); + $posts_columns['runtime'] = __('Runtime', 'backwpup'); + + return $posts_columns; + } + + /** + * {@inheritdoc} + */ + public function column_cb($item) + { + return ''; + } + + /** + * The job id Column. + * + * @param $item + * + * @return string + */ + public function column_time($item) + { + return sprintf(__('%1$s at %2$s', 'backwpup'), date_i18n(get_option('date_format'), $item['logtime'], true), date_i18n(get_option('time_format'), $item['logtime'], true)); + } + + /** + * The type Column. + * + * @param $item + * + * @return string + */ + public function column_type($item) + { + $r = ''; + if ($types = explode('+', $item['type'])) { + foreach ($types as $type) { + if (isset($this->job_types[$type])) { + $r .= $this->job_types[$type]->info['name'] . '
'; + } else { + $r .= $type . '
'; + } + } + } + + return $r; + } + + /** + * The log Column. + * + * @param $item + * + * @return string + */ + public function column_job($item) + { + $log_name = str_replace(['.html', '.gz'], '', basename($item['file'])); + $r = '' . esc_html(!empty($item['name']) ? $item['name'] : $item['file']) . ''; + $actions = []; + $actions['view'] = '' . __('View', 'backwpup') . ''; + if (current_user_can('backwpup_logs_delete')) { + $actions['delete'] = '' . __('Delete', 'backwpup') . ''; + } + $actions['download'] = '' . __('Download', 'backwpup') . ''; + $r .= $this->row_actions($actions); + + return $r; + } + + /** + * The status Column. + * + * @param $item + * + * @return string + */ + public function column_status($item) + { + $r = ''; + if ($item['errors']) { + $r .= sprintf('' . _n('1 ERROR', '%d ERRORS', $item['errors'], 'backwpup') . '
', $item['errors']); + } + if ($item['warnings']) { + $r .= sprintf('' . _n('1 WARNING', '%d WARNINGS', $item['warnings'], 'backwpup') . '
', $item['warnings']); + } + if (!$item['errors'] && !$item['warnings']) { + $r .= '' . __('O.K.', 'backwpup') . ''; + } + + return $r; + } + + /** + * The size Column. + * + * @param $item + * + * @return string + */ + public function column_size($item) + { + if (!empty($item['backupfilesize'])) { + return size_format($item['backupfilesize'], 2); + } + + return __('Log only', 'backwpup'); + } + + /** + * The runtime Column. + * + * @param $item + * + * @return string + */ + public function column_runtime($item) + { + return $item['runtime'] . ' ' . __('seconds', 'backwpup'); + } + + /** + * Load. + */ + public static function load() + { + global $current_user; + + //Create Table + self::$listtable = new BackWPup_Page_Logs(); + + switch (self::$listtable->current_action()) { + // Delete Log + case 'delete': + if (!current_user_can('backwpup_logs_delete')) { + break; + } + if (is_array($_GET['logfiles'])) { + check_admin_referer('bulk-logs'); + + foreach ($_GET['logfiles'] as $logfile) { + $logfile = basename($logfile); + if (is_writeable(self::$listtable->log_folder . '/' . $logfile) && !is_dir(self::$listtable->log_folder . '/' . $logfile) && !is_link(self::$listtable->log_folder . '/' . $logfile)) { + unlink(self::$listtable->log_folder . '/' . $logfile); + } + } + } + break; + // Download Log + case 'download': + $log_file = trailingslashit(self::$listtable->log_folder) . basename(trim($_GET['file'])); + $log_file = realpath(BackWPup_Sanitize_Path::sanitize_path($log_file)); + + if (!$log_file + || !is_readable($log_file) + || is_dir($log_file) + || is_link($log_file) + ) { + header('HTTP/1.0 404 Not Found'); + + exit(); + } + + $capability = 'backwpup_logs'; + + $download_handler = new BackWpup_Download_Handler( + new BackWPup_Download_File( + $log_file, + function (BackWPup_Download_File_Interface $obj) { + $obj->clean_ob() + ->headers() + ; + + // phpcs:ignore + echo backwpup_wpfilesystem()->get_contents($obj->filepath()); + + exit(); + }, + $capability + ), + 'download_backwpup_logs', + $capability, + 'download' + ); + + $download_handler->handle(); + break; + } + + //Save per page + if (isset($_POST['screen-options-apply'], $_POST['wp_screen_options']['option'], $_POST['wp_screen_options']['value']) + && $_POST['wp_screen_options']['option'] == 'backwpuplogs_per_page' + ) { + check_admin_referer('screen-options-nonce', 'screenoptionnonce'); + + if ($_POST['wp_screen_options']['value'] > 0 && $_POST['wp_screen_options']['value'] < 1000) { + update_user_option($current_user->ID, 'backwpuplogs_per_page', (int) $_POST['wp_screen_options']['value']); + wp_redirect(remove_query_arg(['pagenum', 'apage', 'paged'], wp_get_referer())); + + exit; + } + } + + add_screen_option('per_page', [ + 'label' => __('Logs', 'backwpup'), + 'default' => 20, + 'option' => 'backwpuplogs_per_page', + ]); + + self::$listtable->prepare_items(); + } + + /** + * Output css. + */ + public static function admin_print_styles() + { + ?> + } + + /** + * Output js. + */ + public static function admin_print_scripts() + { + wp_enqueue_script('backwpupgeneral'); + } + + /** + * Display the page content. + */ + public static function page() + { + ?>
-

+

@@ -442,32 +433,31 @@ public static function page() {
settings_views = array_filter( $settings_views, function ($setting) { - return $setting instanceof Settings\SettingTab; } ); $this->settings_updaters = array_filter( $settings_updaters, function ($setting) { + return $setting instanceof SettingUpdatable; + } + ); + } - return $setting instanceof Settings\SettingUpdatable; + /** + * @return array + */ + public static function get_information() + { + global $wpdb; + + $information = []; + + // Wordpress version + $information['wpversion']['label'] = __('WordPress version', 'backwpup'); + $information['wpversion']['value'] = BackWPup::get_plugin_data('wp_version'); + + // BackWPup version + if (!BackWPup::is_pro()) { + $information['bwuversion']['label'] = esc_html__('BackWPup version', 'backwpup'); + $information['bwuversion']['value'] = BackWPup::get_plugin_data('Version'); + $information['bwuversion']['html'] = BackWPup::get_plugin_data('Version') . + ' ' . + esc_html__('Get pro.', 'backwpup') . ''; + } else { + $information['bwuversion']['label'] = __('BackWPup Pro version', 'backwpup'); + $information['bwuversion']['value'] = BackWPup::get_plugin_data('Version'); + } + + // PHP version + $information['phpversion']['label'] = esc_html__('PHP version', 'backwpup'); + $bit = ''; + if (PHP_INT_SIZE === 4) { + $bit = ' (32bit)'; + } elseif (PHP_INT_SIZE === 8) { + $bit = ' (64bit)'; + } + $information['phpversion']['value'] = PHP_VERSION . ' ' . $bit; + + // MySQL version + $information['mysqlversion']['label'] = esc_html__('MySQL version', 'backwpup'); + $information['mysqlversion']['value'] = $wpdb->get_var('SELECT VERSION() AS version'); + + // Curl version + $information['curlversion']['label'] = esc_html__('cURL version', 'backwpup'); + if (function_exists('curl_version')) { + $curl_version = curl_version(); + $information['curlversion']['value'] = $curl_version['version']; + $information['curlsslversion']['label'] = __('cURL SSL version', 'backwpup'); + $information['curlsslversion']['value'] = $curl_version['ssl_version']; + } else { + $information['curlversion']['value'] = esc_html__('unavailable', 'backwpup'); + } + + // WP cron URL + $information['wpcronurl']['label'] = esc_html__('WP-Cron url', 'backwpup'); + $information['wpcronurl']['value'] = site_url('wp-cron.php'); + + // Response test + $server_connect = []; + $server_connect['label'] = __('Server self connect', 'backwpup'); + + $raw_response = BackWPup_Job::get_jobrun_url('test'); + $response_code = wp_remote_retrieve_response_code($raw_response); + $response_body = wp_remote_retrieve_body($raw_response); + if (strstr($response_body, 'BackWPup test request') === false) { + $server_connect['value'] = esc_html__('Not expected HTTP response:', 'backwpup') . "\n"; + $server_connect['html'] = wp_kses( + __('Not expected HTTP response:
', 'backwpup'), + ['strong' => []] + ); + if (!$response_code) { + $server_connect['value'] .= sprintf( + wp_kses_post(__('WP Http Error: %s', 'backwpup')), + $raw_response->get_error_message() + ) . "\n"; + $server_connect['html'] = sprintf( + __('WP Http Error: %s', 'backwpup'), + esc_html($raw_response->get_error_message()) + ) . '
'; + } else { + $server_connect['value'] .= sprintf(__('Status-Code: %d', 'backwpup'), $response_code) . "\n"; + $server_connect['html'] .= sprintf( + __('Status-Code: %d', 'backwpup'), + esc_html($response_code) + ) . '
'; + } + $response_headers = wp_remote_retrieve_headers($raw_response); + + foreach ($response_headers as $key => $value) { + $server_connect['value'] .= ucfirst($key) . ": {$value}\n"; + $server_connect['html'] .= esc_html(ucfirst($key)) . ': ' . esc_html( + $value + ) . '
'; + } + $content = wp_remote_retrieve_body($raw_response); + if ($content) { + $server_connect['value'] .= sprintf(__('Content: %s', 'backwpup'), $content); + $server_connect['html'] .= sprintf( + __('Content: %s', 'backwpup'), + esc_html($content) + ); } + } else { + $server_connect['value'] = __('Response Test O.K.', 'backwpup'); + } + $information['serverconnect'] = $server_connect; + + // Document root + $information['docroot']['label'] = 'Document root'; + $information['docroot']['value'] = $_SERVER['DOCUMENT_ROOT']; + + // Temp folder + $information['tmpfolder']['label'] = esc_html__('Temp folder', 'backwpup'); + if (!is_dir(BackWPup::get_plugin_data('TEMP'))) { + $information['tmpfolder']['value'] = sprintf( + esc_html__('Temp folder %s doesn\'t exist.', 'backwpup'), + BackWPup::get_plugin_data('TEMP') + ); + } elseif (!is_writable(BackWPup::get_plugin_data('TEMP'))) { + $information['tmpfolder']['value'] = sprintf( + esc_html__('Temporary folder %s is not writable.', 'backwpup'), + BackWPup::get_plugin_data('TEMP') + ); + } else { + $information['tmpfolder']['value'] = BackWPup::get_plugin_data('TEMP'); + } + + // Log folder + $information['logfolder']['label'] = esc_html__('Log folder', 'backwpup'); + $log_folder = BackWPup_File::get_absolute_path( + get_site_option('backwpup_cfg_logfolder') ); + if (!is_dir($log_folder)) { + $information['logfolder']['value'] = sprintf( + esc_html__('Log folder %s does not exist.', 'backwpup'), + $log_folder + ); + } elseif (!is_writable($log_folder)) { + $information['logfolder']['value'] = sprintf( + esc_html__('Log folder %s is not writable.', 'backwpup'), + $log_folder + ); + } else { + $information['logfolder']['value'] = $log_folder; + } + + // Server + $information['server']['label'] = esc_html__('Server', 'backwpup'); + $information['server']['value'] = $_SERVER['SERVER_SOFTWARE']; + + // OS + $information['os']['label'] = esc_html__('Operating System', 'backwpup'); + $information['os']['value'] = PHP_OS; + + // PHP SAPI + $information['phpsapi']['label'] = esc_html__('PHP SAPI', 'backwpup'); + $information['phpsapi']['value'] = PHP_SAPI; + + // PHP user + $information['phpuser']['label'] = esc_html__('Current PHP user', 'backwpup'); + if (function_exists('get_current_user')) { + $information['phpuser']['value'] = get_current_user(); + } else { + $information['phpuser']['value'] = esc_html__('Function Disabled', 'backwpup'); + } + + // Maximum execution time + $information['maxexectime']['label'] = esc_html__('Maximum execution time', 'backwpup'); + $information['maxexectime']['value'] = sprintf( + __('%d seconds', 'backwpup'), + ini_get('max_execution_time') + ); + + // BackWPup Maximum script execution time + $information['jobmaxexecutiontime']['label'] = esc_html__( + 'BackWPup maximum script execution time', + 'backwpup' + ); + $information['jobmaxexecutiontime']['value'] = sprintf( + __('%d seconds', 'backwpup'), + absint(get_site_option('backwpup_cfg_jobmaxexecutiontime')) + ); + + // Alternate WP cron + $information['altwpcron']['label'] = esc_html__('Alternative WP Cron', 'backwpup'); + if (defined('ALTERNATE_WP_CRON') && ALTERNATE_WP_CRON) { + $information['altwpcron']['value'] = esc_html__('On', 'backwpup'); + } else { + $information['altwpcron']['value'] = esc_html__('Off', 'backwpup'); + } + + // Disable WP cron + $information['disablewpcron']['label'] = esc_html__('Disabled WP Cron', 'backwpup'); + if (defined('DISABLE_WP_CRON') && DISABLE_WP_CRON) { + $information['disablewpcron']['value'] = esc_html__('On', 'backwpup'); + } else { + $information['disablewpcron']['value'] = esc_html__('Off', 'backwpup'); + } + + // CHMOD dir + $information['chmoddir']['label'] = esc_html__('CHMOD Dir', 'backwpup'); + if (defined('FS_CHMOD_DIR')) { + $information['chmoddir']['value'] = FS_CHMOD_DIR; + } else { + $information['chmoddir']['value'] = '0755'; + } + + // Server time + $information['servertime']['label'] = esc_html__('Server Time', 'backwpup'); + $now = localtime(time(), true); + $information['servertime']['value'] = $now['tm_hour'] . ':' . $now['tm_min']; + + // Blog time + $information['blogtime']['label'] = esc_html__('Blog Time', 'backwpup'); + $information['blogtime']['value'] = date('H:i', current_time('timestamp')); + + // Blog timezone + $information['blogtz']['label'] = esc_html__('Blog Timezone', 'backwpup'); + $information['blogtz']['value'] = get_option('timezone_string'); + + // Blog time offset + $information['blogoffset']['label'] = esc_html__('Blog Time offset', 'backwpup'); + $information['blogoffset']['value'] = sprintf( + esc_html__('%s hours', 'backwpup'), + (int) get_option('gmt_offset') + ); + + // Blog language + $information['bloglang']['label'] = esc_html__('Blog language', 'backwpup'); + $information['bloglang']['value'] = get_bloginfo('language'); + + // MySQL encoding + $information['mysqlencoding']['label'] = esc_html__('MySQL Client encoding', 'backwpup'); + $information['mysqlencoding']['value'] = defined('DB_CHARSET') ? DB_CHARSET : ''; + + // PHP memory limitesc_html__ + $information['phpmemlimit']['label'] = esc_html__('PHP Memory limit', 'backwpup'); + $information['phpmemlimit']['value'] = ini_get('memory_limit'); + + // WP memory limit + $information['wpmemlimit']['label'] = esc_html__('WP memory limit', 'backwpup'); + $information['wpmemlimit']['value'] = WP_MEMORY_LIMIT; + + // WP maximum memory limit + $information['wpmaxmemlimit']['label'] = esc_html__('WP maximum memory limit', 'backwpup'); + $information['wpmaxmemlimit']['value'] = WP_MAX_MEMORY_LIMIT; + + // Memory in use + $information['memusage']['label'] = esc_html__('Memory in use', 'backwpup'); + $information['memusage']['value'] = size_format(@memory_get_usage(true), 2); + + // Disabled PHP functions + $disabled = esc_html(ini_get('disable_functions')); + if (!empty($disabled)) { + $information['disabledfunctions']['label'] = esc_html__('Disabled PHP Functions:', 'backwpup'); + $information['disabledfunctions']['value'] = implode(', ', explode(',', $disabled)); + } + + // Loaded PHP extensions + $information['loadedextensions']['label'] = esc_html__('Loaded PHP Extensions:', 'backwpup'); + $extensions = get_loaded_extensions(); + sort($extensions); + $information['loadedextensions']['value'] = implode(', ', $extensions); + + return $information; } - /** - * @return array - */ - public static function get_information() { - - global $wpdb; - - $information = array(); - - // Wordpress version - $information['wpversion']['label'] = __( 'WordPress version', 'backwpup' ); - $information['wpversion']['value'] = BackWPup::get_plugin_data( 'wp_version' ); - - // BackWPup version - if ( ! BackWPup::is_pro() ) { - $information['bwuversion']['label'] = esc_html__( 'BackWPup version', 'backwpup' ); - $information['bwuversion']['value'] = BackWPup::get_plugin_data( 'Version' ); - $information['bwuversion']['html'] = BackWPup::get_plugin_data( 'Version' ) . - ' ' . - esc_html__( 'Get pro.', 'backwpup' ) . ''; - } else { - $information['bwuversion']['label'] = __( 'BackWPup Pro version', 'backwpup' ); - $information['bwuversion']['value'] = BackWPup::get_plugin_data( 'Version' ); - } - - // PHP version - $information['phpversion']['label'] = esc_html__( 'PHP version', 'backwpup' ); - $bit = ''; - if ( PHP_INT_SIZE === 4 ) { - $bit = ' (32bit)'; - } elseif ( PHP_INT_SIZE === 8 ) { - $bit = ' (64bit)'; - } - $information['phpversion']['value'] = PHP_VERSION . ' ' . $bit; - - // MySQL version - $information['mysqlversion']['label'] = esc_html__( 'MySQL version', 'backwpup' ); - $information['mysqlversion']['value'] = $wpdb->get_var( "SELECT VERSION() AS version" ); - - // Curl version - $information['curlversion']['label'] = esc_html__( 'cURL version', 'backwpup' ); - if ( function_exists( 'curl_version' ) ) { - $curl_version = curl_version(); - $information['curlversion']['value'] = $curl_version['version']; - $information['curlsslversion']['label'] = __( 'cURL SSL version', 'backwpup' ); - $information['curlsslversion']['value'] = $curl_version['ssl_version']; - } else { - $information['curlversion']['value'] = esc_html__( 'unavailable', 'backwpup' ); - } - - // WP cron URL - $information['wpcronurl']['label'] = esc_html__( 'WP-Cron url', 'backwpup' ); - $information['wpcronurl']['value'] = site_url( 'wp-cron.php' ); - - // Response test - $server_connect['label'] = __( 'Server self connect', 'backwpup' ); - - $raw_response = BackWPup_Job::get_jobrun_url( 'test' ); - $response_code = wp_remote_retrieve_response_code( $raw_response ); - $response_body = wp_remote_retrieve_body( $raw_response ); - if ( strstr( $response_body, 'BackWPup test request' ) === false ) { - $server_connect['value'] = esc_html__( 'Not expected HTTP response:', 'backwpup' ) . "\n"; - $server_connect['html'] = wp_kses( __( 'Not expected HTTP response:
', 'backwpup' ), - array( 'strong' => array() ) ); - if ( ! $response_code ) { - $server_connect['value'] .= sprintf( wp_kses_post( - __( 'WP Http Error: %s', 'backwpup' ), - $raw_response->get_error_message() - ) ) . "\n"; - $server_connect['html'] = sprintf( - __( 'WP Http Error: %s', 'backwpup' ), - esc_html( $raw_response->get_error_message() ) - ) . '
'; - } else { - $server_connect['value'] .= sprintf( __( 'Status-Code: %d', 'backwpup' ), $response_code ) . "\n"; - $server_connect['html'] .= sprintf( - __( 'Status-Code: %d', 'backwpup' ), - esc_html( $response_code ) - ) . '
'; - } - $response_headers = wp_remote_retrieve_headers( $raw_response ); - foreach ( $response_headers as $key => $value ) { - $server_connect['value'] .= ucfirst( $key ) . ": $value\n"; - $server_connect['html'] .= esc_html( ucfirst( $key ) ) . ': ' . esc_html( - $value - ) . '
'; - } - $content = wp_remote_retrieve_body( $raw_response ); - if ( $content ) { - $server_connect['value'] .= sprintf( __( 'Content: %s', 'backwpup' ), $content ); - $server_connect['html'] .= sprintf( - __( 'Content: %s', 'backwpup' ), - esc_html( $content ) - ); - } - } else { - $server_connect['value'] = __( 'Response Test O.K.', 'backwpup' ); - } - $information['serverconnect'] = $server_connect; - - // Document root - $information['docroot']['label'] = 'Document root'; - $information['docroot']['value'] = $_SERVER['DOCUMENT_ROOT']; - - // Temp folder - $information['tmpfolder']['label'] = esc_html__( 'Temp folder', 'backwpup' ); - if ( ! is_dir( BackWPup::get_plugin_data( 'TEMP' ) ) ) { - $information['tmpfolder']['value'] = sprintf( - esc_html__( 'Temp folder %s doesn\'t exist.', 'backwpup' ), - BackWPup::get_plugin_data( 'TEMP' ) - ); - } elseif ( ! is_writable( BackWPup::get_plugin_data( 'TEMP' ) ) ) { - $information['tmpfolder']['value'] = sprintf( - esc_html__( 'Temporary folder %s is not writable.', 'backwpup' ), - BackWPup::get_plugin_data( 'TEMP' ) - ); - } else { - $information['tmpfolder']['value'] = BackWPup::get_plugin_data( 'TEMP' ); - } - - // Log folder - $information['logfolder']['label'] = esc_html__( 'Log folder', 'backwpup' ); - $log_folder = BackWPup_File::get_absolute_path( - get_site_option( 'backwpup_cfg_logfolder' ) - ); - if ( ! is_dir( $log_folder ) ) { - $information['logfolder']['value'] = sprintf( - esc_html__( 'Log folder %s does not exist.', 'backwpup' ), - $log_folder - ); - } elseif ( ! is_writable( $log_folder ) ) { - $information['logfolder']['value'] = sprintf( - esc_html__( 'Log folder %s is not writable.', 'backwpup' ), - $log_folder - ); - } else { - $information['logfolder']['value'] = $log_folder; - } - - // Server - $information['server']['label'] = esc_html__( 'Server', 'backwpup' ); - $information['server']['value'] = $_SERVER['SERVER_SOFTWARE']; - - // OS - $information['os']['label'] = esc_html__( 'Operating System', 'backwpup' ); - $information['os']['value'] = PHP_OS; - - // PHP SAPI - $information['phpsapi']['label'] = esc_html__( 'PHP SAPI', 'backwpup' ); - $information['phpsapi']['value'] = PHP_SAPI; - - // PHP user - $information['phpuser']['label'] = esc_html__( 'Current PHP user', 'backwpup' ); - if ( function_exists( 'get_current_user' ) ) { - $information['phpuser']['value'] = get_current_user(); - } else { - $information['phpuser']['value'] = esc_html__( 'Function Disabled', 'backwpup' ); - } - - // Maximum execution time - $information['maxexectime']['label'] = esc_html__( 'Maximum execution time', 'backwpup' ); - $information['maxexectime']['value'] = sprintf( - __( '%d seconds', 'backwpup' ), - ini_get( 'max_execution_time' ) - ); - - // BackWPup Maximum script execution time - $information['jobmaxexecutiontime']['label'] = esc_html__( 'BackWPup maximum script execution time', - 'backwpup' ); - $information['jobmaxexecutiontime']['value'] = sprintf( - __( '%d seconds', 'backwpup' ), - absint( get_site_option( 'backwpup_cfg_jobmaxexecutiontime' ) ) - ); - - // Alternate WP cron - $information['altwpcron']['label'] = esc_html__( 'Alternative WP Cron', 'backwpup' ); - if ( defined( 'ALTERNATE_WP_CRON' ) && ALTERNATE_WP_CRON ) { - $information['altwpcron']['value'] = esc_html__( 'On', 'backwpup' ); - } else { - $information['altwpcron']['value'] = esc_html__( 'Off', 'backwpup' ); - } - - // Disable WP cron - $information['disablewpcron']['label'] = esc_html__( 'Disabled WP Cron', 'backwpup' ); - if ( defined( 'DISABLE_WP_CRON' ) && DISABLE_WP_CRON ) { - $information['disablewpcron']['value'] = esc_html__( 'On', 'backwpup' ); - } else { - $information['disablewpcron']['value'] = esc_html__( 'Off', 'backwpup' ); - } - - // CHMOD dir - $information['chmoddir']['label'] = esc_html__( 'CHMOD Dir', 'backwpup' ); - if ( defined( 'FS_CHMOD_DIR' ) ) { - $information['chmoddir']['value'] = FS_CHMOD_DIR; - } else { - $information['chmoddir']['value'] = '0755'; - } - - // Server time - $information['servertime']['label'] = esc_html__( 'Server Time', 'backwpup' ); - $now = localtime( time(), true ); - $information['servertime']['value'] = $now['tm_hour'] . ':' . $now['tm_min']; - - // Blog time - $information['blogtime']['label'] = esc_html__( 'Blog Time', 'backwpup' ); - $information['blogtime']['value'] = date( 'H:i', current_time( 'timestamp' ) ); - - // Blog timezone - $information['blogtz']['label'] = esc_html__( 'Blog Timezone', 'backwpup' ); - $information['blogtz']['value'] = get_option( 'timezone_string' ); - - // Blog time offset - $information['blogoffset']['label'] = esc_html__( 'Blog Time offset', 'backwpup' ); - $information['blogoffset']['value'] = sprintf( - esc_html__( '%s hours', 'backwpup' ), - (int) get_option( 'gmt_offset' ) - ); - - // Blog language - $information['bloglang']['label'] = esc_html__( 'Blog language', 'backwpup' ); - $information['bloglang']['value'] = get_bloginfo( 'language' ); - - // MySQL encoding - $information['mysqlencoding']['label'] = esc_html__( 'MySQL Client encoding', 'backwpup' ); - $information['mysqlencoding']['value'] = defined( 'DB_CHARSET' ) ? DB_CHARSET : ''; - - // PHP memory limitesc_html__ - $information['phpmemlimit']['label'] = esc_html__( 'PHP Memory limit', 'backwpup' ); - $information['phpmemlimit']['value'] = ini_get( 'memory_limit' ); - - // WP memory limit - $information['wpmemlimit']['label'] = esc_html__( 'WP memory limit', 'backwpup' ); - $information['wpmemlimit']['value'] = WP_MEMORY_LIMIT; - - // WP maximum memory limit - $information['wpmaxmemlimit']['label'] = esc_html__( 'WP maximum memory limit', 'backwpup' ); - $information['wpmaxmemlimit']['value'] = WP_MAX_MEMORY_LIMIT; - - // Memory in use - $information['memusage']['label'] = esc_html__( 'Memory in use', 'backwpup' ); - $information['memusage']['value'] = size_format( @memory_get_usage( true ), 2 ); - - // Disabled PHP functions - $disabled = esc_html( ini_get( 'disable_functions' ) ); - if ( ! empty( $disabled ) ) { - $information['disabledfunctions']['label'] = esc_html__( 'Disabled PHP Functions:', 'backwpup' ); - $information['disabledfunctions']['value'] = implode( ', ', explode( ',', $disabled ) ); - } - - // Loaded PHP extensions - $information['loadedextensions']['label'] = esc_html__( 'Loaded PHP Extensions:', 'backwpup' ); - $extensions = get_loaded_extensions(); - sort( $extensions ); - $information['loadedextensions']['value'] = implode( ', ', $extensions ); - - return $information; - } - - public function admin_print_scripts() { - - $suffix = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min'; - - wp_enqueue_script( - 'backwpuppagesettings', - untrailingslashit( BackWPup::get_plugin_data( 'URL' ) ) . "/assets/js/page_settings{$suffix}.js", - array( - 'jquery', - 'backwpupgeneral', - 'backwpup_clipboard', - ), - filemtime( untrailingslashit( BackWPup::get_plugin_data( 'plugindir' ) ) . "/assets/js/page_settings{$suffix}.js" ), - true - ); - - if ( \BackWPup::is_pro() ) { - wp_enqueue_script( - 'backwpuppagesettings-encryption', - untrailingslashit( BackWPup::get_plugin_data( 'URL' ) ) . "/assets/js/pro/settings-encryption{$suffix}.js", - array( - 'underscore', - 'jquery', - 'backwpuppagesettings', - 'thickbox', - ), - filemtime( untrailingslashit( BackWPup::get_plugin_data( 'plugindir' ) ) . "/assets/js/pro/settings-encryption{$suffix}.js" ), - true - ); - - wp_localize_script( - 'backwpuppagesettings-encryption', - 'settingsEncryptionVariables', - array( - 'validPublicKey' => esc_html__( 'Public key is valid.', 'backwpup' ), - 'invalidPublicKey' => esc_html__( 'Public key is invalid.', 'backwpup' ), - 'privateKeyMissed' => esc_html__( 'Please enter your private key.', 'backwpup' ), - 'publicKeyMissed' => esc_html__( - 'Please enter a public key first, or generate a key pair.', - 'backwpup' - ), - 'mustDownloadPrivateKey' => esc_html__( - 'Please download the private key before continuing. If you do not save it locally, you cannot decrypt your backups later.', - 'backwpup' - ), - 'mustDownloadSymmetricKey' => esc_html__( - 'Please download the key before continuing. If you do not save it locally, you cannot decrypt your backups later.', - 'backwpup' - ), - ) - ); - } - } - - public function save_post_form() { - - if ( ! current_user_can( 'backwpup_settings' ) ) { - return; - } - - // Set default options if button clicked. - if ( isset( $_POST['default_settings'] ) && $_POST['default_settings'] ) { // phpcs:ignore - delete_site_option( 'backwpup_cfg_showadminbar' ); - delete_site_option( 'backwpup_cfg_showfoldersize' ); - delete_site_option( 'backwpup_cfg_jobstepretry' ); - delete_site_option( 'backwpup_cfg_jobmaxexecutiontime' ); - delete_site_option( 'backwpup_cfg_loglevel' ); - delete_site_option( 'backwpup_cfg_jobwaittimems' ); - delete_site_option( 'backwpup_cfg_jobrunauthkey' ); - delete_site_option( 'backwpup_cfg_jobdooutput' ); - delete_site_option( 'backwpup_cfg_windows' ); - delete_site_option( 'backwpup_cfg_maxlogs' ); - delete_site_option( 'backwpup_cfg_gzlogs' ); - delete_site_option( 'backwpup_cfg_protectfolders' ); - delete_site_option( 'backwpup_cfg_authentication' ); - delete_site_option( 'backwpup_cfg_logfolder' ); - delete_site_option( 'backwpup_cfg_dropboxappkey' ); - delete_site_option( 'backwpup_cfg_dropboxappsecret' ); - delete_site_option( 'backwpup_cfg_dropboxsandboxappkey' ); - delete_site_option( 'backwpup_cfg_dropboxsandboxappsecret' ); - delete_site_option( 'backwpup_cfg_sugarsynckey' ); - delete_site_option( 'backwpup_cfg_sugarsyncsecret' ); - delete_site_option( 'backwpup_cfg_sugarsyncappid' ); - delete_site_option( 'backwpup_cfg_hash' ); + public function admin_print_scripts() + { + $suffix = (defined('SCRIPT_DEBUG') && SCRIPT_DEBUG) ? '' : '.min'; + + wp_enqueue_script( + 'backwpuppagesettings', + untrailingslashit(BackWPup::get_plugin_data('URL')) . "/assets/js/page_settings{$suffix}.js", + [ + 'jquery', + 'backwpupgeneral', + 'backwpup_clipboard', + ], + filemtime(untrailingslashit(BackWPup::get_plugin_data('plugindir')) . "/assets/js/page_settings{$suffix}.js"), + true + ); + + if (\BackWPup::is_pro()) { + wp_enqueue_script( + 'backwpuppagesettings-encryption', + untrailingslashit(BackWPup::get_plugin_data('URL')) . "/assets/js/pro/settings-encryption{$suffix}.js", + [ + 'underscore', + 'jquery', + 'backwpuppagesettings', + 'thickbox', + ], + filemtime(untrailingslashit(BackWPup::get_plugin_data('plugindir')) . "/assets/js/pro/settings-encryption{$suffix}.js"), + true + ); + + wp_localize_script( + 'backwpuppagesettings-encryption', + 'settingsEncryptionVariables', + [ + 'validPublicKey' => esc_html__('Public key is valid.', 'backwpup'), + 'invalidPublicKey' => esc_html__('Public key is invalid.', 'backwpup'), + 'privateKeyMissed' => esc_html__('Please enter your private key.', 'backwpup'), + 'publicKeyMissed' => esc_html__( + 'Please enter a public key first, or generate a key pair.', + 'backwpup' + ), + 'mustDownloadPrivateKey' => esc_html__( + 'Please download the private key before continuing. If you do not save it locally, you cannot decrypt your backups later.', + 'backwpup' + ), + 'mustDownloadSymmetricKey' => esc_html__( + 'Please download the key before continuing. If you do not save it locally, you cannot decrypt your backups later.', + 'backwpup' + ), + ] + ); + } + } + + public function save_post_form() + { + if (!current_user_can('backwpup_settings')) { + return; + } + + // Set default options if button clicked. + if (isset($_POST['default_settings']) && $_POST['default_settings']) { // phpcs:ignore + delete_site_option('backwpup_cfg_showadminbar'); + delete_site_option('backwpup_cfg_showfoldersize'); + delete_site_option('backwpup_cfg_jobstepretry'); + delete_site_option('backwpup_cfg_jobmaxexecutiontime'); + delete_site_option('backwpup_cfg_loglevel'); + delete_site_option('backwpup_cfg_jobwaittimems'); + delete_site_option('backwpup_cfg_jobrunauthkey'); + delete_site_option('backwpup_cfg_jobdooutput'); + delete_site_option('backwpup_cfg_windows'); + delete_site_option('backwpup_cfg_maxlogs'); + delete_site_option('backwpup_cfg_gzlogs'); + delete_site_option('backwpup_cfg_protectfolders'); + delete_site_option('backwpup_cfg_authentication'); + delete_site_option('backwpup_cfg_logfolder'); + delete_site_option('backwpup_cfg_dropboxappkey'); + delete_site_option('backwpup_cfg_dropboxappsecret'); + delete_site_option('backwpup_cfg_dropboxsandboxappkey'); + delete_site_option('backwpup_cfg_dropboxsandboxappsecret'); + delete_site_option('backwpup_cfg_sugarsynckey'); + delete_site_option('backwpup_cfg_sugarsyncsecret'); + delete_site_option('backwpup_cfg_sugarsyncappid'); + delete_site_option('backwpup_cfg_hash'); delete_site_option('backwpup_cfg_keepplugindata'); - foreach ( $this->settings_updaters as $setting ) { - $setting->reset(); - } + foreach ($this->settings_updaters as $setting) { + $setting->reset(); + } delete_site_option(self::LICENSE_INSTANCE_KEY); delete_site_option(self::LICENSE_API_KEY); delete_site_option(self::LICENSE_PRODUCT_ID); delete_site_option(self::LICENSE_STATUS); - BackWPup_Option::default_site_options(); - BackWPup_Admin::message( __( 'Settings reset to default', 'backwpup' ) ); - - return; - } - - foreach ( $this->settings_updaters as $setting ) { - $setting->update(); - } - - update_site_option( 'backwpup_cfg_showadminbar', ! empty( $_POST['showadminbarmenu'] ) ); - update_site_option( 'backwpup_cfg_showfoldersize', ! empty( $_POST['showfoldersize'] ) ); - - if ( empty( $_POST['jobstepretry'] ) || 100 < $_POST['jobstepretry'] || 1 > $_POST['jobstepretry'] ) { - $_POST['jobstepretry'] = 3; - } - - update_site_option( 'backwpup_cfg_jobstepretry', absint( $_POST['jobstepretry'] ) ); - - if ( (int) $_POST['jobmaxexecutiontime'] > 300 ) { - $_POST['jobmaxexecutiontime'] = 300; - } - - update_site_option( 'backwpup_cfg_jobmaxexecutiontime', absint( $_POST['jobmaxexecutiontime'] ) ); - update_site_option( - 'backwpup_cfg_loglevel', - in_array( - $_POST['loglevel'], - array( 'normal_translated', 'normal', 'debug_translated', 'debug' ), - true - ) ? $_POST['loglevel'] : 'normal_translated' - ); - update_site_option( 'backwpup_cfg_jobwaittimems', absint( $_POST['jobwaittimems'] ) ); - update_site_option( 'backwpup_cfg_jobdooutput', ! empty( $_POST['jobdooutput'] ) ); - update_site_option( 'backwpup_cfg_windows', ! empty( $_POST['windows'] ) ); - - update_site_option( 'backwpup_cfg_maxlogs', absint( $_POST['maxlogs'] ) ); - update_site_option( 'backwpup_cfg_gzlogs', ! empty( $_POST['gzlogs'] ) ); - update_site_option( 'backwpup_cfg_protectfolders', ! empty( $_POST['protectfolders'] ) ); - - $_POST['jobrunauthkey'] = preg_replace( '/[^a-zA-Z0-9]/', '', trim( $_POST['jobrunauthkey'] ) ); - - update_site_option( 'backwpup_cfg_jobrunauthkey', $_POST['jobrunauthkey'] ); - - $_POST['logfolder'] = trailingslashit( - str_replace( '\\', '/', trim( stripslashes( sanitize_text_field( $_POST['logfolder'] ) ) ) ) - ); - - //set def. folders - if ( empty( $_POST['logfolder'] ) || $_POST['logfolder'] === '/' ) { - delete_site_option( 'backwpup_cfg_logfolder' ); - BackWPup_Option::default_site_options(); - } else { - update_site_option( 'backwpup_cfg_logfolder', $_POST['logfolder'] ); - } - - $authentication = get_site_option( - 'backwpup_cfg_authentication', - array( - 'method' => '', - 'basic_user' => '', - 'basic_password' => '', - 'user_id' => 0, - 'query_arg' => '', - ) - ); - $authentication['method'] = ( in_array( - $_POST['authentication_method'], - array( 'user', 'basic', 'query_arg' ), - true - ) ) ? $_POST['authentication_method'] : ''; - $authentication['basic_user'] = sanitize_text_field( $_POST['authentication_basic_user'] ); - $authentication['basic_password'] = BackWPup_Encryption::encrypt( - (string) $_POST['authentication_basic_password'] - ); - $authentication['query_arg'] = sanitize_text_field( $_POST['authentication_query_arg'] ); - $authentication['user_id'] = absint( $_POST['authentication_user_id'] ); - update_site_option( 'backwpup_cfg_authentication', $authentication ); - delete_site_transient( 'backwpup_cookies' ); + BackWPup_Option::default_site_options(); + BackWPup_Admin::message(__('Settings reset to default', 'backwpup')); + + return; + } + + foreach ($this->settings_updaters as $setting) { + $setting->update(); + } + + update_site_option('backwpup_cfg_showadminbar', !empty($_POST['showadminbarmenu'])); + update_site_option('backwpup_cfg_showfoldersize', !empty($_POST['showfoldersize'])); + + if (empty($_POST['jobstepretry']) || 100 < $_POST['jobstepretry'] || 1 > $_POST['jobstepretry']) { + $_POST['jobstepretry'] = 3; + } + + update_site_option('backwpup_cfg_jobstepretry', absint($_POST['jobstepretry'])); + + if ((int) $_POST['jobmaxexecutiontime'] > 300) { + $_POST['jobmaxexecutiontime'] = 300; + } + + update_site_option('backwpup_cfg_jobmaxexecutiontime', absint($_POST['jobmaxexecutiontime'])); + update_site_option( + 'backwpup_cfg_loglevel', + in_array( + $_POST['loglevel'], + ['normal_translated', 'normal', 'debug_translated', 'debug'], + true + ) ? $_POST['loglevel'] : 'normal_translated' + ); + update_site_option('backwpup_cfg_jobwaittimems', absint($_POST['jobwaittimems'])); + update_site_option('backwpup_cfg_jobdooutput', !empty($_POST['jobdooutput'])); + update_site_option('backwpup_cfg_windows', !empty($_POST['windows'])); + + update_site_option('backwpup_cfg_maxlogs', absint($_POST['maxlogs'])); + update_site_option('backwpup_cfg_gzlogs', !empty($_POST['gzlogs'])); + update_site_option('backwpup_cfg_protectfolders', !empty($_POST['protectfolders'])); + + $_POST['jobrunauthkey'] = preg_replace('/[^a-zA-Z0-9]/', '', trim($_POST['jobrunauthkey'])); + + update_site_option('backwpup_cfg_jobrunauthkey', $_POST['jobrunauthkey']); + + $_POST['logfolder'] = trailingslashit( + str_replace('\\', '/', trim(stripslashes(sanitize_text_field($_POST['logfolder'])))) + ); + + //set def. folders + if (empty($_POST['logfolder']) || $_POST['logfolder'] === '/') { + delete_site_option('backwpup_cfg_logfolder'); + BackWPup_Option::default_site_options(); + } else { + update_site_option('backwpup_cfg_logfolder', $_POST['logfolder']); + } + + $authentication = get_site_option( + 'backwpup_cfg_authentication', + [ + 'method' => '', + 'basic_user' => '', + 'basic_password' => '', + 'user_id' => 0, + 'query_arg' => '', + ] + ); + $authentication['method'] = (in_array( + $_POST['authentication_method'], + ['user', 'basic', 'query_arg'], + true + )) ? $_POST['authentication_method'] : ''; + $authentication['basic_user'] = sanitize_text_field($_POST['authentication_basic_user']); + $authentication['basic_password'] = BackWPup_Encryption::encrypt( + (string) $_POST['authentication_basic_password'] + ); + $authentication['query_arg'] = sanitize_text_field($_POST['authentication_query_arg']); + $authentication['user_id'] = absint($_POST['authentication_user_id']); + update_site_option('backwpup_cfg_authentication', $authentication); + delete_site_transient('backwpup_cookies'); update_site_option('backwpup_cfg_keepplugindata', !empty($_POST['keepplugindata'])); @@ -487,120 +488,120 @@ public function save_post_form() { BackWPup_Admin::message(__('Settings saved', 'backwpup')); } - public function page() { - - ?> + public function page() + { + ?>

+ esc_html__('%s › Settings', 'backwpup'), + BackWPup::get_plugin_data('name') + ); ?>

'; - foreach ( $tabs as $id => $name ) { - echo '' . esc_attr( $name ) . ''; - } - echo ''; - BackWPup_Admin::display_messages(); - ?> - -
- - + $tabs = []; + $tabs['general'] = esc_html__('General', 'backwpup'); + $tabs['job'] = esc_html__('Jobs', 'backwpup'); + if (BackWPup::is_pro()) { + $tabs['encryption'] = esc_html__('Encryption', 'backwpup'); + } + $tabs['log'] = esc_html__('Logs', 'backwpup'); + $tabs['net'] = esc_html__('Network', 'backwpup'); + $tabs['apikey'] = esc_html__('API Keys', 'backwpup'); + $tabs['information'] = esc_html__('Information', 'backwpup'); + if (BackWPup::is_pro()) { + $tabs['license'] = esc_html__('License', 'backwpup'); + } + $tabs = apply_filters('backwpup_page_settings_tab', $tabs); + echo ''; + BackWPup_Admin::display_messages(); ?> + + + +
-

-

+

+

- + - +
- +
- +
-

-

+

+

- + @@ -621,123 +622,123 @@ public function page() { /> + get_site_option('backwpup_cfg_keepplugindata'), + true + ); ?> /> + 'Keep BackWPup data stored in the database after uninstall', + 'backwpup' + ); ?>
- +
- +

+ 'Every time BackWPup runs a backup job, a log file is being generated. Choose where to store your log files and how many of them.', + 'backwpup' + ); ?>

- + - + @@ -748,47 +749,42 @@ public function page() {

+ 'There are a couple of general options for backup jobs. Set them here.', + 'backwpup' + ); ?>

+ get_site_option('backwpup_cfg_logfolder') + ); ?>" class="regular-text code"/>

array() ) - ), - '' . trailingslashit( - str_replace( '\\', '/', WP_CONTENT_DIR ) - ) . '' - ); ?> + wp_kses( + __( + 'You can use absolute or relative path! Relative path is relative to %s.', + 'backwpup' + ), + ['code' => []] + ), + '' . trailingslashit( + str_replace('\\', '/', WP_CONTENT_DIR) + ) . '' + ); ?>

- + get_site_option('backwpup_cfg_maxlogs') + ); ?>" class="small-text"/> +
+ 'Compression', + 'backwpup' + ); ?>
- +

+ 'Debug log has much more information than normal logs. It is for support and should be handled carefully. For support is the best to use a not translated log file. Usage of not translated logs can reduce the PHP memory usage too.', + 'backwpup' + ); ?>

- + - + - + - + @@ -913,157 +911,156 @@ public function page() { settings_views as $setting ) { - $setting->tab(); - } - ?> + foreach ($this->settings_views as $setting) { + $setting->tab(); + } ?>

%s', 'backwpup' ), - array( 'code' => array() ) - ), - site_url( 'wp-cron.php' ) - ); ?> + wp_kses( + __('Authentication for %s', 'backwpup'), + ['code' => []] + ), + site_url('wp-cron.php') + ); ?>

+ 'If you protected your blog with HTTP basic authentication (.htaccess), or you use a Plugin to secure wp-cron.php, then use the authentication methods below.', + 'backwpup' + ); ?>

'', - 'basic_user' => '', - 'basic_password' => '', - 'user_id' => 0, - 'query_arg' => '', - ) ); - ?> + $authentication = get_site_option( + 'backwpup_cfg_authentication', + [ + 'method' => '', + 'basic_user' => '', + 'basic_password' => '', + 'user_id' => 0, + 'query_arg' => '', + ] + ); ?>
+ value="" class="small-text"/>
- +
@@ -797,113 +793,115 @@ public function page() {
+ get_site_option('backwpup_cfg_jobrunauthkey') + ); ?>" class="text code"/>

+ 'Will be used to protect job starts from unauthorized person.', + 'backwpup' + ); ?>

- +

+ 'This adds short pauses to the process. Can be used to reduce the CPU load.', + 'backwpup' + ); ?>

+ 'Enable an empty Output on backup working.', + 'backwpup' + ); ?>

+ 'This do an empty output on job working. This can help in some situations or can break the working. You must test it.', + 'backwpup' + ); ?>

+ 'Enable compatibility with IIS on Windows.', + 'backwpup' + ); ?>

- bug #43817), which is triggered on some versions of Windows and IIS. Checking this box will enable a workaround for that bug. Only enable if you are getting errors about “Permission denied” in your logs.', - 'backwpup' - ), - array( 'a' => array() ) ) ?> + bug #43817), which is triggered on some versions of Windows and IIS. Checking this box will enable a workaround for that bug. Only enable if you are getting errors about “Permission denied” in your logs.', + 'backwpup' + ), + ['a' => []] + ); ?>

- + - > + > - > + > - > - + > + - > + >
- +
+ $authentication['basic_user'] + ); ?>" class="regular-text" autocomplete="off"/>
+ BackWPup_Encryption::decrypt($authentication['basic_password']) + ); ?>" class="regular-text" autocomplete="off"/>
+ 'Select WordPress User', + 'backwpup' + ); ?>
? + $authentication['query_arg'] + ); ?>" class="regular-text"/>
@@ -1071,7 +1068,7 @@ public function page() {
- +
@@ -1080,19 +1077,18 @@ public function page() {

+ esc_html_e( + 'Experiencing an issue and need to contact BackWPup support? Click the link below to get debug information you can send to us.', + 'backwpup' + ); ?>

"> - + 'Debug Info', + 'backwpup' + ); ?>"> +

@@ -1100,59 +1096,57 @@ class="thickbox button button-primary" title="

+ 'You will find debug information below. Click the button to copy the debug info to send to support.', + 'backwpup' + ); ?>

Note: ' . - 'Would you like faster, more streamlined support? Pro users can contact BackWPup from right within the plugin.', - 'backwpup' - ), - array( 'strong' => array() ) - ) ?> - - + echo wp_kses( + __( + 'Note: ' . + 'Would you like faster, more streamlined support? Pro users can contact BackWPup from right within the plugin.', + 'backwpup' + ), + ['strong' => []] + ); ?> + +

+ $html = ob_get_clean(); + echo apply_filters('backwpup_get_debug_info_text', $html); ?>

- +

+ foreach ($information as $item) { + echo esc_html($item['label']) . ': ' . esc_html($item['value']) . "\n"; + } ?>
'); - } elseif ($format === 'js') { - self::writeOutput(self::generateScript()); + if (count(static::$records)) { + if ($format === self::FORMAT_HTML) { + static::writeOutput(''); + } elseif ($format === self::FORMAT_JS) { + static::writeOutput(static::generateScript()); } - self::reset(); + static::resetStatic(); } } + public function close(): void + { + self::resetStatic(); + } + + public function reset() + { + parent::reset(); + + self::resetStatic(); + } + /** * Forget all logged records */ - public static function reset() + public static function resetStatic(): void { - self::$records = array(); + static::$records = []; } /** * Wrapper for register_shutdown_function to allow overriding */ - protected function registerShutdownFunction() + protected function registerShutdownFunction(): void { if (PHP_SAPI !== 'cli') { - register_shutdown_function(array('Monolog\Handler\BrowserConsoleHandler', 'send')); + register_shutdown_function(['Monolog\Handler\BrowserConsoleHandler', 'send']); } } /** * Wrapper for echo to allow overriding - * - * @param string $str */ - protected static function writeOutput($str) + protected static function writeOutput(string $str): void { echo $str; } @@ -109,42 +136,55 @@ protected static function writeOutput($str) * If Content-Type is anything else -> unknown * * @return string One of 'js', 'html' or 'unknown' + * @phpstan-return self::FORMAT_* */ - protected static function getResponseFormat() + protected static function getResponseFormat(): string { // Check content type foreach (headers_list() as $header) { if (stripos($header, 'content-type:') === 0) { - // This handler only works with HTML and javascript outputs - // text/javascript is obsolete in favour of application/javascript, but still used - if (stripos($header, 'application/javascript') !== false || stripos($header, 'text/javascript') !== false) { - return 'js'; - } - if (stripos($header, 'text/html') === false) { - return 'unknown'; - } - break; + return static::getResponseFormatFromContentType($header); } } - return 'html'; + return self::FORMAT_HTML; + } + + /** + * @return string One of 'js', 'html' or 'unknown' + * @phpstan-return self::FORMAT_* + */ + protected static function getResponseFormatFromContentType(string $contentType): string + { + // This handler only works with HTML and javascript outputs + // text/javascript is obsolete in favour of application/javascript, but still used + if (stripos($contentType, 'application/javascript') !== false || stripos($contentType, 'text/javascript') !== false) { + return self::FORMAT_JS; + } + + if (stripos($contentType, 'text/html') !== false) { + return self::FORMAT_HTML; + } + + return self::FORMAT_UNKNOWN; } - private static function generateScript() + private static function generateScript(): string { - $script = array(); - foreach (self::$records as $record) { - $context = self::dump('Context', $record['context']); - $extra = self::dump('Extra', $record['extra']); + $script = []; + foreach (static::$records as $record) { + $context = static::dump('Context', $record['context']); + $extra = static::dump('Extra', $record['extra']); if (empty($context) && empty($extra)) { - $script[] = self::call_array('log', self::handleStyles($record['formatted'])); + $script[] = static::call_array('log', static::handleStyles($record['formatted'])); } else { - $script = array_merge($script, - array(self::call_array('groupCollapsed', self::handleStyles($record['formatted']))), + $script = array_merge( + $script, + [static::call_array('groupCollapsed', static::handleStyles($record['formatted']))], $context, $extra, - array(self::call('groupEnd')) + [static::call('groupEnd')] ); } } @@ -152,31 +192,35 @@ private static function generateScript() return "(function (c) {if (c && c.groupCollapsed) {\n" . implode("\n", $script) . "\n}})(console);"; } - private static function handleStyles($formatted) + /** + * @return string[] + */ + private static function handleStyles(string $formatted): array { - $args = array(self::quote('font-weight: normal')); + $args = []; $format = '%c' . $formatted; preg_match_all('/\[\[(.*?)\]\]\{([^}]*)\}/s', $format, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER); foreach (array_reverse($matches) as $match) { - $args[] = self::quote(self::handleCustomStyles($match[2][0], $match[1][0])); $args[] = '"font-weight: normal"'; + $args[] = static::quote(static::handleCustomStyles($match[2][0], $match[1][0])); $pos = $match[0][1]; - $format = substr($format, 0, $pos) . '%c' . $match[1][0] . '%c' . substr($format, $pos + strlen($match[0][0])); + $format = Utils::substr($format, 0, $pos) . '%c' . $match[1][0] . '%c' . Utils::substr($format, $pos + strlen($match[0][0])); } - array_unshift($args, self::quote($format)); + $args[] = static::quote('font-weight: normal'); + $args[] = static::quote($format); - return $args; + return array_reverse($args); } - private static function handleCustomStyles($style, $string) + private static function handleCustomStyles(string $style, string $string): string { - static $colors = array('blue', 'green', 'red', 'magenta', 'orange', 'black', 'grey'); - static $labels = array(); + static $colors = ['blue', 'green', 'red', 'magenta', 'orange', 'black', 'grey']; + static $labels = []; - return preg_replace_callback('/macro\s*:(.*?)(?:;|$)/', function ($m) use ($string, &$colors, &$labels) { + $style = preg_replace_callback('/macro\s*:(.*?)(?:;|$)/', function (array $m) use ($string, &$colors, &$labels) { if (trim($m[1]) === 'autolabel') { // Format the string as a label with consistent auto assigned background color if (!isset($labels[$string])) { @@ -189,41 +233,60 @@ private static function handleCustomStyles($style, $string) return $m[1]; }, $style); + + if (null === $style) { + $pcreErrorCode = preg_last_error(); + throw new \RuntimeException('Failed to run preg_replace_callback: ' . $pcreErrorCode . ' / ' . Utils::pcreLastErrorMessage($pcreErrorCode)); + } + + return $style; } - private static function dump($title, array $dict) + /** + * @param mixed[] $dict + * @return mixed[] + */ + private static function dump(string $title, array $dict): array { - $script = array(); + $script = []; $dict = array_filter($dict); if (empty($dict)) { return $script; } - $script[] = self::call('log', self::quote('%c%s'), self::quote('font-weight: bold'), self::quote($title)); + $script[] = static::call('log', static::quote('%c%s'), static::quote('font-weight: bold'), static::quote($title)); foreach ($dict as $key => $value) { $value = json_encode($value); if (empty($value)) { - $value = self::quote(''); + $value = static::quote(''); } - $script[] = self::call('log', self::quote('%s: %o'), self::quote($key), $value); + $script[] = static::call('log', static::quote('%s: %o'), static::quote((string) $key), $value); } return $script; } - private static function quote($arg) + private static function quote(string $arg): string { return '"' . addcslashes($arg, "\"\n\\") . '"'; } - private static function call() + /** + * @param mixed $args + */ + private static function call(...$args): string { - $args = func_get_args(); $method = array_shift($args); + if (!is_string($method)) { + throw new \UnexpectedValueException('Expected the first arg to be a string, got: '.var_export($method, true)); + } - return self::call_array($method, $args); + return static::call_array($method, $args); } - private static function call_array($method, array $args) + /** + * @param mixed[] $args + */ + private static function call_array(string $method, array $args): string { return 'c.' . $method . '(' . implode(', ', $args) . ');'; } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/BufferHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/BufferHandler.php index 72f89535..fcce5d63 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/BufferHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/BufferHandler.php @@ -1,4 +1,4 @@ - + * + * @phpstan-import-type Record from \Monolog\Logger */ -class BufferHandler extends AbstractHandler +class BufferHandler extends AbstractHandler implements ProcessableHandlerInterface, FormattableHandlerInterface { + use ProcessableHandlerTrait; + + /** @var HandlerInterface */ protected $handler; + /** @var int */ protected $bufferSize = 0; + /** @var int */ protected $bufferLimit; + /** @var bool */ protected $flushOnOverflow; - protected $buffer = array(); + /** @var Record[] */ + protected $buffer = []; + /** @var bool */ protected $initialized = false; /** * @param HandlerInterface $handler Handler. * @param int $bufferLimit How many entries should be buffered at most, beyond that the oldest items are removed from the buffer. - * @param int $level The minimum logging level at which this handler will be triggered - * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not - * @param Boolean $flushOnOverflow If true, the buffer is flushed when the max size has been reached, by default oldest entries are discarded + * @param bool $flushOnOverflow If true, the buffer is flushed when the max size has been reached, by default oldest entries are discarded */ - public function __construct(HandlerInterface $handler, $bufferLimit = 0, $level = Logger::DEBUG, $bubble = true, $flushOnOverflow = false) + public function __construct(HandlerInterface $handler, int $bufferLimit = 0, $level = Logger::DEBUG, bool $bubble = true, bool $flushOnOverflow = false) { parent::__construct($level, $bubble); $this->handler = $handler; - $this->bufferLimit = (int) $bufferLimit; + $this->bufferLimit = $bufferLimit; $this->flushOnOverflow = $flushOnOverflow; } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function handle(array $record) + public function handle(array $record): bool { if ($record['level'] < $this->level) { return false; @@ -56,7 +66,7 @@ public function handle(array $record) if (!$this->initialized) { // __destructor() doesn't get called on Fatal errors - register_shutdown_function(array($this, 'close')); + register_shutdown_function([$this, 'close']); $this->initialized = true; } @@ -70,9 +80,8 @@ public function handle(array $record) } if ($this->processors) { - foreach ($this->processors as $processor) { - $record = call_user_func($processor, $record); - } + /** @var Record $record */ + $record = $this->processRecord($record); } $this->buffer[] = $record; @@ -81,7 +90,7 @@ public function handle(array $record) return false === $this->bubble; } - public function flush() + public function flush(): void { if ($this->bufferSize === 0) { return; @@ -99,19 +108,60 @@ public function __destruct() } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function close() + public function close(): void { $this->flush(); + + $this->handler->close(); } /** * Clears the buffer without flushing any messages down to the wrapped handler. */ - public function clear() + public function clear(): void { $this->bufferSize = 0; - $this->buffer = array(); + $this->buffer = []; + } + + public function reset() + { + $this->flush(); + + parent::reset(); + + $this->resetProcessors(); + + if ($this->handler instanceof ResettableInterface) { + $this->handler->reset(); + } + } + + /** + * {@inheritDoc} + */ + public function setFormatter(FormatterInterface $formatter): HandlerInterface + { + if ($this->handler instanceof FormattableHandlerInterface) { + $this->handler->setFormatter($formatter); + + return $this; + } + + throw new \UnexpectedValueException('The nested handler of type '.get_class($this->handler).' does not support formatters.'); + } + + /** + * {@inheritDoc} + */ + public function getFormatter(): FormatterInterface + { + if ($this->handler instanceof FormattableHandlerInterface) { + return $this->handler->getFormatter(); + } + + throw new \UnexpectedValueException('The nested handler of type '.get_class($this->handler).' does not support formatters.'); } } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/ChromePHPHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/ChromePHPHandler.php index 785cb0c9..234ecf61 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/ChromePHPHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/ChromePHPHandler.php @@ -1,4 +1,4 @@ - + * + * @phpstan-import-type Record from \Monolog\Logger */ class ChromePHPHandler extends AbstractProcessingHandler { + use WebRequestRecognizerTrait; + /** * Version of the extension */ - const VERSION = '4.0'; + protected const VERSION = '4.0'; /** * Header name */ - const HEADER_NAME = 'X-ChromeLogger-Data'; - + protected const HEADER_NAME = 'X-ChromeLogger-Data'; + /** * Regular expression to detect supported browsers (matches any Chrome, or Firefox 43+) */ - const USER_AGENT_REGEX = '{\b(?:Chrome/\d+(?:\.\d+)*|HeadlessChrome|Firefox/(?:4[3-9]|[5-9]\d|\d{3,})(?:\.\d)*)\b}'; + protected const USER_AGENT_REGEX = '{\b(?:Chrome/\d+(?:\.\d+)*|HeadlessChrome|Firefox/(?:4[3-9]|[5-9]\d|\d{3,})(?:\.\d)*)\b}'; + /** @var bool */ protected static $initialized = false; /** * Tracks whether we sent too much data * - * Chrome limits the headers to 256KB, so when we sent 240KB we stop sending + * Chrome limits the headers to 4KB, so when we sent 3KB we stop sending * - * @var Boolean + * @var bool */ protected static $overflowed = false; - protected static $json = array( + /** @var mixed[] */ + protected static $json = [ 'version' => self::VERSION, - 'columns' => array('label', 'log', 'backtrace', 'type'), - 'rows' => array(), - ); + 'columns' => ['label', 'log', 'backtrace', 'type'], + 'rows' => [], + ]; + /** @var bool */ protected static $sendHeaders = true; - /** - * @param int $level The minimum logging level at which this handler will be triggered - * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not - */ - public function __construct($level = Logger::DEBUG, $bubble = true) + public function __construct($level = Logger::DEBUG, bool $bubble = true) { parent::__construct($level, $bubble); if (!function_exists('json_encode')) { @@ -70,17 +75,23 @@ public function __construct($level = Logger::DEBUG, $bubble = true) } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function handleBatch(array $records) + public function handleBatch(array $records): void { - $messages = array(); + if (!$this->isWebRequest()) { + return; + } + + $messages = []; foreach ($records as $record) { if ($record['level'] < $this->level) { continue; } - $messages[] = $this->processRecord($record); + /** @var Record $message */ + $message = $this->processRecord($record); + $messages[] = $message; } if (!empty($messages)) { @@ -93,7 +104,7 @@ public function handleBatch(array $records) /** * {@inheritDoc} */ - protected function getDefaultFormatter() + protected function getDefaultFormatter(): FormatterInterface { return new ChromePHPFormatter(); } @@ -103,10 +114,13 @@ protected function getDefaultFormatter() * * @see sendHeader() * @see send() - * @param array $record */ - protected function write(array $record) + protected function write(array $record): void { + if (!$this->isWebRequest()) { + return; + } + self::$json['rows'][] = $record['formatted']; $this->send(); @@ -117,7 +131,7 @@ protected function write(array $record) * * @see sendHeader() */ - protected function send() + protected function send(): void { if (self::$overflowed || !self::$sendHeaders) { return; @@ -131,40 +145,37 @@ protected function send() return; } - self::$json['request_uri'] = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : ''; + self::$json['request_uri'] = $_SERVER['REQUEST_URI'] ?? ''; } - $json = @json_encode(self::$json); - $data = base64_encode(utf8_encode($json)); - if (strlen($data) > 240 * 1024) { + $json = Utils::jsonEncode(self::$json, Utils::DEFAULT_JSON_FLAGS & ~JSON_UNESCAPED_UNICODE, true); + $data = base64_encode($json); + if (strlen($data) > 3 * 1024) { self::$overflowed = true; - $record = array( + $record = [ 'message' => 'Incomplete logs, chrome header size limit reached', - 'context' => array(), + 'context' => [], 'level' => Logger::WARNING, 'level_name' => Logger::getLevelName(Logger::WARNING), 'channel' => 'monolog', - 'datetime' => new \DateTime(), - 'extra' => array(), - ); + 'datetime' => new \DateTimeImmutable(), + 'extra' => [], + ]; self::$json['rows'][count(self::$json['rows']) - 1] = $this->getFormatter()->format($record); - $json = @json_encode(self::$json); - $data = base64_encode(utf8_encode($json)); + $json = Utils::jsonEncode(self::$json, Utils::DEFAULT_JSON_FLAGS & ~JSON_UNESCAPED_UNICODE, true); + $data = base64_encode($json); } if (trim($data) !== '') { - $this->sendHeader(self::HEADER_NAME, $data); + $this->sendHeader(static::HEADER_NAME, $data); } } /** * Send header string to the client - * - * @param string $header - * @param string $content */ - protected function sendHeader($header, $content) + protected function sendHeader(string $header, string $content): void { if (!headers_sent() && self::$sendHeaders) { header(sprintf('%s: %s', $header, $content)); @@ -173,39 +184,13 @@ protected function sendHeader($header, $content) /** * Verifies if the headers are accepted by the current user agent - * - * @return Boolean */ - protected function headersAccepted() + protected function headersAccepted(): bool { if (empty($_SERVER['HTTP_USER_AGENT'])) { return false; } - return preg_match(self::USER_AGENT_REGEX, $_SERVER['HTTP_USER_AGENT']); - } - - /** - * BC getter for the sendHeaders property that has been made static - */ - public function __get($property) - { - if ('sendHeaders' !== $property) { - throw new \InvalidArgumentException('Undefined property '.$property); - } - - return static::$sendHeaders; - } - - /** - * BC setter for the sendHeaders property that has been made static - */ - public function __set($property, $value) - { - if ('sendHeaders' !== $property) { - throw new \InvalidArgumentException('Undefined property '.$property); - } - - static::$sendHeaders = $value; + return preg_match(static::USER_AGENT_REGEX, $_SERVER['HTTP_USER_AGENT']) === 1; } } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/CouchDBHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/CouchDBHandler.php index cc986971..52657613 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/CouchDBHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/CouchDBHandler.php @@ -1,4 +1,4 @@ -options = array_merge(array( + $this->options = array_merge([ 'host' => 'localhost', 'port' => 5984, 'dbname' => 'logger', 'username' => null, 'password' => null, - ), $options); + ], $options); parent::__construct($level, $bubble); } @@ -39,7 +44,7 @@ public function __construct(array $options = array(), $level = Logger::DEBUG, $b /** * {@inheritDoc} */ - protected function write(array $record) + protected function write(array $record): void { $basicAuth = null; if ($this->options['username']) { @@ -47,17 +52,17 @@ protected function write(array $record) } $url = 'http://'.$basicAuth.$this->options['host'].':'.$this->options['port'].'/'.$this->options['dbname']; - $context = stream_context_create(array( - 'http' => array( + $context = stream_context_create([ + 'http' => [ 'method' => 'POST', 'content' => $record['formatted'], 'ignore_errors' => true, 'max_redirects' => 0, 'header' => 'Content-type: application/json', - ), - )); + ], + ]); - if (false === @file_get_contents($url, null, $context)) { + if (false === @file_get_contents($url, false, $context)) { throw new \RuntimeException(sprintf('Could not connect to %s', $url)); } } @@ -65,7 +70,7 @@ protected function write(array $record) /** * {@inheritDoc} */ - protected function getDefaultFormatter() + protected function getDefaultFormatter(): FormatterInterface { return new JsonFormatter(JsonFormatter::BATCH_MODE_JSON, false); } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/CubeHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/CubeHandler.php index 96b3ca0c..3535a4fc 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/CubeHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/CubeHandler.php @@ -1,4 +1,4 @@ - + * @deprecated Since 2.8.0 and 3.2.0, Cube appears abandoned and thus we will drop this handler in Monolog 4 */ class CubeHandler extends AbstractProcessingHandler { - private $udpConnection; - private $httpConnection; + /** @var resource|\Socket|null */ + private $udpConnection = null; + /** @var resource|\CurlHandle|null */ + private $httpConnection = null; + /** @var string */ private $scheme; + /** @var string */ private $host; + /** @var int */ private $port; - private $acceptedSchemes = array('http', 'udp'); + /** @var string[] */ + private $acceptedSchemes = ['http', 'udp']; /** * Create a Cube handler @@ -35,23 +43,24 @@ class CubeHandler extends AbstractProcessingHandler * A valid url must consist of three parts : protocol://host:port * Only valid protocols used by Cube are http and udp */ - public function __construct($url, $level = Logger::DEBUG, $bubble = true) + public function __construct(string $url, $level = Logger::DEBUG, bool $bubble = true) { $urlInfo = parse_url($url); - if (!isset($urlInfo['scheme'], $urlInfo['host'], $urlInfo['port'])) { + if ($urlInfo === false || !isset($urlInfo['scheme'], $urlInfo['host'], $urlInfo['port'])) { throw new \UnexpectedValueException('URL "'.$url.'" is not valid'); } if (!in_array($urlInfo['scheme'], $this->acceptedSchemes)) { throw new \UnexpectedValueException( 'Invalid protocol (' . $urlInfo['scheme'] . ').' - . ' Valid options are ' . implode(', ', $this->acceptedSchemes)); + . ' Valid options are ' . implode(', ', $this->acceptedSchemes) + ); } $this->scheme = $urlInfo['scheme']; $this->host = $urlInfo['host']; - $this->port = $urlInfo['port']; + $this->port = (int) $urlInfo['port']; parent::__construct($level, $bubble); } @@ -62,50 +71,53 @@ public function __construct($url, $level = Logger::DEBUG, $bubble = true) * @throws \LogicException when unable to connect to the socket * @throws MissingExtensionException when there is no socket extension */ - protected function connectUdp() + protected function connectUdp(): void { if (!extension_loaded('sockets')) { throw new MissingExtensionException('The sockets extension is required to use udp URLs with the CubeHandler'); } - $this->udpConnection = socket_create(AF_INET, SOCK_DGRAM, 0); - if (!$this->udpConnection) { + $udpConnection = socket_create(AF_INET, SOCK_DGRAM, 0); + if (false === $udpConnection) { throw new \LogicException('Unable to create a socket'); } + $this->udpConnection = $udpConnection; if (!socket_connect($this->udpConnection, $this->host, $this->port)) { throw new \LogicException('Unable to connect to the socket at ' . $this->host . ':' . $this->port); } } /** - * Establish a connection to a http server - * @throws \LogicException when no curl extension + * Establish a connection to an http server + * + * @throws \LogicException when unable to connect to the socket + * @throws MissingExtensionException when no curl extension */ - protected function connectHttp() + protected function connectHttp(): void { if (!extension_loaded('curl')) { - throw new \LogicException('The curl extension is needed to use http URLs with the CubeHandler'); + throw new MissingExtensionException('The curl extension is required to use http URLs with the CubeHandler'); } - $this->httpConnection = curl_init('http://'.$this->host.':'.$this->port.'/1.0/event/put'); - - if (!$this->httpConnection) { + $httpConnection = curl_init('http://'.$this->host.':'.$this->port.'/1.0/event/put'); + if (false === $httpConnection) { throw new \LogicException('Unable to connect to ' . $this->host . ':' . $this->port); } + $this->httpConnection = $httpConnection; curl_setopt($this->httpConnection, CURLOPT_CUSTOMREQUEST, "POST"); curl_setopt($this->httpConnection, CURLOPT_RETURNTRANSFER, true); } /** - * {@inheritdoc} + * {@inheritDoc} */ - protected function write(array $record) + protected function write(array $record): void { $date = $record['datetime']; - $data = array('time' => $date->format('Y-m-d\TH:i:s.uO')); + $data = ['time' => $date->format('Y-m-d\TH:i:s.uO')]; unset($record['datetime']); if (isset($record['context']['type'])) { @@ -119,13 +131,13 @@ protected function write(array $record) $data['data']['level'] = $record['level']; if ($this->scheme === 'http') { - $this->writeHttp(json_encode($data)); + $this->writeHttp(Utils::jsonEncode($data)); } else { - $this->writeUdp(json_encode($data)); + $this->writeUdp(Utils::jsonEncode($data)); } } - private function writeUdp($data) + private function writeUdp(string $data): void { if (!$this->udpConnection) { $this->connectUdp(); @@ -134,17 +146,21 @@ private function writeUdp($data) socket_send($this->udpConnection, $data, strlen($data), 0); } - private function writeHttp($data) + private function writeHttp(string $data): void { if (!$this->httpConnection) { $this->connectHttp(); } + if (null === $this->httpConnection) { + throw new \LogicException('No connection could be established'); + } + curl_setopt($this->httpConnection, CURLOPT_POSTFIELDS, '['.$data.']'); - curl_setopt($this->httpConnection, CURLOPT_HTTPHEADER, array( + curl_setopt($this->httpConnection, CURLOPT_HTTPHEADER, [ 'Content-Type: application/json', 'Content-Length: ' . strlen('['.$data.']'), - )); + ]); Curl\Util::execute($this->httpConnection, 5, false); } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/Curl/Util.php b/vendor/monolog/monolog/src/Monolog/Handler/Curl/Util.php index 48d30b35..7213e8ee 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/Curl/Util.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/Curl/Util.php @@ -1,4 +1,4 @@ - */ + private static $retriableErrorCodes = [ CURLE_COULDNT_RESOLVE_HOST, CURLE_COULDNT_CONNECT, CURLE_HTTP_NOT_FOUND, @@ -21,18 +29,21 @@ class Util CURLE_OPERATION_TIMEOUTED, CURLE_HTTP_POST_ERROR, CURLE_SSL_CONNECT_ERROR, - ); + ]; /** * Executes a CURL request with optional retries and exception on failure * - * @param resource $ch curl handler - * @throws \RuntimeException + * @param resource|CurlHandle $ch curl handler + * @param int $retries + * @param bool $closeAfterDone + * @return bool|string @see curl_exec */ - public static function execute($ch, $retries = 5, $closeAfterDone = true) + public static function execute($ch, int $retries = 5, bool $closeAfterDone = true) { while ($retries--) { - if (curl_exec($ch) === false) { + $curlResponse = curl_exec($ch); + if ($curlResponse === false) { $curlErrno = curl_errno($ch); if (false === in_array($curlErrno, self::$retriableErrorCodes, true) || !$retries) { @@ -42,7 +53,7 @@ public static function execute($ch, $retries = 5, $closeAfterDone = true) curl_close($ch); } - throw new \RuntimeException(sprintf('Curl error (code %s): %s', $curlErrno, $curlError)); + throw new \RuntimeException(sprintf('Curl error (code %d): %s', $curlErrno, $curlError)); } continue; @@ -51,7 +62,10 @@ public static function execute($ch, $retries = 5, $closeAfterDone = true) if ($closeAfterDone) { curl_close($ch); } - break; + + return $curlResponse; } + + return false; } } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/DeduplicationHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/DeduplicationHandler.php index 7778c22a..9b85ae7e 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/DeduplicationHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/DeduplicationHandler.php @@ -1,4 +1,4 @@ - + * + * @phpstan-import-type Record from \Monolog\Logger + * @phpstan-import-type LevelName from \Monolog\Logger + * @phpstan-import-type Level from \Monolog\Logger */ class DeduplicationHandler extends BufferHandler { @@ -41,7 +46,7 @@ class DeduplicationHandler extends BufferHandler protected $deduplicationStore; /** - * @var int + * @var Level */ protected $deduplicationLevel; @@ -58,11 +63,13 @@ class DeduplicationHandler extends BufferHandler /** * @param HandlerInterface $handler Handler. * @param string $deduplicationStore The file/path where the deduplication log should be kept - * @param int $deduplicationLevel The minimum logging level for log records to be looked at for deduplication purposes + * @param string|int $deduplicationLevel The minimum logging level for log records to be looked at for deduplication purposes * @param int $time The period (in seconds) during which duplicate entries should be suppressed after a given log is sent through - * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * + * @phpstan-param Level|LevelName|LogLevel::* $deduplicationLevel */ - public function __construct(HandlerInterface $handler, $deduplicationStore = null, $deduplicationLevel = Logger::ERROR, $time = 60, $bubble = true) + public function __construct(HandlerInterface $handler, ?string $deduplicationStore = null, $deduplicationLevel = Logger::ERROR, int $time = 60, bool $bubble = true) { parent::__construct($handler, 0, Logger::DEBUG, $bubble, false); @@ -71,7 +78,7 @@ public function __construct(HandlerInterface $handler, $deduplicationStore = nul $this->time = $time; } - public function flush() + public function flush(): void { if ($this->bufferSize === 0) { return; @@ -81,7 +88,6 @@ public function flush() foreach ($this->buffer as $record) { if ($record['level'] >= $this->deduplicationLevel) { - $passthru = $passthru || !$this->isDuplicate($record); if ($passthru) { $this->appendRecord($record); @@ -101,7 +107,10 @@ public function flush() } } - private function isDuplicate(array $record) + /** + * @phpstan-param Record $record + */ + private function isDuplicate(array $record): bool { if (!file_exists($this->deduplicationStore)) { return false; @@ -131,21 +140,26 @@ private function isDuplicate(array $record) return false; } - private function collectLogs() + private function collectLogs(): void { if (!file_exists($this->deduplicationStore)) { - return false; + return; } $handle = fopen($this->deduplicationStore, 'rw+'); + + if (!$handle) { + throw new \RuntimeException('Failed to open file for reading and writing: ' . $this->deduplicationStore); + } + flock($handle, LOCK_EX); - $validLogs = array(); + $validLogs = []; $timestampValidity = time() - $this->time; while (!feof($handle)) { $log = fgets($handle); - if (substr($log, 0, 10) >= $timestampValidity) { + if ($log && substr($log, 0, 10) >= $timestampValidity) { $validLogs[] = $log; } } @@ -162,7 +176,10 @@ private function collectLogs() $this->gc = false; } - private function appendRecord(array $record) + /** + * @phpstan-param Record $record + */ + private function appendRecord(array $record): void { file_put_contents($this->deduplicationStore, $record['datetime']->getTimestamp() . ':' . $record['level_name'] . ':' . preg_replace('{[\r\n].*}', '', $record['message']) . "\n", FILE_APPEND); } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/DoctrineCouchDBHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/DoctrineCouchDBHandler.php index b91ffec9..ebd52c3a 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/DoctrineCouchDBHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/DoctrineCouchDBHandler.php @@ -1,4 +1,4 @@ -client = $client; parent::__construct($level, $bubble); @@ -33,12 +35,12 @@ public function __construct(CouchDBClient $client, $level = Logger::DEBUG, $bubb /** * {@inheritDoc} */ - protected function write(array $record) + protected function write(array $record): void { $this->client->postDocument($record['formatted']); } - protected function getDefaultFormatter() + protected function getDefaultFormatter(): FormatterInterface { return new NormalizerFormatter; } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/DynamoDbHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/DynamoDbHandler.php index 237b71f6..21840bf6 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/DynamoDbHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/DynamoDbHandler.php @@ -1,4 +1,4 @@ -=')) { $this->version = 3; $this->marshaler = new Marshaler; @@ -69,28 +65,29 @@ public function __construct(DynamoDbClient $client, $table, $level = Logger::DEB } /** - * {@inheritdoc} + * {@inheritDoc} */ - protected function write(array $record) + protected function write(array $record): void { $filtered = $this->filterEmptyFields($record['formatted']); if ($this->version === 3) { $formatted = $this->marshaler->marshalItem($filtered); } else { + /** @phpstan-ignore-next-line */ $formatted = $this->client->formatAttributes($filtered); } - $this->client->putItem(array( + $this->client->putItem([ 'TableName' => $this->table, 'Item' => $formatted, - )); + ]); } /** - * @param array $record - * @return array + * @param mixed[] $record + * @return mixed[] */ - protected function filterEmptyFields(array $record) + protected function filterEmptyFields(array $record): array { return array_filter($record, function ($value) { return !empty($value) || false === $value || 0 === $value; @@ -98,9 +95,9 @@ protected function filterEmptyFields(array $record) } /** - * {@inheritdoc} + * {@inheritDoc} */ - protected function getDefaultFormatter() + protected function getDefaultFormatter(): FormatterInterface { return new ScalarFormatter(self::DATE_FORMAT); } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/ElasticSearchHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/ElasticSearchHandler.php index 81967406..e88375c0 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/ElasticSearchHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/ElasticSearchHandler.php @@ -1,4 +1,4 @@ -setHosts($hosts) + * ->build(); * - * $client = new \Elastica\Client(); * $options = array( * 'index' => 'elastic_index_name', - * 'type' => 'elastic_doc_type', + * 'type' => 'elastic_doc_type', * ); - * $handler = new ElasticSearchHandler($client, $options); + * $handler = new ElasticsearchHandler($client, $options); * $log = new Logger('application'); * $log->pushHandler($handler); * - * @author Jelle Vink + * @author Avtandil Kikabidze */ -class ElasticSearchHandler extends AbstractProcessingHandler +class ElasticsearchHandler extends AbstractProcessingHandler { /** - * @var Client + * @var Client|Client8 */ protected $client; /** - * @var array Handler config options + * @var mixed[] Handler config options + */ + protected $options = []; + + /** + * @var bool */ - protected $options = array(); + private $needsType; /** - * @param Client $client Elastica Client object - * @param array $options Handler configuration - * @param int $level The minimum logging level at which this handler will be triggered - * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + * @param Client|Client8 $client Elasticsearch Client object + * @param mixed[] $options Handler configuration */ - public function __construct(Client $client, array $options = array(), $level = Logger::DEBUG, $bubble = true) + public function __construct($client, array $options = [], $level = Logger::DEBUG, bool $bubble = true) { + if (!$client instanceof Client && !$client instanceof Client8) { + throw new \TypeError('Elasticsearch\Client or Elastic\Elasticsearch\Client instance required'); + } + parent::__construct($level, $bubble); $this->client = $client; $this->options = array_merge( - array( - 'index' => 'monolog', // Elastic index name - 'type' => 'record', // Elastic document type - 'ignore_error' => false, // Suppress Elastica exceptions - ), + [ + 'index' => 'monolog', // Elastic index name + 'type' => '_doc', // Elastic document type + 'ignore_error' => false, // Suppress Elasticsearch exceptions + ], $options ); + + if ($client instanceof Client8 || $client::VERSION[0] === '7') { + $this->needsType = false; + // force the type to _doc for ES8/ES7 + $this->options['type'] = '_doc'; + } else { + $this->needsType = true; + } } /** * {@inheritDoc} */ - protected function write(array $record) + protected function write(array $record): void { - $this->bulkSend(array($record['formatted'])); + $this->bulkSend([$record['formatted']]); } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function setFormatter(FormatterInterface $formatter) + public function setFormatter(FormatterInterface $formatter): HandlerInterface { - if ($formatter instanceof ElasticaFormatter) { + if ($formatter instanceof ElasticsearchFormatter) { return parent::setFormatter($formatter); } - throw new \InvalidArgumentException('ElasticSearchHandler is only compatible with ElasticaFormatter'); + + throw new InvalidArgumentException('ElasticsearchHandler is only compatible with ElasticsearchFormatter'); } /** * Getter options - * @return array + * + * @return mixed[] */ - public function getOptions() + public function getOptions(): array { return $this->options; } @@ -96,15 +124,15 @@ public function getOptions() /** * {@inheritDoc} */ - protected function getDefaultFormatter() + protected function getDefaultFormatter(): FormatterInterface { - return new ElasticaFormatter($this->options['index'], $this->options['type']); + return new ElasticsearchFormatter($this->options['index'], $this->options['type']); } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function handleBatch(array $records) + public function handleBatch(array $records): void { $documents = $this->getFormatter()->formatBatch($records); $this->bulkSend($documents); @@ -112,17 +140,79 @@ public function handleBatch(array $records) /** * Use Elasticsearch bulk API to send list of documents - * @param array $documents + * + * @param array[] $records Records + _index/_type keys * @throws \RuntimeException */ - protected function bulkSend(array $documents) + protected function bulkSend(array $records): void { try { - $this->client->addDocuments($documents); - } catch (ExceptionInterface $e) { - if (!$this->options['ignore_error']) { - throw new \RuntimeException("Error sending messages to Elasticsearch", 0, $e); + $params = [ + 'body' => [], + ]; + + foreach ($records as $record) { + $params['body'][] = [ + 'index' => $this->needsType ? [ + '_index' => $record['_index'], + '_type' => $record['_type'], + ] : [ + '_index' => $record['_index'], + ], + ]; + unset($record['_index'], $record['_type']); + + $params['body'][] = $record; + } + + /** @var Elasticsearch */ + $responses = $this->client->bulk($params); + + if ($responses['errors'] === true) { + throw $this->createExceptionFromResponses($responses); } + } catch (Throwable $e) { + if (! $this->options['ignore_error']) { + throw new RuntimeException('Error sending messages to Elasticsearch', 0, $e); + } + } + } + + /** + * Creates elasticsearch exception from responses array + * + * Only the first error is converted into an exception. + * + * @param mixed[]|Elasticsearch $responses returned by $this->client->bulk() + */ + protected function createExceptionFromResponses($responses): Throwable + { + foreach ($responses['items'] ?? [] as $item) { + if (isset($item['index']['error'])) { + return $this->createExceptionFromError($item['index']['error']); + } + } + + if (class_exists(ElasticInvalidArgumentException::class)) { + return new ElasticInvalidArgumentException('Elasticsearch failed to index one or more records.'); } + + return new ElasticsearchRuntimeException('Elasticsearch failed to index one or more records.'); + } + + /** + * Creates elasticsearch exception from error array + * + * @param mixed[] $error + */ + protected function createExceptionFromError(array $error): Throwable + { + $previous = isset($error['caused_by']) ? $this->createExceptionFromError($error['caused_by']) : null; + + if (class_exists(ElasticInvalidArgumentException::class)) { + return new ElasticInvalidArgumentException($error['type'] . ': ' . $error['reason'], 0, $previous); + } + + return new ElasticsearchRuntimeException($error['type'] . ': ' . $error['reason'], 0, $previous); } } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/ElasticaHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/ElasticaHandler.php new file mode 100644 index 00000000..fc92ca42 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/ElasticaHandler.php @@ -0,0 +1,129 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Elastica\Document; +use Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\ElasticaFormatter; +use Monolog\Logger; +use Elastica\Client; +use Elastica\Exception\ExceptionInterface; + +/** + * Elastic Search handler + * + * Usage example: + * + * $client = new \Elastica\Client(); + * $options = array( + * 'index' => 'elastic_index_name', + * 'type' => 'elastic_doc_type', Types have been removed in Elastica 7 + * ); + * $handler = new ElasticaHandler($client, $options); + * $log = new Logger('application'); + * $log->pushHandler($handler); + * + * @author Jelle Vink + */ +class ElasticaHandler extends AbstractProcessingHandler +{ + /** + * @var Client + */ + protected $client; + + /** + * @var mixed[] Handler config options + */ + protected $options = []; + + /** + * @param Client $client Elastica Client object + * @param mixed[] $options Handler configuration + */ + public function __construct(Client $client, array $options = [], $level = Logger::DEBUG, bool $bubble = true) + { + parent::__construct($level, $bubble); + $this->client = $client; + $this->options = array_merge( + [ + 'index' => 'monolog', // Elastic index name + 'type' => 'record', // Elastic document type + 'ignore_error' => false, // Suppress Elastica exceptions + ], + $options + ); + } + + /** + * {@inheritDoc} + */ + protected function write(array $record): void + { + $this->bulkSend([$record['formatted']]); + } + + /** + * {@inheritDoc} + */ + public function setFormatter(FormatterInterface $formatter): HandlerInterface + { + if ($formatter instanceof ElasticaFormatter) { + return parent::setFormatter($formatter); + } + + throw new \InvalidArgumentException('ElasticaHandler is only compatible with ElasticaFormatter'); + } + + /** + * @return mixed[] + */ + public function getOptions(): array + { + return $this->options; + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new ElasticaFormatter($this->options['index'], $this->options['type']); + } + + /** + * {@inheritDoc} + */ + public function handleBatch(array $records): void + { + $documents = $this->getFormatter()->formatBatch($records); + $this->bulkSend($documents); + } + + /** + * Use Elasticsearch bulk API to send list of documents + * + * @param Document[] $documents + * + * @throws \RuntimeException + */ + protected function bulkSend(array $documents): void + { + try { + $this->client->addDocuments($documents); + } catch (ExceptionInterface $e) { + if (!$this->options['ignore_error']) { + throw new \RuntimeException("Error sending messages to Elasticsearch", 0, $e); + } + } + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/ErrorLogHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/ErrorLogHandler.php index 1447a584..f2e22036 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/ErrorLogHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/ErrorLogHandler.php @@ -1,4 +1,4 @@ -expandNewlines) { - $lines = preg_split('{[\r\n]+}', (string) $record['formatted']); - foreach ($lines as $line) { - error_log($line, $this->messageType); - } - } else { + if (!$this->expandNewlines) { error_log((string) $record['formatted'], $this->messageType); + + return; + } + + $lines = preg_split('{[\r\n]+}', (string) $record['formatted']); + if ($lines === false) { + $pcreErrorCode = preg_last_error(); + throw new \RuntimeException('Failed to preg_split formatted string: ' . $pcreErrorCode . ' / '. Utils::pcreLastErrorMessage($pcreErrorCode)); + } + foreach ($lines as $line) { + error_log($line, $this->messageType); } } } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FallbackGroupHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/FallbackGroupHandler.php new file mode 100644 index 00000000..d4e234ce --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/FallbackGroupHandler.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Throwable; + +/** + * Forwards records to at most one handler + * + * If a handler fails, the exception is suppressed and the record is forwarded to the next handler. + * + * As soon as one handler handles a record successfully, the handling stops there. + * + * @phpstan-import-type Record from \Monolog\Logger + */ +class FallbackGroupHandler extends GroupHandler +{ + /** + * {@inheritDoc} + */ + public function handle(array $record): bool + { + if ($this->processors) { + /** @var Record $record */ + $record = $this->processRecord($record); + } + foreach ($this->handlers as $handler) { + try { + $handler->handle($record); + break; + } catch (Throwable $e) { + // What throwable? + } + } + + return false === $this->bubble; + } + + /** + * {@inheritDoc} + */ + public function handleBatch(array $records): void + { + if ($this->processors) { + $processed = []; + foreach ($records as $record) { + $processed[] = $this->processRecord($record); + } + /** @var Record[] $records */ + $records = $processed; + } + + foreach ($this->handlers as $handler) { + try { + $handler->handleBatch($records); + break; + } catch (Throwable $e) { + // What throwable? + } + } + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FilterHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/FilterHandler.php index 2a0f7fd1..718f17ef 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/FilterHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/FilterHandler.php @@ -1,4 +1,4 @@ - + * + * @phpstan-import-type Record from \Monolog\Logger + * @phpstan-import-type Level from \Monolog\Logger + * @phpstan-import-type LevelName from \Monolog\Logger */ -class FilterHandler extends AbstractHandler +class FilterHandler extends Handler implements ProcessableHandlerInterface, ResettableInterface, FormattableHandlerInterface { + use ProcessableHandlerTrait; + /** * Handler or factory callable($record, $this) * - * @var callable|\Monolog\Handler\HandlerInterface + * @var callable|HandlerInterface + * @phpstan-var callable(?Record, HandlerInterface): HandlerInterface|HandlerInterface */ protected $handler; @@ -34,23 +44,29 @@ class FilterHandler extends AbstractHandler * Minimum level for logs that are passed to handler * * @var int[] + * @phpstan-var array */ protected $acceptedLevels; /** * Whether the messages that are handled can bubble up the stack or not * - * @var Boolean + * @var bool */ protected $bubble; /** - * @param callable|HandlerInterface $handler Handler or factory callable($record, $this). + * @psalm-param HandlerInterface|callable(?Record, HandlerInterface): HandlerInterface $handler + * + * @param callable|HandlerInterface $handler Handler or factory callable($record|null, $filterHandler). * @param int|array $minLevelOrList A list of levels to accept or a minimum level if maxLevel is provided - * @param int $maxLevel Maximum level to accept, only used if $minLevelOrList is not an array - * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + * @param int|string $maxLevel Maximum level to accept, only used if $minLevelOrList is not an array + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * + * @phpstan-param Level|LevelName|LogLevel::*|array $minLevelOrList + * @phpstan-param Level|LevelName|LogLevel::* $maxLevel */ - public function __construct($handler, $minLevelOrList = Logger::DEBUG, $maxLevel = Logger::EMERGENCY, $bubble = true) + public function __construct($handler, $minLevelOrList = Logger::DEBUG, $maxLevel = Logger::EMERGENCY, bool $bubble = true) { $this->handler = $handler; $this->bubble = $bubble; @@ -62,9 +78,9 @@ public function __construct($handler, $minLevelOrList = Logger::DEBUG, $maxLevel } /** - * @return array + * @phpstan-return array */ - public function getAcceptedLevels() + public function getAcceptedLevels(): array { return array_flip($this->acceptedLevels); } @@ -72,8 +88,11 @@ public function getAcceptedLevels() /** * @param int|string|array $minLevelOrList A list of levels to accept or a minimum level or level name if maxLevel is provided * @param int|string $maxLevel Maximum level or level name to accept, only used if $minLevelOrList is not an array + * + * @phpstan-param Level|LevelName|LogLevel::*|array $minLevelOrList + * @phpstan-param Level|LevelName|LogLevel::* $maxLevel */ - public function setAcceptedLevels($minLevelOrList = Logger::DEBUG, $maxLevel = Logger::EMERGENCY) + public function setAcceptedLevels($minLevelOrList = Logger::DEBUG, $maxLevel = Logger::EMERGENCY): self { if (is_array($minLevelOrList)) { $acceptedLevels = array_map('Monolog\Logger::toMonologLevel', $minLevelOrList); @@ -85,56 +104,109 @@ public function setAcceptedLevels($minLevelOrList = Logger::DEBUG, $maxLevel = L })); } $this->acceptedLevels = array_flip($acceptedLevels); + + return $this; } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function isHandling(array $record) + public function isHandling(array $record): bool { return isset($this->acceptedLevels[$record['level']]); } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function handle(array $record) + public function handle(array $record): bool { if (!$this->isHandling($record)) { return false; } - // The same logic as in FingersCrossedHandler - if (!$this->handler instanceof HandlerInterface) { - $this->handler = call_user_func($this->handler, $record, $this); - if (!$this->handler instanceof HandlerInterface) { - throw new \RuntimeException("The factory callable should return a HandlerInterface"); - } - } - if ($this->processors) { - foreach ($this->processors as $processor) { - $record = call_user_func($processor, $record); - } + /** @var Record $record */ + $record = $this->processRecord($record); } - $this->handler->handle($record); + $this->getHandler($record)->handle($record); return false === $this->bubble; } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function handleBatch(array $records) + public function handleBatch(array $records): void { - $filtered = array(); + $filtered = []; foreach ($records as $record) { if ($this->isHandling($record)) { $filtered[] = $record; } } - $this->handler->handleBatch($filtered); + if (count($filtered) > 0) { + $this->getHandler($filtered[count($filtered) - 1])->handleBatch($filtered); + } + } + + /** + * Return the nested handler + * + * If the handler was provided as a factory callable, this will trigger the handler's instantiation. + * + * @return HandlerInterface + * + * @phpstan-param Record $record + */ + public function getHandler(array $record = null) + { + if (!$this->handler instanceof HandlerInterface) { + $this->handler = ($this->handler)($record, $this); + if (!$this->handler instanceof HandlerInterface) { + throw new \RuntimeException("The factory callable should return a HandlerInterface"); + } + } + + return $this->handler; + } + + /** + * {@inheritDoc} + */ + public function setFormatter(FormatterInterface $formatter): HandlerInterface + { + $handler = $this->getHandler(); + if ($handler instanceof FormattableHandlerInterface) { + $handler->setFormatter($formatter); + + return $this; + } + + throw new \UnexpectedValueException('The nested handler of type '.get_class($handler).' does not support formatters.'); + } + + /** + * {@inheritDoc} + */ + public function getFormatter(): FormatterInterface + { + $handler = $this->getHandler(); + if ($handler instanceof FormattableHandlerInterface) { + return $handler->getFormatter(); + } + + throw new \UnexpectedValueException('The nested handler of type '.get_class($handler).' does not support formatters.'); + } + + public function reset() + { + $this->resetProcessors(); + + if ($this->getHandler() instanceof ResettableInterface) { + $this->getHandler()->reset(); + } } } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ActivationStrategyInterface.php b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ActivationStrategyInterface.php index c3e42efe..0aa5607b 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ActivationStrategyInterface.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ActivationStrategyInterface.php @@ -1,4 +1,4 @@ - + * + * @phpstan-import-type Record from \Monolog\Logger */ interface ActivationStrategyInterface { /** * Returns whether the given record activates the handler. * - * @param array $record - * @return Boolean + * @phpstan-param Record $record */ - public function isHandlerActivated(array $record); + public function isHandlerActivated(array $record): bool; } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.php b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.php index 2a2a64d9..7b9abb58 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.php @@ -1,4 +1,4 @@ - * * @author Mike Meessen + * + * @phpstan-import-type Record from \Monolog\Logger + * @phpstan-import-type Level from \Monolog\Logger + * @phpstan-import-type LevelName from \Monolog\Logger */ class ChannelLevelActivationStrategy implements ActivationStrategyInterface { + /** + * @var Level + */ private $defaultActionLevel; + + /** + * @var array + */ private $channelToActionLevel; /** - * @param int $defaultActionLevel The default action level to be used if the record's category doesn't match any - * @param array $channelToActionLevel An array that maps channel names to action levels. + * @param int|string $defaultActionLevel The default action level to be used if the record's category doesn't match any + * @param array $channelToActionLevel An array that maps channel names to action levels. + * + * @phpstan-param array $channelToActionLevel + * @phpstan-param Level|LevelName|LogLevel::* $defaultActionLevel */ - public function __construct($defaultActionLevel, $channelToActionLevel = array()) + public function __construct($defaultActionLevel, array $channelToActionLevel = []) { $this->defaultActionLevel = Logger::toMonologLevel($defaultActionLevel); $this->channelToActionLevel = array_map('Monolog\Logger::toMonologLevel', $channelToActionLevel); } - public function isHandlerActivated(array $record) + /** + * @phpstan-param Record $record + */ + public function isHandlerActivated(array $record): bool { if (isset($this->channelToActionLevel[$record['channel']])) { return $record['level'] >= $this->channelToActionLevel[$record['channel']]; diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.php b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.php index 6e630852..5ec88eab 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.php @@ -1,4 +1,4 @@ - + * + * @phpstan-import-type Level from \Monolog\Logger + * @phpstan-import-type LevelName from \Monolog\Logger */ class ErrorLevelActivationStrategy implements ActivationStrategyInterface { + /** + * @var Level + */ private $actionLevel; + /** + * @param int|string $actionLevel Level or name or value + * + * @phpstan-param Level|LevelName|LogLevel::* $actionLevel + */ public function __construct($actionLevel) { $this->actionLevel = Logger::toMonologLevel($actionLevel); } - public function isHandlerActivated(array $record) + public function isHandlerActivated(array $record): bool { return $record['level'] >= $this->actionLevel; } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossedHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossedHandler.php index d1dcaacf..0627b445 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossedHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossedHandler.php @@ -1,4 +1,4 @@ - + * + * @phpstan-import-type Record from \Monolog\Logger + * @phpstan-import-type Level from \Monolog\Logger + * @phpstan-import-type LevelName from \Monolog\Logger */ -class FingersCrossedHandler extends AbstractHandler +class FingersCrossedHandler extends Handler implements ProcessableHandlerInterface, ResettableInterface, FormattableHandlerInterface { + use ProcessableHandlerTrait; + + /** + * @var callable|HandlerInterface + * @phpstan-var callable(?Record, HandlerInterface): HandlerInterface|HandlerInterface + */ protected $handler; + /** @var ActivationStrategyInterface */ protected $activationStrategy; + /** @var bool */ protected $buffering = true; + /** @var int */ protected $bufferSize; - protected $buffer = array(); + /** @var Record[] */ + protected $buffer = []; + /** @var bool */ protected $stopBuffering; + /** + * @var ?int + * @phpstan-var ?Level + */ protected $passthruLevel; + /** @var bool */ + protected $bubble; /** - * @param callable|HandlerInterface $handler Handler or factory callable($record, $fingersCrossedHandler). - * @param int|ActivationStrategyInterface $activationStrategy Strategy which determines when this handler takes action - * @param int $bufferSize How many entries should be buffered at most, beyond that the oldest items are removed from the buffer. - * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not - * @param Boolean $stopBuffering Whether the handler should stop buffering after being triggered (default true) - * @param int $passthruLevel Minimum level to always flush to handler on close, even if strategy not triggered + * @psalm-param HandlerInterface|callable(?Record, HandlerInterface): HandlerInterface $handler + * + * @param callable|HandlerInterface $handler Handler or factory callable($record|null, $fingersCrossedHandler). + * @param int|string|ActivationStrategyInterface $activationStrategy Strategy which determines when this handler takes action, or a level name/value at which the handler is activated + * @param int $bufferSize How many entries should be buffered at most, beyond that the oldest items are removed from the buffer. + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * @param bool $stopBuffering Whether the handler should stop buffering after being triggered (default true) + * @param int|string $passthruLevel Minimum level to always flush to handler on close, even if strategy not triggered + * + * @phpstan-param Level|LevelName|LogLevel::* $passthruLevel + * @phpstan-param Level|LevelName|LogLevel::*|ActivationStrategyInterface $activationStrategy */ - public function __construct($handler, $activationStrategy = null, $bufferSize = 0, $bubble = true, $stopBuffering = true, $passthruLevel = null) + public function __construct($handler, $activationStrategy = null, int $bufferSize = 0, bool $bubble = true, bool $stopBuffering = true, $passthruLevel = null) { if (null === $activationStrategy) { $activationStrategy = new ErrorLevelActivationStrategy(Logger::WARNING); @@ -72,9 +105,9 @@ public function __construct($handler, $activationStrategy = null, $bufferSize = } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function isHandling(array $record) + public function isHandling(array $record): bool { return true; } @@ -82,32 +115,24 @@ public function isHandling(array $record) /** * Manually activate this logger regardless of the activation strategy */ - public function activate() + public function activate(): void { if ($this->stopBuffering) { $this->buffering = false; } - if (!$this->handler instanceof HandlerInterface) { - $record = end($this->buffer) ?: null; - $this->handler = call_user_func($this->handler, $record, $this); - if (!$this->handler instanceof HandlerInterface) { - throw new \RuntimeException("The factory callable should return a HandlerInterface"); - } - } - $this->handler->handleBatch($this->buffer); - $this->buffer = array(); + $this->getHandler(end($this->buffer) ?: null)->handleBatch($this->buffer); + $this->buffer = []; } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function handle(array $record) + public function handle(array $record): bool { if ($this->processors) { - foreach ($this->processors as $processor) { - $record = call_user_func($processor, $record); - } + /** @var Record $record */ + $record = $this->processRecord($record); } if ($this->buffering) { @@ -119,16 +144,48 @@ public function handle(array $record) $this->activate(); } } else { - $this->handler->handle($record); + $this->getHandler($record)->handle($record); } return false === $this->bubble; } /** - * {@inheritdoc} + * {@inheritDoc} + */ + public function close(): void + { + $this->flushBuffer(); + + $this->getHandler()->close(); + } + + public function reset() + { + $this->flushBuffer(); + + $this->resetProcessors(); + + if ($this->getHandler() instanceof ResettableInterface) { + $this->getHandler()->reset(); + } + } + + /** + * Clears the buffer without flushing any messages down to the wrapped handler. + * + * It also resets the handler to its initial buffering state. + */ + public function clear(): void + { + $this->buffer = []; + $this->reset(); + } + + /** + * Resets the state of the handler. Stops forwarding records to the wrapped handler. */ - public function close() + private function flushBuffer(): void { if (null !== $this->passthruLevel) { $level = $this->passthruLevel; @@ -136,28 +193,60 @@ public function close() return $record['level'] >= $level; }); if (count($this->buffer) > 0) { - $this->handler->handleBatch($this->buffer); - $this->buffer = array(); + $this->getHandler(end($this->buffer))->handleBatch($this->buffer); } } + + $this->buffer = []; + $this->buffering = true; } /** - * Resets the state of the handler. Stops forwarding records to the wrapped handler. + * Return the nested handler + * + * If the handler was provided as a factory callable, this will trigger the handler's instantiation. + * + * @return HandlerInterface + * + * @phpstan-param Record $record */ - public function reset() + public function getHandler(array $record = null) { - $this->buffering = true; + if (!$this->handler instanceof HandlerInterface) { + $this->handler = ($this->handler)($record, $this); + if (!$this->handler instanceof HandlerInterface) { + throw new \RuntimeException("The factory callable should return a HandlerInterface"); + } + } + + return $this->handler; } /** - * Clears the buffer without flushing any messages down to the wrapped handler. - * - * It also resets the handler to its initial buffering state. + * {@inheritDoc} */ - public function clear() + public function setFormatter(FormatterInterface $formatter): HandlerInterface { - $this->buffer = array(); - $this->reset(); + $handler = $this->getHandler(); + if ($handler instanceof FormattableHandlerInterface) { + $handler->setFormatter($formatter); + + return $this; + } + + throw new \UnexpectedValueException('The nested handler of type '.get_class($handler).' does not support formatters.'); + } + + /** + * {@inheritDoc} + */ + public function getFormatter(): FormatterInterface + { + $handler = $this->getHandler(); + if ($handler instanceof FormattableHandlerInterface) { + return $handler->getFormatter(); + } + + throw new \UnexpectedValueException('The nested handler of type '.get_class($handler).' does not support formatters.'); } } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FirePHPHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/FirePHPHandler.php index fee47950..72718de6 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/FirePHPHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/FirePHPHandler.php @@ -1,4 +1,4 @@ - + * + * @phpstan-import-type FormattedRecord from AbstractProcessingHandler */ class FirePHPHandler extends AbstractProcessingHandler { + use WebRequestRecognizerTrait; + /** * WildFire JSON header message format */ - const PROTOCOL_URI = 'http://meta.wildfirehq.org/Protocol/JsonStream/0.2'; + protected const PROTOCOL_URI = 'http://meta.wildfirehq.org/Protocol/JsonStream/0.2'; /** * FirePHP structure for parsing messages & their presentation */ - const STRUCTURE_URI = 'http://meta.firephp.org/Wildfire/Structure/FirePHP/FirebugConsole/0.1'; + protected const STRUCTURE_URI = 'http://meta.firephp.org/Wildfire/Structure/FirePHP/FirebugConsole/0.1'; /** * Must reference a "known" plugin, otherwise headers won't display in FirePHP */ - const PLUGIN_URI = 'http://meta.firephp.org/Wildfire/Plugin/FirePHP/Library-FirePHPCore/0.3'; + protected const PLUGIN_URI = 'http://meta.firephp.org/Wildfire/Plugin/FirePHP/Library-FirePHPCore/0.3'; /** * Header prefix for Wildfire to recognize & parse headers */ - const HEADER_PREFIX = 'X-Wf'; + protected const HEADER_PREFIX = 'X-Wf'; /** * Whether or not Wildfire vendor-specific headers have been generated & sent yet + * @var bool */ protected static $initialized = false; @@ -51,35 +57,43 @@ class FirePHPHandler extends AbstractProcessingHandler */ protected static $messageIndex = 1; + /** @var bool */ protected static $sendHeaders = true; /** * Base header creation function used by init headers & record headers * - * @param array $meta Wildfire Plugin, Protocol & Structure Indexes - * @param string $message Log message - * @return array Complete header string ready for the client as key and message as value + * @param array $meta Wildfire Plugin, Protocol & Structure Indexes + * @param string $message Log message + * + * @return array Complete header string ready for the client as key and message as value + * + * @phpstan-return non-empty-array */ - protected function createHeader(array $meta, $message) + protected function createHeader(array $meta, string $message): array { - $header = sprintf('%s-%s', self::HEADER_PREFIX, join('-', $meta)); + $header = sprintf('%s-%s', static::HEADER_PREFIX, join('-', $meta)); - return array($header => $message); + return [$header => $message]; } /** * Creates message header from record * + * @return array + * + * @phpstan-return non-empty-array + * * @see createHeader() - * @param array $record - * @return string + * + * @phpstan-param FormattedRecord $record */ - protected function createRecordHeader(array $record) + protected function createRecordHeader(array $record): array { // Wildfire is extensible to support multiple protocols & plugins in a single request, // but we're not taking advantage of that (yet), so we're using "1" for simplicity's sake. return $this->createHeader( - array(1, 1, 1, self::$messageIndex++), + [1, 1, 1, self::$messageIndex++], $record['formatted'] ); } @@ -87,7 +101,7 @@ protected function createRecordHeader(array $record) /** * {@inheritDoc} */ - protected function getDefaultFormatter() + protected function getDefaultFormatter(): FormatterInterface { return new WildfireFormatter(); } @@ -97,25 +111,23 @@ protected function getDefaultFormatter() * * @see createHeader() * @see sendHeader() - * @return array + * + * @return array */ - protected function getInitHeaders() + protected function getInitHeaders(): array { // Initial payload consists of required headers for Wildfire return array_merge( - $this->createHeader(array('Protocol', 1), self::PROTOCOL_URI), - $this->createHeader(array(1, 'Structure', 1), self::STRUCTURE_URI), - $this->createHeader(array(1, 'Plugin', 1), self::PLUGIN_URI) + $this->createHeader(['Protocol', 1], static::PROTOCOL_URI), + $this->createHeader([1, 'Structure', 1], static::STRUCTURE_URI), + $this->createHeader([1, 'Plugin', 1], static::PLUGIN_URI) ); } /** * Send header string to the client - * - * @param string $header - * @param string $content */ - protected function sendHeader($header, $content) + protected function sendHeader(string $header, string $content): void { if (!headers_sent() && self::$sendHeaders) { header(sprintf('%s: %s', $header, $content)); @@ -127,11 +139,10 @@ protected function sendHeader($header, $content) * * @see sendHeader() * @see sendInitHeaders() - * @param array $record */ - protected function write(array $record) + protected function write(array $record): void { - if (!self::$sendHeaders) { + if (!self::$sendHeaders || !$this->isWebRequest()) { return; } @@ -157,10 +168,8 @@ protected function write(array $record) /** * Verifies if the headers are accepted by the current user agent - * - * @return Boolean */ - protected function headersAccepted() + protected function headersAccepted(): bool { if (!empty($_SERVER['HTTP_USER_AGENT']) && preg_match('{\bFirePHP/\d+\.\d+\b}', $_SERVER['HTTP_USER_AGENT'])) { return true; @@ -168,28 +177,4 @@ protected function headersAccepted() return isset($_SERVER['HTTP_X_FIREPHP_VERSION']); } - - /** - * BC getter for the sendHeaders property that has been made static - */ - public function __get($property) - { - if ('sendHeaders' !== $property) { - throw new \InvalidArgumentException('Undefined property '.$property); - } - - return static::$sendHeaders; - } - - /** - * BC setter for the sendHeaders property that has been made static - */ - public function __set($property, $value) - { - if ('sendHeaders' !== $property) { - throw new \InvalidArgumentException('Undefined property '.$property); - } - - static::$sendHeaders = $value; - } } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FleepHookHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/FleepHookHandler.php index c43c0134..85c95b9d 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/FleepHookHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/FleepHookHandler.php @@ -1,4 +1,4 @@ - + * + * @phpstan-import-type FormattedRecord from AbstractProcessingHandler */ class FleepHookHandler extends SocketHandler { - const FLEEP_HOST = 'fleep.io'; + protected const FLEEP_HOST = 'fleep.io'; - const FLEEP_HOOK_URI = '/hook/'; + protected const FLEEP_HOOK_URI = '/hook/'; /** * @var string Webhook token (specifies the conversation where logs are sent) @@ -40,20 +43,35 @@ class FleepHookHandler extends SocketHandler * see https://fleep.io/integrations/webhooks/ * * @param string $token Webhook token - * @param bool|int $level The minimum logging level at which this handler will be triggered - * @param bool $bubble Whether the messages that are handled can bubble up the stack or not * @throws MissingExtensionException */ - public function __construct($token, $level = Logger::DEBUG, $bubble = true) - { + public function __construct( + string $token, + $level = Logger::DEBUG, + bool $bubble = true, + bool $persistent = false, + float $timeout = 0.0, + float $writingTimeout = 10.0, + ?float $connectionTimeout = null, + ?int $chunkSize = null + ) { if (!extension_loaded('openssl')) { throw new MissingExtensionException('The OpenSSL PHP extension is required to use the FleepHookHandler'); } $this->token = $token; - $connectionString = 'ssl://' . self::FLEEP_HOST . ':443'; - parent::__construct($connectionString, $level, $bubble); + $connectionString = 'ssl://' . static::FLEEP_HOST . ':443'; + parent::__construct( + $connectionString, + $level, + $bubble, + $persistent, + $timeout, + $writingTimeout, + $connectionTimeout, + $chunkSize + ); } /** @@ -63,29 +81,24 @@ public function __construct($token, $level = Logger::DEBUG, $bubble = true) * * @return LineFormatter */ - protected function getDefaultFormatter() + protected function getDefaultFormatter(): FormatterInterface { return new LineFormatter(null, null, true, true); } /** * Handles a log record - * - * @param array $record */ - public function write(array $record) + public function write(array $record): void { parent::write($record); $this->closeSocket(); } /** - * {@inheritdoc} - * - * @param array $record - * @return string + * {@inheritDoc} */ - protected function generateDataStream($record) + protected function generateDataStream(array $record): string { $content = $this->buildContent($record); @@ -94,14 +107,11 @@ protected function generateDataStream($record) /** * Builds the header of the API Call - * - * @param string $content - * @return string */ - private function buildHeader($content) + private function buildHeader(string $content): string { - $header = "POST " . self::FLEEP_HOOK_URI . $this->token . " HTTP/1.1\r\n"; - $header .= "Host: " . self::FLEEP_HOST . "\r\n"; + $header = "POST " . static::FLEEP_HOOK_URI . $this->token . " HTTP/1.1\r\n"; + $header .= "Host: " . static::FLEEP_HOST . "\r\n"; $header .= "Content-Type: application/x-www-form-urlencoded\r\n"; $header .= "Content-Length: " . strlen($content) . "\r\n"; $header .= "\r\n"; @@ -112,14 +122,13 @@ private function buildHeader($content) /** * Builds the body of API call * - * @param array $record - * @return string + * @phpstan-param FormattedRecord $record */ - private function buildContent($record) + private function buildContent(array $record): string { - $dataArray = array( + $dataArray = [ 'message' => $record['formatted'], - ); + ]; return http_build_query($dataArray); } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FlowdockHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/FlowdockHandler.php index dd9a361c..b837bdb6 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/FlowdockHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/FlowdockHandler.php @@ -1,4 +1,4 @@ - * @see https://www.flowdock.com/api/push + * + * @phpstan-import-type FormattedRecord from AbstractProcessingHandler */ class FlowdockHandler extends SocketHandler { @@ -34,26 +37,39 @@ class FlowdockHandler extends SocketHandler protected $apiToken; /** - * @param string $apiToken - * @param bool|int $level The minimum logging level at which this handler will be triggered - * @param bool $bubble Whether the messages that are handled can bubble up the stack or not - * * @throws MissingExtensionException if OpenSSL is missing */ - public function __construct($apiToken, $level = Logger::DEBUG, $bubble = true) - { + public function __construct( + string $apiToken, + $level = Logger::DEBUG, + bool $bubble = true, + bool $persistent = false, + float $timeout = 0.0, + float $writingTimeout = 10.0, + ?float $connectionTimeout = null, + ?int $chunkSize = null + ) { if (!extension_loaded('openssl')) { throw new MissingExtensionException('The OpenSSL PHP extension is required to use the FlowdockHandler'); } - parent::__construct('ssl://api.flowdock.com:443', $level, $bubble); + parent::__construct( + 'ssl://api.flowdock.com:443', + $level, + $bubble, + $persistent, + $timeout, + $writingTimeout, + $connectionTimeout, + $chunkSize + ); $this->apiToken = $apiToken; } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function setFormatter(FormatterInterface $formatter) + public function setFormatter(FormatterInterface $formatter): HandlerInterface { if (!$formatter instanceof FlowdockFormatter) { throw new \InvalidArgumentException('The FlowdockHandler requires an instance of Monolog\Formatter\FlowdockFormatter to function correctly'); @@ -64,20 +80,16 @@ public function setFormatter(FormatterInterface $formatter) /** * Gets the default formatter. - * - * @return FormatterInterface */ - protected function getDefaultFormatter() + protected function getDefaultFormatter(): FormatterInterface { throw new \InvalidArgumentException('The FlowdockHandler must be configured (via setFormatter) with an instance of Monolog\Formatter\FlowdockFormatter to function correctly'); } /** - * {@inheritdoc} - * - * @param array $record + * {@inheritDoc} */ - protected function write(array $record) + protected function write(array $record): void { parent::write($record); @@ -85,12 +97,9 @@ protected function write(array $record) } /** - * {@inheritdoc} - * - * @param array $record - * @return string + * {@inheritDoc} */ - protected function generateDataStream($record) + protected function generateDataStream(array $record): string { $content = $this->buildContent($record); @@ -100,21 +109,17 @@ protected function generateDataStream($record) /** * Builds the body of API call * - * @param array $record - * @return string + * @phpstan-param FormattedRecord $record */ - private function buildContent($record) + private function buildContent(array $record): string { - return json_encode($record['formatted']['flowdock']); + return Utils::jsonEncode($record['formatted']['flowdock']); } /** * Builds the header of the API Call - * - * @param string $content - * @return string */ - private function buildHeader($content) + private function buildHeader(string $content): string { $header = "POST /v1/messages/team_inbox/" . $this->apiToken . " HTTP/1.1\r\n"; $header .= "Host: api.flowdock.com\r\n"; diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FormattableHandlerInterface.php b/vendor/monolog/monolog/src/Monolog/Handler/FormattableHandlerInterface.php new file mode 100644 index 00000000..fc1693cd --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/FormattableHandlerInterface.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\FormatterInterface; + +/** + * Interface to describe loggers that have a formatter + * + * @author Jordi Boggiano + */ +interface FormattableHandlerInterface +{ + /** + * Sets the formatter. + * + * @param FormatterInterface $formatter + * @return HandlerInterface self + */ + public function setFormatter(FormatterInterface $formatter): HandlerInterface; + + /** + * Gets the formatter. + * + * @return FormatterInterface + */ + public function getFormatter(): FormatterInterface; +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FormattableHandlerTrait.php b/vendor/monolog/monolog/src/Monolog/Handler/FormattableHandlerTrait.php new file mode 100644 index 00000000..b60bdce0 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/FormattableHandlerTrait.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\LineFormatter; + +/** + * Helper trait for implementing FormattableInterface + * + * @author Jordi Boggiano + */ +trait FormattableHandlerTrait +{ + /** + * @var ?FormatterInterface + */ + protected $formatter; + + /** + * {@inheritDoc} + */ + public function setFormatter(FormatterInterface $formatter): HandlerInterface + { + $this->formatter = $formatter; + + return $this; + } + + /** + * {@inheritDoc} + */ + public function getFormatter(): FormatterInterface + { + if (!$this->formatter) { + $this->formatter = $this->getDefaultFormatter(); + } + + return $this->formatter; + } + + /** + * Gets the default formatter. + * + * Overwrite this if the LineFormatter is not a good default for your handler. + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new LineFormatter(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/GelfHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/GelfHandler.php index d3847d82..4ff26c4c 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/GelfHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/GelfHandler.php @@ -1,4 +1,4 @@ -publisher = $publisher; } /** - * {@inheritdoc} - */ - public function close() - { - $this->publisher = null; - } - - /** - * {@inheritdoc} + * {@inheritDoc} */ - protected function write(array $record) + protected function write(array $record): void { $this->publisher->publish($record['formatted']); } @@ -66,7 +50,7 @@ protected function write(array $record) /** * {@inheritDoc} */ - protected function getDefaultFormatter() + protected function getDefaultFormatter(): FormatterInterface { return new GelfMessageFormatter(); } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/GroupHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/GroupHandler.php index 663f5a92..3c9dc4b3 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/GroupHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/GroupHandler.php @@ -1,4 +1,4 @@ - + * + * @phpstan-import-type Record from \Monolog\Logger */ -class GroupHandler extends AbstractHandler +class GroupHandler extends Handler implements ProcessableHandlerInterface, ResettableInterface { + use ProcessableHandlerTrait; + + /** @var HandlerInterface[] */ protected $handlers; + /** @var bool */ + protected $bubble; /** - * @param array $handlers Array of Handlers. - * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + * @param HandlerInterface[] $handlers Array of Handlers. + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not */ - public function __construct(array $handlers, $bubble = true) + public function __construct(array $handlers, bool $bubble = true) { foreach ($handlers as $handler) { if (!$handler instanceof HandlerInterface) { @@ -39,9 +47,9 @@ public function __construct(array $handlers, $bubble = true) } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function isHandling(array $record) + public function isHandling(array $record): bool { foreach ($this->handlers as $handler) { if ($handler->isHandling($record)) { @@ -53,14 +61,13 @@ public function isHandling(array $record) } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function handle(array $record) + public function handle(array $record): bool { if ($this->processors) { - foreach ($this->processors as $processor) { - $record = call_user_func($processor, $record); - } + /** @var Record $record */ + $record = $this->processRecord($record); } foreach ($this->handlers as $handler) { @@ -71,17 +78,16 @@ public function handle(array $record) } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function handleBatch(array $records) + public function handleBatch(array $records): void { if ($this->processors) { - $processed = array(); + $processed = []; foreach ($records as $record) { - foreach ($this->processors as $processor) { - $processed[] = call_user_func($processor, $record); - } + $processed[] = $this->processRecord($record); } + /** @var Record[] $records */ $records = $processed; } @@ -90,13 +96,35 @@ public function handleBatch(array $records) } } + public function reset() + { + $this->resetProcessors(); + + foreach ($this->handlers as $handler) { + if ($handler instanceof ResettableInterface) { + $handler->reset(); + } + } + } + + public function close(): void + { + parent::close(); + + foreach ($this->handlers as $handler) { + $handler->close(); + } + } + /** - * {@inheritdoc} + * {@inheritDoc} */ - public function setFormatter(FormatterInterface $formatter) + public function setFormatter(FormatterInterface $formatter): HandlerInterface { foreach ($this->handlers as $handler) { - $handler->setFormatter($formatter); + if ($handler instanceof FormattableHandlerInterface) { + $handler->setFormatter($formatter); + } } return $this; diff --git a/vendor/monolog/monolog/src/Monolog/Handler/Handler.php b/vendor/monolog/monolog/src/Monolog/Handler/Handler.php new file mode 100644 index 00000000..34b4935d --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/Handler.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +/** + * Base Handler class providing basic close() support as well as handleBatch + * + * @author Jordi Boggiano + */ +abstract class Handler implements HandlerInterface +{ + /** + * {@inheritDoc} + */ + public function handleBatch(array $records): void + { + foreach ($records as $record) { + $this->handle($record); + } + } + + /** + * {@inheritDoc} + */ + public function close(): void + { + } + + public function __destruct() + { + try { + $this->close(); + } catch (\Throwable $e) { + // do nothing + } + } + + public function __sleep() + { + $this->close(); + + $reflClass = new \ReflectionClass($this); + + $keys = []; + foreach ($reflClass->getProperties() as $reflProp) { + if (!$reflProp->isStatic()) { + $keys[] = $reflProp->getName(); + } + } + + return $keys; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/HandlerInterface.php b/vendor/monolog/monolog/src/Monolog/Handler/HandlerInterface.php index d920c4ba..affcc51f 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/HandlerInterface.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/HandlerInterface.php @@ -1,4 +1,4 @@ - + * + * @phpstan-import-type Record from \Monolog\Logger + * @phpstan-import-type Level from \Monolog\Logger */ interface HandlerInterface { @@ -31,9 +32,11 @@ interface HandlerInterface * * @param array $record Partial log record containing only a level key * - * @return Boolean + * @return bool + * + * @phpstan-param array{level: Level} $record */ - public function isHandling(array $record); + public function isHandling(array $record): bool; /** * Handles a record. @@ -45,46 +48,38 @@ public function isHandling(array $record); * Unless the bubbling is interrupted (by returning true), the Logger class will keep on * calling further handlers in the stack with a given log record. * - * @param array $record The record to handle - * @return Boolean true means that this handler handled the record, and that bubbling is not permitted. - * false means the record was either not processed or that this handler allows bubbling. + * @param array $record The record to handle + * @return bool true means that this handler handled the record, and that bubbling is not permitted. + * false means the record was either not processed or that this handler allows bubbling. + * + * @phpstan-param Record $record */ - public function handle(array $record); + public function handle(array $record): bool; /** * Handles a set of records at once. * * @param array $records The records to handle (an array of record arrays) - */ - public function handleBatch(array $records); - - /** - * Adds a processor in the stack. * - * @param callable $callback - * @return self + * @phpstan-param Record[] $records */ - public function pushProcessor($callback); + public function handleBatch(array $records): void; /** - * Removes the processor on top of the stack and returns it. + * Closes the handler. * - * @return callable - */ - public function popProcessor(); - - /** - * Sets the formatter. + * Ends a log cycle and frees all resources used by the handler. * - * @param FormatterInterface $formatter - * @return self - */ - public function setFormatter(FormatterInterface $formatter); - - /** - * Gets the formatter. + * Closing a Handler means flushing all buffers and freeing any open resources/handles. + * + * Implementations have to be idempotent (i.e. it should be possible to call close several times without breakage) + * and ideally handlers should be able to reopen themselves on handle() after they have been closed. + * + * This is useful at the end of a request and will be called automatically when the object + * is destroyed if you extend Monolog\Handler\Handler. * - * @return FormatterInterface + * If you are thinking of calling this method yourself, most likely you should be + * calling ResettableInterface::reset instead. Have a look. */ - public function getFormatter(); + public function close(): void; } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/HandlerWrapper.php b/vendor/monolog/monolog/src/Monolog/Handler/HandlerWrapper.php index e540d80f..d4351b9f 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/HandlerWrapper.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/HandlerWrapper.php @@ -1,4 +1,4 @@ - */ -class HandlerWrapper implements HandlerInterface +class HandlerWrapper implements HandlerInterface, ProcessableHandlerInterface, FormattableHandlerInterface, ResettableInterface { /** * @var HandlerInterface */ protected $handler; - /** - * HandlerWrapper constructor. - * @param HandlerInterface $handler - */ public function __construct(HandlerInterface $handler) { $this->handler = $handler; } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function isHandling(array $record) + public function isHandling(array $record): bool { return $this->handler->isHandling($record); } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function handle(array $record) + public function handle(array $record): bool { return $this->handler->handle($record); } /** - * {@inheritdoc} + * {@inheritDoc} + */ + public function handleBatch(array $records): void + { + $this->handler->handleBatch($records); + } + + /** + * {@inheritDoc} */ - public function handleBatch(array $records) + public function close(): void { - return $this->handler->handleBatch($records); + $this->handler->close(); } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function pushProcessor($callback) + public function pushProcessor(callable $callback): HandlerInterface { - $this->handler->pushProcessor($callback); + if ($this->handler instanceof ProcessableHandlerInterface) { + $this->handler->pushProcessor($callback); - return $this; + return $this; + } + + throw new \LogicException('The wrapped handler does not implement ' . ProcessableHandlerInterface::class); } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function popProcessor() + public function popProcessor(): callable { - return $this->handler->popProcessor(); + if ($this->handler instanceof ProcessableHandlerInterface) { + return $this->handler->popProcessor(); + } + + throw new \LogicException('The wrapped handler does not implement ' . ProcessableHandlerInterface::class); } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function setFormatter(FormatterInterface $formatter) + public function setFormatter(FormatterInterface $formatter): HandlerInterface { - $this->handler->setFormatter($formatter); + if ($this->handler instanceof FormattableHandlerInterface) { + $this->handler->setFormatter($formatter); - return $this; + return $this; + } + + throw new \LogicException('The wrapped handler does not implement ' . FormattableHandlerInterface::class); } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function getFormatter() + public function getFormatter(): FormatterInterface + { + if ($this->handler instanceof FormattableHandlerInterface) { + return $this->handler->getFormatter(); + } + + throw new \LogicException('The wrapped handler does not implement ' . FormattableHandlerInterface::class); + } + + public function reset() { - return $this->handler->getFormatter(); + if ($this->handler instanceof ResettableInterface) { + $this->handler->reset(); + } } } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/HipChatHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/HipChatHandler.php deleted file mode 100644 index 73049f36..00000000 --- a/vendor/monolog/monolog/src/Monolog/Handler/HipChatHandler.php +++ /dev/null @@ -1,350 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler; - -use Monolog\Logger; - -/** - * Sends notifications through the hipchat api to a hipchat room - * - * Notes: - * API token - HipChat API token - * Room - HipChat Room Id or name, where messages are sent - * Name - Name used to send the message (from) - * notify - Should the message trigger a notification in the clients - * version - The API version to use (HipChatHandler::API_V1 | HipChatHandler::API_V2) - * - * @author Rafael Dohms - * @see https://www.hipchat.com/docs/api - */ -class HipChatHandler extends SocketHandler -{ - /** - * Use API version 1 - */ - const API_V1 = 'v1'; - - /** - * Use API version v2 - */ - const API_V2 = 'v2'; - - /** - * The maximum allowed length for the name used in the "from" field. - */ - const MAXIMUM_NAME_LENGTH = 15; - - /** - * The maximum allowed length for the message. - */ - const MAXIMUM_MESSAGE_LENGTH = 9500; - - /** - * @var string - */ - private $token; - - /** - * @var string - */ - private $room; - - /** - * @var string - */ - private $name; - - /** - * @var bool - */ - private $notify; - - /** - * @var string - */ - private $format; - - /** - * @var string - */ - private $host; - - /** - * @var string - */ - private $version; - - /** - * @param string $token HipChat API Token - * @param string $room The room that should be alerted of the message (Id or Name) - * @param string $name Name used in the "from" field. - * @param bool $notify Trigger a notification in clients or not - * @param int $level The minimum logging level at which this handler will be triggered - * @param bool $bubble Whether the messages that are handled can bubble up the stack or not - * @param bool $useSSL Whether to connect via SSL. - * @param string $format The format of the messages (default to text, can be set to html if you have html in the messages) - * @param string $host The HipChat server hostname. - * @param string $version The HipChat API version (default HipChatHandler::API_V1) - */ - public function __construct($token, $room, $name = 'Monolog', $notify = false, $level = Logger::CRITICAL, $bubble = true, $useSSL = true, $format = 'text', $host = 'api.hipchat.com', $version = self::API_V1) - { - if ($version == self::API_V1 && !$this->validateStringLength($name, static::MAXIMUM_NAME_LENGTH)) { - throw new \InvalidArgumentException('The supplied name is too long. HipChat\'s v1 API supports names up to 15 UTF-8 characters.'); - } - - $connectionString = $useSSL ? 'ssl://'.$host.':443' : $host.':80'; - parent::__construct($connectionString, $level, $bubble); - - $this->token = $token; - $this->name = $name; - $this->notify = $notify; - $this->room = $room; - $this->format = $format; - $this->host = $host; - $this->version = $version; - } - - /** - * {@inheritdoc} - * - * @param array $record - * @return string - */ - protected function generateDataStream($record) - { - $content = $this->buildContent($record); - - return $this->buildHeader($content) . $content; - } - - /** - * Builds the body of API call - * - * @param array $record - * @return string - */ - private function buildContent($record) - { - $dataArray = array( - 'notify' => $this->version == self::API_V1 ? - ($this->notify ? 1 : 0) : - ($this->notify ? 'true' : 'false'), - 'message' => $record['formatted'], - 'message_format' => $this->format, - 'color' => $this->getAlertColor($record['level']), - ); - - if (!$this->validateStringLength($dataArray['message'], static::MAXIMUM_MESSAGE_LENGTH)) { - if (function_exists('mb_substr')) { - $dataArray['message'] = mb_substr($dataArray['message'], 0, static::MAXIMUM_MESSAGE_LENGTH).' [truncated]'; - } else { - $dataArray['message'] = substr($dataArray['message'], 0, static::MAXIMUM_MESSAGE_LENGTH).' [truncated]'; - } - } - - // if we are using the legacy API then we need to send some additional information - if ($this->version == self::API_V1) { - $dataArray['room_id'] = $this->room; - } - - // append the sender name if it is set - // always append it if we use the v1 api (it is required in v1) - if ($this->version == self::API_V1 || $this->name !== null) { - $dataArray['from'] = (string) $this->name; - } - - return http_build_query($dataArray); - } - - /** - * Builds the header of the API Call - * - * @param string $content - * @return string - */ - private function buildHeader($content) - { - if ($this->version == self::API_V1) { - $header = "POST /v1/rooms/message?format=json&auth_token={$this->token} HTTP/1.1\r\n"; - } else { - // needed for rooms with special (spaces, etc) characters in the name - $room = rawurlencode($this->room); - $header = "POST /v2/room/{$room}/notification?auth_token={$this->token} HTTP/1.1\r\n"; - } - - $header .= "Host: {$this->host}\r\n"; - $header .= "Content-Type: application/x-www-form-urlencoded\r\n"; - $header .= "Content-Length: " . strlen($content) . "\r\n"; - $header .= "\r\n"; - - return $header; - } - - /** - * Assigns a color to each level of log records. - * - * @param int $level - * @return string - */ - protected function getAlertColor($level) - { - switch (true) { - case $level >= Logger::ERROR: - return 'red'; - case $level >= Logger::WARNING: - return 'yellow'; - case $level >= Logger::INFO: - return 'green'; - case $level == Logger::DEBUG: - return 'gray'; - default: - return 'yellow'; - } - } - - /** - * {@inheritdoc} - * - * @param array $record - */ - protected function write(array $record) - { - parent::write($record); - $this->closeSocket(); - } - - /** - * {@inheritdoc} - */ - public function handleBatch(array $records) - { - if (count($records) == 0) { - return true; - } - - $batchRecords = $this->combineRecords($records); - - $handled = false; - foreach ($batchRecords as $batchRecord) { - if ($this->isHandling($batchRecord)) { - $this->write($batchRecord); - $handled = true; - } - } - - if (!$handled) { - return false; - } - - return false === $this->bubble; - } - - /** - * Combines multiple records into one. Error level of the combined record - * will be the highest level from the given records. Datetime will be taken - * from the first record. - * - * @param $records - * @return array - */ - private function combineRecords($records) - { - $batchRecord = null; - $batchRecords = array(); - $messages = array(); - $formattedMessages = array(); - $level = 0; - $levelName = null; - $datetime = null; - - foreach ($records as $record) { - $record = $this->processRecord($record); - - if ($record['level'] > $level) { - $level = $record['level']; - $levelName = $record['level_name']; - } - - if (null === $datetime) { - $datetime = $record['datetime']; - } - - $messages[] = $record['message']; - $messageStr = implode(PHP_EOL, $messages); - $formattedMessages[] = $this->getFormatter()->format($record); - $formattedMessageStr = implode('', $formattedMessages); - - $batchRecord = array( - 'message' => $messageStr, - 'formatted' => $formattedMessageStr, - 'context' => array(), - 'extra' => array(), - ); - - if (!$this->validateStringLength($batchRecord['formatted'], static::MAXIMUM_MESSAGE_LENGTH)) { - // Pop the last message and implode the remaining messages - $lastMessage = array_pop($messages); - $lastFormattedMessage = array_pop($formattedMessages); - $batchRecord['message'] = implode(PHP_EOL, $messages); - $batchRecord['formatted'] = implode('', $formattedMessages); - - $batchRecords[] = $batchRecord; - $messages = array($lastMessage); - $formattedMessages = array($lastFormattedMessage); - - $batchRecord = null; - } - } - - if (null !== $batchRecord) { - $batchRecords[] = $batchRecord; - } - - // Set the max level and datetime for all records - foreach ($batchRecords as &$batchRecord) { - $batchRecord = array_merge( - $batchRecord, - array( - 'level' => $level, - 'level_name' => $levelName, - 'datetime' => $datetime, - ) - ); - } - - return $batchRecords; - } - - /** - * Validates the length of a string. - * - * If the `mb_strlen()` function is available, it will use that, as HipChat - * allows UTF-8 characters. Otherwise, it will fall back to `strlen()`. - * - * Note that this might cause false failures in the specific case of using - * a valid name with less than 16 characters, but 16 or more bytes, on a - * system where `mb_strlen()` is unavailable. - * - * @param string $str - * @param int $length - * - * @return bool - */ - private function validateStringLength($str, $length) - { - if (function_exists('mb_strlen')) { - return (mb_strlen($str) <= $length); - } - - return (strlen($str) <= $length); - } -} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/IFTTTHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/IFTTTHandler.php index d60a3c82..000ccea4 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/IFTTTHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/IFTTTHandler.php @@ -1,4 +1,4 @@ -eventName = $eventName; $this->secretKey = $secretKey; @@ -44,25 +49,25 @@ public function __construct($eventName, $secretKey, $level = Logger::ERROR, $bub } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function write(array $record) + public function write(array $record): void { - $postData = array( + $postData = [ "value1" => $record["channel"], "value2" => $record["level_name"], "value3" => $record["message"], - ); - $postString = json_encode($postData); + ]; + $postString = Utils::jsonEncode($postData); $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, "https://maker.ifttt.com/trigger/" . $this->eventName . "/with/key/" . $this->secretKey); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $postString); - curl_setopt($ch, CURLOPT_HTTPHEADER, array( + curl_setopt($ch, CURLOPT_HTTPHEADER, [ "Content-Type: application/json", - )); + ]); Curl\Util::execute($ch); } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/InsightOpsHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/InsightOpsHandler.php new file mode 100644 index 00000000..71f64a26 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/InsightOpsHandler.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * Inspired on LogEntriesHandler. + * + * @author Robert Kaufmann III + * @author Gabriel Machado + */ +class InsightOpsHandler extends SocketHandler +{ + /** + * @var string + */ + protected $logToken; + + /** + * @param string $token Log token supplied by InsightOps + * @param string $region Region where InsightOps account is hosted. Could be 'us' or 'eu'. + * @param bool $useSSL Whether or not SSL encryption should be used + * + * @throws MissingExtensionException If SSL encryption is set to true and OpenSSL is missing + */ + public function __construct( + string $token, + string $region = 'us', + bool $useSSL = true, + $level = Logger::DEBUG, + bool $bubble = true, + bool $persistent = false, + float $timeout = 0.0, + float $writingTimeout = 10.0, + ?float $connectionTimeout = null, + ?int $chunkSize = null + ) { + if ($useSSL && !extension_loaded('openssl')) { + throw new MissingExtensionException('The OpenSSL PHP plugin is required to use SSL encrypted connection for InsightOpsHandler'); + } + + $endpoint = $useSSL + ? 'ssl://' . $region . '.data.logs.insight.rapid7.com:443' + : $region . '.data.logs.insight.rapid7.com:80'; + + parent::__construct( + $endpoint, + $level, + $bubble, + $persistent, + $timeout, + $writingTimeout, + $connectionTimeout, + $chunkSize + ); + $this->logToken = $token; + } + + /** + * {@inheritDoc} + */ + protected function generateDataStream(array $record): string + { + return $this->logToken . ' ' . $record['formatted']; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/LogEntriesHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/LogEntriesHandler.php index 494c605b..25fcd159 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/LogEntriesHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/LogEntriesHandler.php @@ -1,4 +1,4 @@ -logToken = $token; } /** - * {@inheritdoc} - * - * @param array $record - * @return string + * {@inheritDoc} */ - protected function generateDataStream($record) + protected function generateDataStream(array $record): string { return $this->logToken . ' ' . $record['formatted']; } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/LogglyHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/LogglyHandler.php index bcd62e1c..6d13db37 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/LogglyHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/LogglyHandler.php @@ -1,4 +1,4 @@ -token = $token; @@ -42,26 +59,72 @@ public function __construct($token, $level = Logger::DEBUG, $bubble = true) parent::__construct($level, $bubble); } - public function setTag($tag) + /** + * Loads and returns the shared curl handler for the given endpoint. + * + * @param string $endpoint + * + * @return resource|CurlHandle + */ + protected function getCurlHandler(string $endpoint) { - $tag = !empty($tag) ? $tag : array(); - $this->tag = is_array($tag) ? $tag : array($tag); + if (!array_key_exists($endpoint, $this->curlHandlers)) { + $this->curlHandlers[$endpoint] = $this->loadCurlHandle($endpoint); + } + + return $this->curlHandlers[$endpoint]; } - public function addTag($tag) + /** + * Starts a fresh curl session for the given endpoint and returns its handler. + * + * @param string $endpoint + * + * @return resource|CurlHandle + */ + private function loadCurlHandle(string $endpoint) + { + $url = sprintf("https://%s/%s/%s/", static::HOST, $endpoint, $this->token); + + $ch = curl_init(); + + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + + return $ch; + } + + /** + * @param string[]|string $tag + */ + public function setTag($tag): self + { + $tag = !empty($tag) ? $tag : []; + $this->tag = is_array($tag) ? $tag : [$tag]; + + return $this; + } + + /** + * @param string[]|string $tag + */ + public function addTag($tag): self { if (!empty($tag)) { - $tag = is_array($tag) ? $tag : array($tag); + $tag = is_array($tag) ? $tag : [$tag]; $this->tag = array_unique(array_merge($this->tag, $tag)); } + + return $this; } - protected function write(array $record) + protected function write(array $record): void { - $this->send($record["formatted"], self::ENDPOINT_SINGLE); + $this->send($record["formatted"], static::ENDPOINT_SINGLE); } - public function handleBatch(array $records) + public function handleBatch(array $records): void { $level = $this->level; @@ -70,32 +133,27 @@ public function handleBatch(array $records) }); if ($records) { - $this->send($this->getFormatter()->formatBatch($records), self::ENDPOINT_BATCH); + $this->send($this->getFormatter()->formatBatch($records), static::ENDPOINT_BATCH); } } - protected function send($data, $endpoint) + protected function send(string $data, string $endpoint): void { - $url = sprintf("https://%s/%s/%s/", self::HOST, $endpoint, $this->token); + $ch = $this->getCurlHandler($endpoint); - $headers = array('Content-Type: application/json'); + $headers = ['Content-Type: application/json']; if (!empty($this->tag)) { $headers[] = 'X-LOGGLY-TAG: '.implode(',', $this->tag); } - $ch = curl_init(); - - curl_setopt($ch, CURLOPT_URL, $url); - curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $data); curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - Curl\Util::execute($ch); + Curl\Util::execute($ch, 5, false); } - protected function getDefaultFormatter() + protected function getDefaultFormatter(): FormatterInterface { return new LogglyFormatter(); } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/LogmaticHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/LogmaticHandler.php new file mode 100644 index 00000000..859a4690 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/LogmaticHandler.php @@ -0,0 +1,106 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\LogmaticFormatter; + +/** + * @author Julien Breux + */ +class LogmaticHandler extends SocketHandler +{ + /** + * @var string + */ + private $logToken; + + /** + * @var string + */ + private $hostname; + + /** + * @var string + */ + private $appname; + + /** + * @param string $token Log token supplied by Logmatic. + * @param string $hostname Host name supplied by Logmatic. + * @param string $appname Application name supplied by Logmatic. + * @param bool $useSSL Whether or not SSL encryption should be used. + * + * @throws MissingExtensionException If SSL encryption is set to true and OpenSSL is missing + */ + public function __construct( + string $token, + string $hostname = '', + string $appname = '', + bool $useSSL = true, + $level = Logger::DEBUG, + bool $bubble = true, + bool $persistent = false, + float $timeout = 0.0, + float $writingTimeout = 10.0, + ?float $connectionTimeout = null, + ?int $chunkSize = null + ) { + if ($useSSL && !extension_loaded('openssl')) { + throw new MissingExtensionException('The OpenSSL PHP extension is required to use SSL encrypted connection for LogmaticHandler'); + } + + $endpoint = $useSSL ? 'ssl://api.logmatic.io:10515' : 'api.logmatic.io:10514'; + $endpoint .= '/v1/'; + + parent::__construct( + $endpoint, + $level, + $bubble, + $persistent, + $timeout, + $writingTimeout, + $connectionTimeout, + $chunkSize + ); + + $this->logToken = $token; + $this->hostname = $hostname; + $this->appname = $appname; + } + + /** + * {@inheritDoc} + */ + protected function generateDataStream(array $record): string + { + return $this->logToken . ' ' . $record['formatted']; + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter(): FormatterInterface + { + $formatter = new LogmaticFormatter(); + + if (!empty($this->hostname)) { + $formatter->setHostname($this->hostname); + } + if (!empty($this->appname)) { + $formatter->setAppname($this->appname); + } + + return $formatter; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/MailHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/MailHandler.php index 9e232838..97f34320 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/MailHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/MailHandler.php @@ -1,4 +1,4 @@ -level) { continue; } - $messages[] = $this->processRecord($record); + /** @var Record $message */ + $message = $this->processRecord($record); + $messages[] = $message; } if (!empty($messages)) { @@ -42,18 +49,24 @@ public function handleBatch(array $records) * * @param string $content formatted email body to be sent * @param array $records the array of log records that formed this content + * + * @phpstan-param Record[] $records */ - abstract protected function send($content, array $records); + abstract protected function send(string $content, array $records): void; /** - * {@inheritdoc} + * {@inheritDoc} */ - protected function write(array $record) + protected function write(array $record): void { - $this->send((string) $record['formatted'], array($record)); + $this->send((string) $record['formatted'], [$record]); } - protected function getHighestRecord(array $records) + /** + * @phpstan-param non-empty-array $records + * @phpstan-return Record + */ + protected function getHighestRecord(array $records): array { $highestRecord = null; foreach ($records as $record) { @@ -64,4 +77,19 @@ protected function getHighestRecord(array $records) return $highestRecord; } + + protected function isHtmlBody(string $body): bool + { + return ($body[0] ?? null) === '<'; + } + + /** + * Gets the default formatter. + * + * @return FormatterInterface + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new HtmlFormatter(); + } } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/MandrillHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/MandrillHandler.php index ab95924f..3003500e 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/MandrillHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/MandrillHandler.php @@ -1,4 +1,4 @@ -message = $message; @@ -44,24 +48,35 @@ public function __construct($apiKey, $message, $level = Logger::ERROR, $bubble = } /** - * {@inheritdoc} + * {@inheritDoc} */ - protected function send($content, array $records) + protected function send(string $content, array $records): void { + $mime = 'text/plain'; + if ($this->isHtmlBody($content)) { + $mime = 'text/html'; + } + $message = clone $this->message; - $message->setBody($content); - $message->setDate(time()); + $message->setBody($content, $mime); + /** @phpstan-ignore-next-line */ + if (version_compare(Swift::VERSION, '6.0.0', '>=')) { + $message->setDate(new \DateTimeImmutable()); + } else { + /** @phpstan-ignore-next-line */ + $message->setDate(time()); + } $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, 'https://mandrillapp.com/api/1.0/messages/send-raw.json'); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query(array( + curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([ 'key' => $this->apiKey, 'raw_message' => (string) $message, 'async' => false, - ))); + ])); Curl\Util::execute($ch); } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/MissingExtensionException.php b/vendor/monolog/monolog/src/Monolog/Handler/MissingExtensionException.php index 4724a7e2..3965aeea 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/MissingExtensionException.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/MissingExtensionException.php @@ -1,4 +1,4 @@ - + * @author Christian Bergau */ class MissingExtensionException extends \Exception { diff --git a/vendor/monolog/monolog/src/Monolog/Handler/MongoDBHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/MongoDBHandler.php index 56fe755b..30630911 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/MongoDBHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/MongoDBHandler.php @@ -1,4 +1,4 @@ -pushHandler($mongodb); * - * @author Thomas Tourlourat + * The above examples uses the MongoDB PHP library's client class; however, the + * MongoDB\Driver\Manager class from ext-mongodb is also supported. */ class MongoDBHandler extends AbstractProcessingHandler { - protected $mongoCollection; + /** @var \MongoDB\Collection */ + private $collection; + /** @var Client|Manager */ + private $manager; + /** @var string */ + private $namespace; - public function __construct($mongo, $database, $collection, $level = Logger::DEBUG, $bubble = true) + /** + * Constructor. + * + * @param Client|Manager $mongodb MongoDB library or driver client + * @param string $database Database name + * @param string $collection Collection name + */ + public function __construct($mongodb, string $database, string $collection, $level = Logger::DEBUG, bool $bubble = true) { - if (!($mongo instanceof \MongoClient || $mongo instanceof \Mongo || $mongo instanceof \MongoDB\Client)) { - throw new \InvalidArgumentException('MongoClient, Mongo or MongoDB\Client instance required'); + if (!($mongodb instanceof Client || $mongodb instanceof Manager)) { + throw new \InvalidArgumentException('MongoDB\Client or MongoDB\Driver\Manager instance required'); } - $this->mongoCollection = $mongo->selectCollection($database, $collection); + if ($mongodb instanceof Client) { + $this->collection = $mongodb->selectCollection($database, $collection); + } else { + $this->manager = $mongodb; + $this->namespace = $database . '.' . $collection; + } parent::__construct($level, $bubble); } - protected function write(array $record) + protected function write(array $record): void { - if ($this->mongoCollection instanceof \MongoDB\Collection) { - $this->mongoCollection->insertOne($record["formatted"]); - } else { - $this->mongoCollection->save($record["formatted"]); + if (isset($this->collection)) { + $this->collection->insertOne($record['formatted']); + } + + if (isset($this->manager, $this->namespace)) { + $bulk = new BulkWrite; + $bulk->insert($record["formatted"]); + $this->manager->executeBulkWrite($this->namespace, $bulk); } } /** * {@inheritDoc} */ - protected function getDefaultFormatter() + protected function getDefaultFormatter(): FormatterInterface { - return new NormalizerFormatter(); + return new MongoDBFormatter; } } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/NativeMailerHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/NativeMailerHandler.php index d7807fd1..0c0a3bdb 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/NativeMailerHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/NativeMailerHandler.php @@ -1,4 +1,4 @@ -to = is_array($to) ? $to : array($to); + $this->to = (array) $to; $this->subject = $subject; $this->addHeader(sprintf('From: %s', $from)); $this->maxColumnWidth = $maxColumnWidth; @@ -84,10 +82,9 @@ public function __construct($to, $subject, $from, $level = Logger::ERROR, $bubbl /** * Add headers to the message * - * @param string|array $headers Custom added headers - * @return self + * @param string|string[] $headers Custom added headers */ - public function addHeader($headers) + public function addHeader($headers): self { foreach ((array) $headers as $header) { if (strpos($header, "\n") !== false || strpos($header, "\r") !== false) { @@ -102,10 +99,9 @@ public function addHeader($headers) /** * Add parameters to the message * - * @param string|array $parameters Custom added parameters - * @return self + * @param string|string[] $parameters Custom added parameters */ - public function addParameter($parameters) + public function addParameter($parameters): self { $this->parameters = array_merge($this->parameters, (array) $parameters); @@ -113,14 +109,19 @@ public function addParameter($parameters) } /** - * {@inheritdoc} + * {@inheritDoc} */ - protected function send($content, array $records) + protected function send(string $content, array $records): void { - $content = wordwrap($content, $this->maxColumnWidth); + $contentType = $this->getContentType() ?: ($this->isHtmlBody($content) ? 'text/html' : 'text/plain'); + + if ($contentType !== 'text/html') { + $content = wordwrap($content, $this->maxColumnWidth); + } + $headers = ltrim(implode("\r\n", $this->headers) . "\r\n", "\r\n"); - $headers .= 'Content-type: ' . $this->getContentType() . '; charset=' . $this->getEncoding() . "\r\n"; - if ($this->getContentType() == 'text/html' && false === strpos($headers, 'MIME-Version:')) { + $headers .= 'Content-type: ' . $contentType . '; charset=' . $this->getEncoding() . "\r\n"; + if ($contentType === 'text/html' && false === strpos($headers, 'MIME-Version:')) { $headers .= 'MIME-Version: 1.0' . "\r\n"; } @@ -136,28 +137,20 @@ protected function send($content, array $records) } } - /** - * @return string $contentType - */ - public function getContentType() + public function getContentType(): ?string { return $this->contentType; } - /** - * @return string $encoding - */ - public function getEncoding() + public function getEncoding(): string { return $this->encoding; } /** - * @param string $contentType The content type of the email - Defaults to text/plain. Use text/html for HTML - * messages. - * @return self + * @param string $contentType The content type of the email - Defaults to text/plain. Use text/html for HTML messages. */ - public function setContentType($contentType) + public function setContentType(string $contentType): self { if (strpos($contentType, "\n") !== false || strpos($contentType, "\r") !== false) { throw new \InvalidArgumentException('The content type can not contain newline characters to prevent email header injection'); @@ -168,11 +161,7 @@ public function setContentType($contentType) return $this; } - /** - * @param string $encoding - * @return self - */ - public function setEncoding($encoding) + public function setEncoding(string $encoding): self { if (strpos($encoding, "\n") !== false || strpos($encoding, "\r") !== false) { throw new \InvalidArgumentException('The encoding can not contain newline characters to prevent email header injection'); diff --git a/vendor/monolog/monolog/src/Monolog/Handler/NewRelicHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/NewRelicHandler.php index 6718e9e0..114d749e 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/NewRelicHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/NewRelicHandler.php @@ -1,4 +1,4 @@ -isNewRelicEnabled()) { throw new MissingExtensionException('The newrelic PHP extension is required to use the NewRelicHandler'); @@ -84,7 +88,7 @@ protected function write(array $record) unset($record['formatted']['context']['transaction_name']); } - if (isset($record['context']['exception']) && $record['context']['exception'] instanceof \Exception) { + if (isset($record['context']['exception']) && $record['context']['exception'] instanceof \Throwable) { newrelic_notice_error($record['message'], $record['context']['exception']); unset($record['formatted']['context']['exception']); } else { @@ -121,7 +125,7 @@ protected function write(array $record) * * @return bool */ - protected function isNewRelicEnabled() + protected function isNewRelicEnabled(): bool { return extension_loaded('newrelic'); } @@ -130,10 +134,9 @@ protected function isNewRelicEnabled() * Returns the appname where this log should be sent. Each log can override the default appname, set in this * handler's constructor, by providing the appname in it's context. * - * @param array $context - * @return null|string + * @param mixed[] $context */ - protected function getAppName(array $context) + protected function getAppName(array $context): ?string { if (isset($context['appname'])) { return $context['appname']; @@ -146,11 +149,9 @@ protected function getAppName(array $context) * Returns the name of the current transaction. Each log can override the default transaction name, set in this * handler's constructor, by providing the transaction_name in it's context * - * @param array $context - * - * @return null|string + * @param mixed[] $context */ - protected function getTransactionName(array $context) + protected function getTransactionName(array $context): ?string { if (isset($context['transaction_name'])) { return $context['transaction_name']; @@ -161,20 +162,16 @@ protected function getTransactionName(array $context) /** * Sets the NewRelic application that should receive this log. - * - * @param string $appName */ - protected function setNewRelicAppName($appName) + protected function setNewRelicAppName(string $appName): void { newrelic_set_appname($appName); } /** * Overwrites the name of the current transaction - * - * @param string $transactionName */ - protected function setNewRelicTransactionName($transactionName) + protected function setNewRelicTransactionName(string $transactionName): void { newrelic_name_transaction($transactionName); } @@ -183,19 +180,19 @@ protected function setNewRelicTransactionName($transactionName) * @param string $key * @param mixed $value */ - protected function setNewRelicParameter($key, $value) + protected function setNewRelicParameter(string $key, $value): void { if (null === $value || is_scalar($value)) { newrelic_add_custom_parameter($key, $value); } else { - newrelic_add_custom_parameter($key, @json_encode($value)); + newrelic_add_custom_parameter($key, Utils::jsonEncode($value, null, true)); } } /** * {@inheritDoc} */ - protected function getDefaultFormatter() + protected function getDefaultFormatter(): FormatterInterface { return new NormalizerFormatter(); } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/NoopHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/NoopHandler.php new file mode 100644 index 00000000..1ddf0beb --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/NoopHandler.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +/** + * No-op + * + * This handler handles anything, but does nothing, and does not stop bubbling to the rest of the stack. + * This can be used for testing, or to disable a handler when overriding a configuration without + * influencing the rest of the stack. + * + * @author Roel Harbers + */ +class NoopHandler extends Handler +{ + /** + * {@inheritDoc} + */ + public function isHandling(array $record): bool + { + return true; + } + + /** + * {@inheritDoc} + */ + public function handle(array $record): bool + { + return false; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/NullHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/NullHandler.php index 4b845883..e75ee0c6 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/NullHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/NullHandler.php @@ -1,4 +1,4 @@ - + * + * @phpstan-import-type Level from \Monolog\Logger + * @phpstan-import-type LevelName from \Monolog\Logger */ -class NullHandler extends AbstractHandler +class NullHandler extends Handler { /** - * @param int $level The minimum logging level at which this handler will be triggered + * @var int + */ + private $level; + + /** + * @param string|int $level The minimum logging level at which this handler will be triggered + * + * @phpstan-param Level|LevelName|LogLevel::* $level */ public function __construct($level = Logger::DEBUG) { - parent::__construct($level, false); + $this->level = Logger::toMonologLevel($level); } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function handle(array $record) + public function isHandling(array $record): bool { - if ($record['level'] < $this->level) { - return false; - } + return $record['level'] >= $this->level; + } - return true; + /** + * {@inheritDoc} + */ + public function handle(array $record): bool + { + return $record['level'] >= $this->level; } } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/OverflowHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/OverflowHandler.php new file mode 100644 index 00000000..22068c9a --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/OverflowHandler.php @@ -0,0 +1,149 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Formatter\FormatterInterface; + +/** + * Handler to only pass log messages when a certain threshold of number of messages is reached. + * + * This can be useful in cases of processing a batch of data, but you're for example only interested + * in case it fails catastrophically instead of a warning for 1 or 2 events. Worse things can happen, right? + * + * Usage example: + * + * ``` + * $log = new Logger('application'); + * $handler = new SomeHandler(...) + * + * // Pass all warnings to the handler when more than 10 & all error messages when more then 5 + * $overflow = new OverflowHandler($handler, [Logger::WARNING => 10, Logger::ERROR => 5]); + * + * $log->pushHandler($overflow); + *``` + * + * @author Kris Buist + */ +class OverflowHandler extends AbstractHandler implements FormattableHandlerInterface +{ + /** @var HandlerInterface */ + private $handler; + + /** @var int[] */ + private $thresholdMap = [ + Logger::DEBUG => 0, + Logger::INFO => 0, + Logger::NOTICE => 0, + Logger::WARNING => 0, + Logger::ERROR => 0, + Logger::CRITICAL => 0, + Logger::ALERT => 0, + Logger::EMERGENCY => 0, + ]; + + /** + * Buffer of all messages passed to the handler before the threshold was reached + * + * @var mixed[][] + */ + private $buffer = []; + + /** + * @param HandlerInterface $handler + * @param int[] $thresholdMap Dictionary of logger level => threshold + */ + public function __construct( + HandlerInterface $handler, + array $thresholdMap = [], + $level = Logger::DEBUG, + bool $bubble = true + ) { + $this->handler = $handler; + foreach ($thresholdMap as $thresholdLevel => $threshold) { + $this->thresholdMap[$thresholdLevel] = $threshold; + } + parent::__construct($level, $bubble); + } + + /** + * Handles a record. + * + * All records may be passed to this method, and the handler should discard + * those that it does not want to handle. + * + * The return value of this function controls the bubbling process of the handler stack. + * Unless the bubbling is interrupted (by returning true), the Logger class will keep on + * calling further handlers in the stack with a given log record. + * + * {@inheritDoc} + */ + public function handle(array $record): bool + { + if ($record['level'] < $this->level) { + return false; + } + + $level = $record['level']; + + if (!isset($this->thresholdMap[$level])) { + $this->thresholdMap[$level] = 0; + } + + if ($this->thresholdMap[$level] > 0) { + // The overflow threshold is not yet reached, so we're buffering the record and lowering the threshold by 1 + $this->thresholdMap[$level]--; + $this->buffer[$level][] = $record; + + return false === $this->bubble; + } + + if ($this->thresholdMap[$level] == 0) { + // This current message is breaking the threshold. Flush the buffer and continue handling the current record + foreach ($this->buffer[$level] ?? [] as $buffered) { + $this->handler->handle($buffered); + } + $this->thresholdMap[$level]--; + unset($this->buffer[$level]); + } + + $this->handler->handle($record); + + return false === $this->bubble; + } + + /** + * {@inheritDoc} + */ + public function setFormatter(FormatterInterface $formatter): HandlerInterface + { + if ($this->handler instanceof FormattableHandlerInterface) { + $this->handler->setFormatter($formatter); + + return $this; + } + + throw new \UnexpectedValueException('The nested handler of type '.get_class($this->handler).' does not support formatters.'); + } + + /** + * {@inheritDoc} + */ + public function getFormatter(): FormatterInterface + { + if ($this->handler instanceof FormattableHandlerInterface) { + return $this->handler->getFormatter(); + } + + throw new \UnexpectedValueException('The nested handler of type '.get_class($this->handler).' does not support formatters.'); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/PHPConsoleHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/PHPConsoleHandler.php index 1f2076a4..23a1d117 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/PHPConsoleHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/PHPConsoleHandler.php @@ -1,4 +1,4 @@ -addDebug('SELECT * FROM users', array('db', 'time' => 0.012)); + * $logger->debug('SELECT * FROM users', array('db', 'time' => 0.012)); * PC::debug($_SERVER); // PHP Console debugger for any type of vars * * @author Sergey Barbushin https://www.linkedin.com/in/barbushin + * + * @phpstan-import-type Record from \Monolog\Logger + * @deprecated Since 2.8.0 and 3.2.0, PHPConsole is abandoned and thus we will drop this handler in Monolog 4 */ class PHPConsoleHandler extends AbstractProcessingHandler { - private $options = array( + /** @var array */ + private $options = [ 'enabled' => true, // bool Is PHP Console server enabled - 'classesPartialsTraceIgnore' => array('Monolog\\'), // array Hide calls of classes started with... - 'debugTagsKeysInContext' => array(0, 'tag'), // bool Is PHP Console server enabled + 'classesPartialsTraceIgnore' => ['Monolog\\'], // array Hide calls of classes started with... + 'debugTagsKeysInContext' => [0, 'tag'], // bool Is PHP Console server enabled 'useOwnErrorsHandler' => false, // bool Enable errors handling 'useOwnExceptionsHandler' => false, // bool Enable exceptions handling 'sourcesBasePath' => null, // string Base path of all project sources to strip in errors source paths @@ -51,7 +56,7 @@ class PHPConsoleHandler extends AbstractProcessingHandler 'headersLimit' => null, // int|null Set headers size limit for your web-server 'password' => null, // string|null Protect PHP Console connection by password 'enableSslOnlyMode' => false, // bool Force connection by SSL for clients with PHP Console installed - 'ipMasks' => array(), // array Set IP masks of clients that will be allowed to connect to PHP Console: array('192.168.*.*', '127.0.0.1') + 'ipMasks' => [], // array Set IP masks of clients that will be allowed to connect to PHP Console: array('192.168.*.*', '127.0.0.1') 'enableEvalListener' => false, // bool Enable eval request to be handled by eval dispatcher(if enabled, 'password' option is also required) 'dumperDetectCallbacks' => false, // bool Convert callback items in dumper vars to (callback SomeClass::someMethod) strings 'dumperLevelLimit' => 5, // int Maximum dumped vars array or object nested dump level @@ -59,40 +64,43 @@ class PHPConsoleHandler extends AbstractProcessingHandler 'dumperItemSizeLimit' => 5000, // int Maximum length of any string or dumped array item 'dumperDumpSizeLimit' => 500000, // int Maximum approximate size of dumped vars result formatted in JSON 'detectDumpTraceAndSource' => false, // bool Autodetect and append trace data to debug - 'dataStorage' => null, // PhpConsole\Storage|null Fixes problem with custom $_SESSION handler(see http://goo.gl/Ne8juJ) - ); + 'dataStorage' => null, // \PhpConsole\Storage|null Fixes problem with custom $_SESSION handler(see http://goo.gl/Ne8juJ) + ]; /** @var Connector */ private $connector; /** - * @param array $options See \Monolog\Handler\PHPConsoleHandler::$options for more details - * @param Connector|null $connector Instance of \PhpConsole\Connector class (optional) - * @param int $level - * @param bool $bubble - * @throws Exception + * @param array $options See \Monolog\Handler\PHPConsoleHandler::$options for more details + * @param Connector|null $connector Instance of \PhpConsole\Connector class (optional) + * @throws \RuntimeException */ - public function __construct(array $options = array(), Connector $connector = null, $level = Logger::DEBUG, $bubble = true) + public function __construct(array $options = [], ?Connector $connector = null, $level = Logger::DEBUG, bool $bubble = true) { if (!class_exists('PhpConsole\Connector')) { - throw new Exception('PHP Console library not found. See https://github.com/barbushin/php-console#installation'); + throw new \RuntimeException('PHP Console library not found. See https://github.com/barbushin/php-console#installation'); } parent::__construct($level, $bubble); $this->options = $this->initOptions($options); $this->connector = $this->initConnector($connector); } - private function initOptions(array $options) + /** + * @param array $options + * + * @return array + */ + private function initOptions(array $options): array { $wrongOptions = array_diff(array_keys($options), array_keys($this->options)); if ($wrongOptions) { - throw new Exception('Unknown options: ' . implode(', ', $wrongOptions)); + throw new \RuntimeException('Unknown options: ' . implode(', ', $wrongOptions)); } return array_replace($this->options, $options); } - private function initConnector(Connector $connector = null) + private function initConnector(?Connector $connector = null): Connector { if (!$connector) { if ($this->options['dataStorage']) { @@ -107,7 +115,7 @@ private function initConnector(Connector $connector = null) if ($this->options['enabled'] && $connector->isActiveClient()) { if ($this->options['useOwnErrorsHandler'] || $this->options['useOwnExceptionsHandler']) { - $handler = Handler::getInstance(); + $handler = VendorPhpConsoleHandler::getInstance(); $handler->setHandleErrors($this->options['useOwnErrorsHandler']); $handler->setHandleExceptions($this->options['useOwnExceptionsHandler']); $handler->start(); @@ -147,17 +155,20 @@ private function initConnector(Connector $connector = null) return $connector; } - public function getConnector() + public function getConnector(): Connector { return $this->connector; } - public function getOptions() + /** + * @return array + */ + public function getOptions(): array { return $this->options; } - public function handle(array $record) + public function handle(array $record): bool { if ($this->options['enabled'] && $this->connector->isActiveClient()) { return parent::handle($record); @@ -168,49 +179,59 @@ public function handle(array $record) /** * Writes the record down to the log of the implementing handler - * - * @param array $record - * @return void */ - protected function write(array $record) + protected function write(array $record): void { if ($record['level'] < Logger::NOTICE) { $this->handleDebugRecord($record); - } elseif (isset($record['context']['exception']) && $record['context']['exception'] instanceof Exception) { + } elseif (isset($record['context']['exception']) && $record['context']['exception'] instanceof \Throwable) { $this->handleExceptionRecord($record); } else { $this->handleErrorRecord($record); } } - private function handleDebugRecord(array $record) + /** + * @phpstan-param Record $record + */ + private function handleDebugRecord(array $record): void { $tags = $this->getRecordTags($record); $message = $record['message']; if ($record['context']) { - $message .= ' ' . json_encode($this->connector->getDumper()->dump(array_filter($record['context']))); + $message .= ' ' . Utils::jsonEncode($this->connector->getDumper()->dump(array_filter($record['context'])), null, true); } $this->connector->getDebugDispatcher()->dispatchDebug($message, $tags, $this->options['classesPartialsTraceIgnore']); } - private function handleExceptionRecord(array $record) + /** + * @phpstan-param Record $record + */ + private function handleExceptionRecord(array $record): void { $this->connector->getErrorsDispatcher()->dispatchException($record['context']['exception']); } - private function handleErrorRecord(array $record) + /** + * @phpstan-param Record $record + */ + private function handleErrorRecord(array $record): void { $context = $record['context']; $this->connector->getErrorsDispatcher()->dispatchError( - isset($context['code']) ? $context['code'] : null, - isset($context['message']) ? $context['message'] : $record['message'], - isset($context['file']) ? $context['file'] : null, - isset($context['line']) ? $context['line'] : null, + $context['code'] ?? null, + $context['message'] ?? $record['message'], + $context['file'] ?? null, + $context['line'] ?? null, $this->options['classesPartialsTraceIgnore'] ); } + /** + * @phpstan-param Record $record + * @return string + */ private function getRecordTags(array &$record) { $tags = null; @@ -235,7 +256,7 @@ private function getRecordTags(array &$record) /** * {@inheritDoc} */ - protected function getDefaultFormatter() + protected function getDefaultFormatter(): FormatterInterface { return new LineFormatter('%message%'); } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/ProcessHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/ProcessHandler.php new file mode 100644 index 00000000..8a8cf1be --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/ProcessHandler.php @@ -0,0 +1,191 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * Stores to STDIN of any process, specified by a command. + * + * Usage example: + *
+ * $log = new Logger('myLogger');
+ * $log->pushHandler(new ProcessHandler('/usr/bin/php /var/www/monolog/someScript.php'));
+ * 
+ * + * @author Kolja Zuelsdorf + */ +class ProcessHandler extends AbstractProcessingHandler +{ + /** + * Holds the process to receive data on its STDIN. + * + * @var resource|bool|null + */ + private $process; + + /** + * @var string + */ + private $command; + + /** + * @var string|null + */ + private $cwd; + + /** + * @var resource[] + */ + private $pipes = []; + + /** + * @var array + */ + protected const DESCRIPTOR_SPEC = [ + 0 => ['pipe', 'r'], // STDIN is a pipe that the child will read from + 1 => ['pipe', 'w'], // STDOUT is a pipe that the child will write to + 2 => ['pipe', 'w'], // STDERR is a pipe to catch the any errors + ]; + + /** + * @param string $command Command for the process to start. Absolute paths are recommended, + * especially if you do not use the $cwd parameter. + * @param string|null $cwd "Current working directory" (CWD) for the process to be executed in. + * @throws \InvalidArgumentException + */ + public function __construct(string $command, $level = Logger::DEBUG, bool $bubble = true, ?string $cwd = null) + { + if ($command === '') { + throw new \InvalidArgumentException('The command argument must be a non-empty string.'); + } + if ($cwd === '') { + throw new \InvalidArgumentException('The optional CWD argument must be a non-empty string or null.'); + } + + parent::__construct($level, $bubble); + + $this->command = $command; + $this->cwd = $cwd; + } + + /** + * Writes the record down to the log of the implementing handler + * + * @throws \UnexpectedValueException + */ + protected function write(array $record): void + { + $this->ensureProcessIsStarted(); + + $this->writeProcessInput($record['formatted']); + + $errors = $this->readProcessErrors(); + if (empty($errors) === false) { + throw new \UnexpectedValueException(sprintf('Errors while writing to process: %s', $errors)); + } + } + + /** + * Makes sure that the process is actually started, and if not, starts it, + * assigns the stream pipes, and handles startup errors, if any. + */ + private function ensureProcessIsStarted(): void + { + if (is_resource($this->process) === false) { + $this->startProcess(); + + $this->handleStartupErrors(); + } + } + + /** + * Starts the actual process and sets all streams to non-blocking. + */ + private function startProcess(): void + { + $this->process = proc_open($this->command, static::DESCRIPTOR_SPEC, $this->pipes, $this->cwd); + + foreach ($this->pipes as $pipe) { + stream_set_blocking($pipe, false); + } + } + + /** + * Selects the STDERR stream, handles upcoming startup errors, and throws an exception, if any. + * + * @throws \UnexpectedValueException + */ + private function handleStartupErrors(): void + { + $selected = $this->selectErrorStream(); + if (false === $selected) { + throw new \UnexpectedValueException('Something went wrong while selecting a stream.'); + } + + $errors = $this->readProcessErrors(); + + if (is_resource($this->process) === false || empty($errors) === false) { + throw new \UnexpectedValueException( + sprintf('The process "%s" could not be opened: ' . $errors, $this->command) + ); + } + } + + /** + * Selects the STDERR stream. + * + * @return int|bool + */ + protected function selectErrorStream() + { + $empty = []; + $errorPipes = [$this->pipes[2]]; + + return stream_select($errorPipes, $empty, $empty, 1); + } + + /** + * Reads the errors of the process, if there are any. + * + * @codeCoverageIgnore + * @return string Empty string if there are no errors. + */ + protected function readProcessErrors(): string + { + return (string) stream_get_contents($this->pipes[2]); + } + + /** + * Writes to the input stream of the opened process. + * + * @codeCoverageIgnore + */ + protected function writeProcessInput(string $string): void + { + fwrite($this->pipes[0], $string); + } + + /** + * {@inheritDoc} + */ + public function close(): void + { + if (is_resource($this->process)) { + foreach ($this->pipes as $pipe) { + fclose($pipe); + } + proc_close($this->process); + $this->process = null; + } + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/ProcessableHandlerInterface.php b/vendor/monolog/monolog/src/Monolog/Handler/ProcessableHandlerInterface.php new file mode 100644 index 00000000..3adec7a4 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/ProcessableHandlerInterface.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Processor\ProcessorInterface; + +/** + * Interface to describe loggers that have processors + * + * @author Jordi Boggiano + * + * @phpstan-import-type Record from \Monolog\Logger + */ +interface ProcessableHandlerInterface +{ + /** + * Adds a processor in the stack. + * + * @psalm-param ProcessorInterface|callable(Record): Record $callback + * + * @param ProcessorInterface|callable $callback + * @return HandlerInterface self + */ + public function pushProcessor(callable $callback): HandlerInterface; + + /** + * Removes the processor on top of the stack and returns it. + * + * @psalm-return ProcessorInterface|callable(Record): Record $callback + * + * @throws \LogicException In case the processor stack is empty + * @return callable|ProcessorInterface + */ + public function popProcessor(): callable; +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/ProcessableHandlerTrait.php b/vendor/monolog/monolog/src/Monolog/Handler/ProcessableHandlerTrait.php new file mode 100644 index 00000000..9ef6e301 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/ProcessableHandlerTrait.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\ResettableInterface; +use Monolog\Processor\ProcessorInterface; + +/** + * Helper trait for implementing ProcessableInterface + * + * @author Jordi Boggiano + * + * @phpstan-import-type Record from \Monolog\Logger + */ +trait ProcessableHandlerTrait +{ + /** + * @var callable[] + * @phpstan-var array + */ + protected $processors = []; + + /** + * {@inheritDoc} + */ + public function pushProcessor(callable $callback): HandlerInterface + { + array_unshift($this->processors, $callback); + + return $this; + } + + /** + * {@inheritDoc} + */ + public function popProcessor(): callable + { + if (!$this->processors) { + throw new \LogicException('You tried to pop from an empty processor stack.'); + } + + return array_shift($this->processors); + } + + /** + * Processes a record. + * + * @phpstan-param Record $record + * @phpstan-return Record + */ + protected function processRecord(array $record): array + { + foreach ($this->processors as $processor) { + $record = $processor($record); + } + + return $record; + } + + protected function resetProcessors(): void + { + foreach ($this->processors as $processor) { + if ($processor instanceof ResettableInterface) { + $processor->reset(); + } + } + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/PsrHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/PsrHandler.php index 1ae85845..36e19ccc 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/PsrHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/PsrHandler.php @@ -1,4 +1,4 @@ - */ -class PsrHandler extends AbstractHandler +class PsrHandler extends AbstractHandler implements FormattableHandlerInterface { /** * PSR-3 compliant logger @@ -28,12 +33,15 @@ class PsrHandler extends AbstractHandler */ protected $logger; + /** + * @var FormatterInterface|null + */ + protected $formatter; + /** * @param LoggerInterface $logger The underlying PSR-3 compliant logger to which messages will be proxied - * @param int $level The minimum logging level at which this handler will be triggered - * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not */ - public function __construct(LoggerInterface $logger, $level = Logger::DEBUG, $bubble = true) + public function __construct(LoggerInterface $logger, $level = Logger::DEBUG, bool $bubble = true) { parent::__construct($level, $bubble); @@ -43,14 +51,45 @@ public function __construct(LoggerInterface $logger, $level = Logger::DEBUG, $bu /** * {@inheritDoc} */ - public function handle(array $record) + public function handle(array $record): bool { if (!$this->isHandling($record)) { return false; } - $this->logger->log(strtolower($record['level_name']), $record['message'], $record['context']); + if ($this->formatter) { + $formatted = $this->formatter->format($record); + $this->logger->log(strtolower($record['level_name']), (string) $formatted, $record['context']); + } else { + $this->logger->log(strtolower($record['level_name']), $record['message'], $record['context']); + } return false === $this->bubble; } + + /** + * Sets the formatter. + * + * @param FormatterInterface $formatter + */ + public function setFormatter(FormatterInterface $formatter): HandlerInterface + { + $this->formatter = $formatter; + + return $this; + } + + /** + * Gets the formatter. + * + * @return FormatterInterface + */ + public function getFormatter(): FormatterInterface + { + if (!$this->formatter) { + throw new \LogicException('No formatter has been set and this handler does not have a default formatter'); + } + + return $this->formatter; + } } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/PushoverHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/PushoverHandler.php index bba72005..fed2303d 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/PushoverHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/PushoverHandler.php @@ -1,4 +1,4 @@ - * @see https://www.pushover.net/api + * + * @phpstan-import-type FormattedRecord from AbstractProcessingHandler + * @phpstan-import-type Level from \Monolog\Logger + * @phpstan-import-type LevelName from \Monolog\Logger */ class PushoverHandler extends SocketHandler { + /** @var string */ private $token; + /** @var array */ private $users; + /** @var string */ private $title; - private $user; + /** @var string|int|null */ + private $user = null; + /** @var int */ private $retry; + /** @var int */ private $expire; + /** @var int */ private $highPriorityLevel; + /** @var int */ private $emergencyLevel; + /** @var bool */ private $useFormattedMessage = false; /** * All parameters that can be sent to Pushover * @see https://pushover.net/api - * @var array + * @var array */ - private $parameterNames = array( + private $parameterNames = [ 'token' => true, 'user' => true, 'message' => true, @@ -51,72 +66,103 @@ class PushoverHandler extends SocketHandler 'retry' => true, 'expire' => true, 'callback' => true, - ); + ]; /** * Sounds the api supports by default * @see https://pushover.net/api#sounds - * @var array + * @var string[] */ - private $sounds = array( + private $sounds = [ 'pushover', 'bike', 'bugle', 'cashregister', 'classical', 'cosmic', 'falling', 'gamelan', 'incoming', 'intermission', 'magic', 'mechanical', 'pianobar', 'siren', 'spacealarm', 'tugboat', 'alien', 'climb', 'persistent', 'echo', 'updown', 'none', - ); + ]; /** * @param string $token Pushover api token * @param string|array $users Pushover user id or array of ids the message will be sent to - * @param string $title Title sent to the Pushover API - * @param int $level The minimum logging level at which this handler will be triggered - * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not - * @param Boolean $useSSL Whether to connect via SSL. Required when pushing messages to users that are not + * @param string|null $title Title sent to the Pushover API + * @param bool $useSSL Whether to connect via SSL. Required when pushing messages to users that are not * the pushover.net app owner. OpenSSL is required for this option. - * @param int $highPriorityLevel The minimum logging level at which this handler will start + * @param string|int $highPriorityLevel The minimum logging level at which this handler will start * sending "high priority" requests to the Pushover API - * @param int $emergencyLevel The minimum logging level at which this handler will start + * @param string|int $emergencyLevel The minimum logging level at which this handler will start * sending "emergency" requests to the Pushover API - * @param int $retry The retry parameter specifies how often (in seconds) the Pushover servers will send the same notification to the user. - * @param int $expire The expire parameter specifies how many seconds your notification will continue to be retried for (every retry seconds). + * @param int $retry The retry parameter specifies how often (in seconds) the Pushover servers will + * send the same notification to the user. + * @param int $expire The expire parameter specifies how many seconds your notification will continue + * to be retried for (every retry seconds). + * + * @phpstan-param string|array $users + * @phpstan-param Level|LevelName|LogLevel::* $highPriorityLevel + * @phpstan-param Level|LevelName|LogLevel::* $emergencyLevel */ - public function __construct($token, $users, $title = null, $level = Logger::CRITICAL, $bubble = true, $useSSL = true, $highPriorityLevel = Logger::CRITICAL, $emergencyLevel = Logger::EMERGENCY, $retry = 30, $expire = 25200) - { + public function __construct( + string $token, + $users, + ?string $title = null, + $level = Logger::CRITICAL, + bool $bubble = true, + bool $useSSL = true, + $highPriorityLevel = Logger::CRITICAL, + $emergencyLevel = Logger::EMERGENCY, + int $retry = 30, + int $expire = 25200, + bool $persistent = false, + float $timeout = 0.0, + float $writingTimeout = 10.0, + ?float $connectionTimeout = null, + ?int $chunkSize = null + ) { $connectionString = $useSSL ? 'ssl://api.pushover.net:443' : 'api.pushover.net:80'; - parent::__construct($connectionString, $level, $bubble); + parent::__construct( + $connectionString, + $level, + $bubble, + $persistent, + $timeout, + $writingTimeout, + $connectionTimeout, + $chunkSize + ); $this->token = $token; $this->users = (array) $users; - $this->title = $title ?: gethostname(); + $this->title = $title ?: (string) gethostname(); $this->highPriorityLevel = Logger::toMonologLevel($highPriorityLevel); $this->emergencyLevel = Logger::toMonologLevel($emergencyLevel); $this->retry = $retry; $this->expire = $expire; } - protected function generateDataStream($record) + protected function generateDataStream(array $record): string { $content = $this->buildContent($record); return $this->buildHeader($content) . $content; } - private function buildContent($record) + /** + * @phpstan-param FormattedRecord $record + */ + private function buildContent(array $record): string { // Pushover has a limit of 512 characters on title and message combined. $maxMessageLength = 512 - strlen($this->title); $message = ($this->useFormattedMessage) ? $record['formatted'] : $record['message']; - $message = substr($message, 0, $maxMessageLength); + $message = Utils::substr($message, 0, $maxMessageLength); $timestamp = $record['datetime']->getTimestamp(); - $dataArray = array( + $dataArray = [ 'token' => $this->token, 'user' => $this->user, 'message' => $message, 'title' => $this->title, 'timestamp' => $timestamp, - ); + ]; if (isset($record['level']) && $record['level'] >= $this->emergencyLevel) { $dataArray['priority'] = 2; @@ -141,7 +187,7 @@ private function buildContent($record) return http_build_query($dataArray); } - private function buildHeader($content) + private function buildHeader(string $content): string { $header = "POST /1/messages.json HTTP/1.1\r\n"; $header .= "Host: api.pushover.net\r\n"; @@ -152,7 +198,7 @@ private function buildHeader($content) return $header; } - protected function write(array $record) + protected function write(array $record): void { foreach ($this->users as $user) { $this->user = $user; @@ -164,22 +210,37 @@ protected function write(array $record) $this->user = null; } - public function setHighPriorityLevel($value) + /** + * @param int|string $value + * + * @phpstan-param Level|LevelName|LogLevel::* $value + */ + public function setHighPriorityLevel($value): self { - $this->highPriorityLevel = $value; + $this->highPriorityLevel = Logger::toMonologLevel($value); + + return $this; } - public function setEmergencyLevel($value) + /** + * @param int|string $value + * + * @phpstan-param Level|LevelName|LogLevel::* $value + */ + public function setEmergencyLevel($value): self { - $this->emergencyLevel = $value; + $this->emergencyLevel = Logger::toMonologLevel($value); + + return $this; } /** * Use the formatted message? - * @param bool $value */ - public function useFormattedMessage($value) + public function useFormattedMessage(bool $value): self { - $this->useFormattedMessage = (boolean) $value; + $this->useFormattedMessage = $value; + + return $this; } } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/RavenHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/RavenHandler.php deleted file mode 100644 index 53a8b391..00000000 --- a/vendor/monolog/monolog/src/Monolog/Handler/RavenHandler.php +++ /dev/null @@ -1,232 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler; - -use Monolog\Formatter\LineFormatter; -use Monolog\Formatter\FormatterInterface; -use Monolog\Logger; -use Raven_Client; - -/** - * Handler to send messages to a Sentry (https://github.com/getsentry/sentry) server - * using raven-php (https://github.com/getsentry/raven-php) - * - * @author Marc Abramowitz - */ -class RavenHandler extends AbstractProcessingHandler -{ - /** - * Translates Monolog log levels to Raven log levels. - */ - private $logLevels = array( - Logger::DEBUG => Raven_Client::DEBUG, - Logger::INFO => Raven_Client::INFO, - Logger::NOTICE => Raven_Client::INFO, - Logger::WARNING => Raven_Client::WARNING, - Logger::ERROR => Raven_Client::ERROR, - Logger::CRITICAL => Raven_Client::FATAL, - Logger::ALERT => Raven_Client::FATAL, - Logger::EMERGENCY => Raven_Client::FATAL, - ); - - /** - * @var string should represent the current version of the calling - * software. Can be any string (git commit, version number) - */ - private $release; - - /** - * @var Raven_Client the client object that sends the message to the server - */ - protected $ravenClient; - - /** - * @var LineFormatter The formatter to use for the logs generated via handleBatch() - */ - protected $batchFormatter; - - /** - * @param Raven_Client $ravenClient - * @param int $level The minimum logging level at which this handler will be triggered - * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not - */ - public function __construct(Raven_Client $ravenClient, $level = Logger::DEBUG, $bubble = true) - { - parent::__construct($level, $bubble); - - $this->ravenClient = $ravenClient; - } - - /** - * {@inheritdoc} - */ - public function handleBatch(array $records) - { - $level = $this->level; - - // filter records based on their level - $records = array_filter($records, function ($record) use ($level) { - return $record['level'] >= $level; - }); - - if (!$records) { - return; - } - - // the record with the highest severity is the "main" one - $record = array_reduce($records, function ($highest, $record) { - if ($record['level'] > $highest['level']) { - return $record; - } - - return $highest; - }); - - // the other ones are added as a context item - $logs = array(); - foreach ($records as $r) { - $logs[] = $this->processRecord($r); - } - - if ($logs) { - $record['context']['logs'] = (string) $this->getBatchFormatter()->formatBatch($logs); - } - - $this->handle($record); - } - - /** - * Sets the formatter for the logs generated by handleBatch(). - * - * @param FormatterInterface $formatter - */ - public function setBatchFormatter(FormatterInterface $formatter) - { - $this->batchFormatter = $formatter; - } - - /** - * Gets the formatter for the logs generated by handleBatch(). - * - * @return FormatterInterface - */ - public function getBatchFormatter() - { - if (!$this->batchFormatter) { - $this->batchFormatter = $this->getDefaultBatchFormatter(); - } - - return $this->batchFormatter; - } - - /** - * {@inheritdoc} - */ - protected function write(array $record) - { - $previousUserContext = false; - $options = array(); - $options['level'] = $this->logLevels[$record['level']]; - $options['tags'] = array(); - if (!empty($record['extra']['tags'])) { - $options['tags'] = array_merge($options['tags'], $record['extra']['tags']); - unset($record['extra']['tags']); - } - if (!empty($record['context']['tags'])) { - $options['tags'] = array_merge($options['tags'], $record['context']['tags']); - unset($record['context']['tags']); - } - if (!empty($record['context']['fingerprint'])) { - $options['fingerprint'] = $record['context']['fingerprint']; - unset($record['context']['fingerprint']); - } - if (!empty($record['context']['logger'])) { - $options['logger'] = $record['context']['logger']; - unset($record['context']['logger']); - } else { - $options['logger'] = $record['channel']; - } - foreach ($this->getExtraParameters() as $key) { - foreach (array('extra', 'context') as $source) { - if (!empty($record[$source][$key])) { - $options[$key] = $record[$source][$key]; - unset($record[$source][$key]); - } - } - } - if (!empty($record['context'])) { - $options['extra']['context'] = $record['context']; - if (!empty($record['context']['user'])) { - $previousUserContext = $this->ravenClient->context->user; - $this->ravenClient->user_context($record['context']['user']); - unset($options['extra']['context']['user']); - } - } - if (!empty($record['extra'])) { - $options['extra']['extra'] = $record['extra']; - } - - if (!empty($this->release) && !isset($options['release'])) { - $options['release'] = $this->release; - } - - if (isset($record['context']['exception']) && ($record['context']['exception'] instanceof \Exception || (PHP_VERSION_ID >= 70000 && $record['context']['exception'] instanceof \Throwable))) { - $options['extra']['message'] = $record['formatted']; - $this->ravenClient->captureException($record['context']['exception'], $options); - } else { - $this->ravenClient->captureMessage($record['formatted'], array(), $options); - } - - if ($previousUserContext !== false) { - $this->ravenClient->user_context($previousUserContext); - } - } - - /** - * {@inheritDoc} - */ - protected function getDefaultFormatter() - { - return new LineFormatter('[%channel%] %message%'); - } - - /** - * Gets the default formatter for the logs generated by handleBatch(). - * - * @return FormatterInterface - */ - protected function getDefaultBatchFormatter() - { - return new LineFormatter(); - } - - /** - * Gets extra parameters supported by Raven that can be found in "extra" and "context" - * - * @return array - */ - protected function getExtraParameters() - { - return array('checksum', 'release', 'event_id'); - } - - /** - * @param string $value - * @return self - */ - public function setRelease($value) - { - $this->release = $value; - - return $this; - } -} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/RedisHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/RedisHandler.php index 590f9965..91d16eaf 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/RedisHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/RedisHandler.php @@ -1,4 +1,4 @@ -pushHandler($redis); * * @author Thomas Tourlourat + * + * @phpstan-import-type FormattedRecord from AbstractProcessingHandler */ class RedisHandler extends AbstractProcessingHandler { + /** @var \Predis\Client<\Predis\Client>|\Redis */ private $redisClient; + /** @var string */ private $redisKey; + /** @var int */ protected $capSize; /** - * @param \Predis\Client|\Redis $redis The redis instance + * @param \Predis\Client<\Predis\Client>|\Redis $redis The redis instance * @param string $key The key name to push records to - * @param int $level The minimum logging level at which this handler will be triggered - * @param bool $bubble Whether the messages that are handled can bubble up the stack or not - * @param int $capSize Number of entries to limit list size to + * @param int $capSize Number of entries to limit list size to, 0 = unlimited */ - public function __construct($redis, $key, $level = Logger::DEBUG, $bubble = true, $capSize = false) + public function __construct($redis, string $key, $level = Logger::DEBUG, bool $bubble = true, int $capSize = 0) { if (!(($redis instanceof \Predis\Client) || ($redis instanceof \Redis))) { throw new \InvalidArgumentException('Predis\Client or Redis instance required'); @@ -54,7 +58,7 @@ public function __construct($redis, $key, $level = Logger::DEBUG, $bubble = true /** * {@inheritDoc} */ - protected function write(array $record) + protected function write(array $record): void { if ($this->capSize) { $this->writeCapped($record); @@ -67,13 +71,13 @@ protected function write(array $record) * Write and cap the collection * Writes the record to the redis list and caps its * - * @param array $record associative record array - * @return void + * @phpstan-param FormattedRecord $record */ - protected function writeCapped(array $record) + protected function writeCapped(array $record): void { if ($this->redisClient instanceof \Redis) { - $this->redisClient->multi() + $mode = defined('\Redis::MULTI') ? \Redis::MULTI : 1; + $this->redisClient->multi($mode) ->rpush($this->redisKey, $record["formatted"]) ->ltrim($this->redisKey, -$this->capSize, -1) ->exec(); @@ -90,7 +94,7 @@ protected function writeCapped(array $record) /** * {@inheritDoc} */ - protected function getDefaultFormatter() + protected function getDefaultFormatter(): FormatterInterface { return new LineFormatter(); } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/RedisPubSubHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/RedisPubSubHandler.php new file mode 100644 index 00000000..7789309c --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/RedisPubSubHandler.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\LineFormatter; +use Monolog\Formatter\FormatterInterface; +use Monolog\Logger; + +/** + * Sends the message to a Redis Pub/Sub channel using PUBLISH + * + * usage example: + * + * $log = new Logger('application'); + * $redis = new RedisPubSubHandler(new Predis\Client("tcp://localhost:6379"), "logs", Logger::WARNING); + * $log->pushHandler($redis); + * + * @author Gaëtan Faugère + */ +class RedisPubSubHandler extends AbstractProcessingHandler +{ + /** @var \Predis\Client<\Predis\Client>|\Redis */ + private $redisClient; + /** @var string */ + private $channelKey; + + /** + * @param \Predis\Client<\Predis\Client>|\Redis $redis The redis instance + * @param string $key The channel key to publish records to + */ + public function __construct($redis, string $key, $level = Logger::DEBUG, bool $bubble = true) + { + if (!(($redis instanceof \Predis\Client) || ($redis instanceof \Redis))) { + throw new \InvalidArgumentException('Predis\Client or Redis instance required'); + } + + $this->redisClient = $redis; + $this->channelKey = $key; + + parent::__construct($level, $bubble); + } + + /** + * {@inheritDoc} + */ + protected function write(array $record): void + { + $this->redisClient->publish($this->channelKey, $record["formatted"]); + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new LineFormatter(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/RollbarHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/RollbarHandler.php index 6c8a3e3e..adcc9395 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/RollbarHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/RollbarHandler.php @@ -1,4 +1,4 @@ - 'debug', Logger::INFO => 'info', Logger::NOTICE => 'info', @@ -49,7 +48,7 @@ class RollbarHandler extends AbstractProcessingHandler Logger::CRITICAL => 'critical', Logger::ALERT => 'critical', Logger::EMERGENCY => 'critical', - ); + ]; /** * Records whether any log records have been added since the last flush of the rollbar notifier @@ -58,24 +57,23 @@ class RollbarHandler extends AbstractProcessingHandler */ private $hasRecords = false; + /** @var bool */ protected $initialized = false; /** - * @param RollbarNotifier $rollbarNotifier RollbarNotifier object constructed with valid token - * @param int $level The minimum logging level at which this handler will be triggered - * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * @param RollbarLogger $rollbarLogger RollbarLogger object constructed with valid token */ - public function __construct(RollbarNotifier $rollbarNotifier, $level = Logger::ERROR, $bubble = true) + public function __construct(RollbarLogger $rollbarLogger, $level = Logger::ERROR, bool $bubble = true) { - $this->rollbarNotifier = $rollbarNotifier; + $this->rollbarLogger = $rollbarLogger; parent::__construct($level, $bubble); } /** - * {@inheritdoc} + * {@inheritDoc} */ - protected function write(array $record) + protected function write(array $record): void { if (!$this->initialized) { // __destructor() doesn't get called on Fatal errors @@ -84,49 +82,50 @@ protected function write(array $record) } $context = $record['context']; - $payload = array(); - if (isset($context['payload'])) { - $payload = $context['payload']; - unset($context['payload']); - } - $context = array_merge($context, $record['extra'], array( + $context = array_merge($context, $record['extra'], [ 'level' => $this->levelMap[$record['level']], 'monolog_level' => $record['level_name'], 'channel' => $record['channel'], 'datetime' => $record['datetime']->format('U'), - )); + ]); - if (isset($context['exception']) && $context['exception'] instanceof Exception) { - $payload['level'] = $context['level']; + if (isset($context['exception']) && $context['exception'] instanceof Throwable) { $exception = $context['exception']; unset($context['exception']); - - $this->rollbarNotifier->report_exception($exception, $context, $payload); + $toLog = $exception; } else { - $this->rollbarNotifier->report_message( - $record['message'], - $context['level'], - $context, - $payload - ); + $toLog = $record['message']; } + // @phpstan-ignore-next-line + $this->rollbarLogger->log($context['level'], $toLog, $context); + $this->hasRecords = true; } - public function flush() + public function flush(): void { if ($this->hasRecords) { - $this->rollbarNotifier->flush(); + $this->rollbarLogger->flush(); $this->hasRecords = false; } } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function close() + public function close(): void { $this->flush(); } + + /** + * {@inheritDoc} + */ + public function reset() + { + $this->flush(); + + parent::reset(); + } } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php index 3b60b3d1..17745d22 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php @@ -1,4 +1,4 @@ -filename = $filename; - $this->maxFiles = (int) $maxFiles; - $this->nextRotation = new \DateTime('tomorrow'); + $this->filename = Utils::canonicalizePath($filename); + $this->maxFiles = $maxFiles; + $this->nextRotation = new \DateTimeImmutable('tomorrow'); $this->filenameFormat = '{filename}-{date}'; - $this->dateFormat = 'Y-m-d'; + $this->dateFormat = static::FILE_PER_DAY; parent::__construct($this->getTimedFilename(), $level, $bubble, $filePermission, $useLocking); } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function close() + public function close(): void { parent::close(); @@ -66,40 +72,52 @@ public function close() } } - public function setFilenameFormat($filenameFormat, $dateFormat) + /** + * {@inheritDoc} + */ + public function reset() { - if (!preg_match('{^Y(([/_.-]?m)([/_.-]?d)?)?$}', $dateFormat)) { - trigger_error( + parent::reset(); + + if (true === $this->mustRotate) { + $this->rotate(); + } + } + + public function setFilenameFormat(string $filenameFormat, string $dateFormat): self + { + if (!preg_match('{^[Yy](([/_.-]?m)([/_.-]?d)?)?$}', $dateFormat)) { + throw new InvalidArgumentException( 'Invalid date format - format must be one of '. 'RotatingFileHandler::FILE_PER_DAY ("Y-m-d"), RotatingFileHandler::FILE_PER_MONTH ("Y-m") '. 'or RotatingFileHandler::FILE_PER_YEAR ("Y"), or you can set one of the '. - 'date formats using slashes, underscores and/or dots instead of dashes.', - E_USER_DEPRECATED + 'date formats using slashes, underscores and/or dots instead of dashes.' ); } if (substr_count($filenameFormat, '{date}') === 0) { - trigger_error( - 'Invalid filename format - format should contain at least `{date}`, because otherwise rotating is impossible.', - E_USER_DEPRECATED + throw new InvalidArgumentException( + 'Invalid filename format - format must contain at least `{date}`, because otherwise rotating is impossible.' ); } $this->filenameFormat = $filenameFormat; $this->dateFormat = $dateFormat; $this->url = $this->getTimedFilename(); $this->close(); + + return $this; } /** - * {@inheritdoc} + * {@inheritDoc} */ - protected function write(array $record) + protected function write(array $record): void { // on the first record written, if the log is new, we should rotate (once per day) if (null === $this->mustRotate) { - $this->mustRotate = !file_exists($this->url); + $this->mustRotate = null === $this->url || !file_exists($this->url); } - if ($this->nextRotation < $record['datetime']) { + if ($this->nextRotation <= $record['datetime']) { $this->mustRotate = true; $this->close(); } @@ -110,11 +128,11 @@ protected function write(array $record) /** * Rotates the files. */ - protected function rotate() + protected function rotate(): void { // update filename $this->url = $this->getTimedFilename(); - $this->nextRotation = new \DateTime('tomorrow'); + $this->nextRotation = new \DateTimeImmutable('tomorrow'); // skip GC of old logs if files are unlimited if (0 === $this->maxFiles) { @@ -122,6 +140,11 @@ protected function rotate() } $logFiles = glob($this->getGlobPattern()); + if (false === $logFiles) { + // failed to glob + return; + } + if ($this->maxFiles >= count($logFiles)) { // no files to remove return; @@ -136,7 +159,9 @@ protected function rotate() if (is_writable($file)) { // suppress errors here as unlink() might fail if two processes // are cleaning up/rotating at the same time - set_error_handler(function ($errno, $errstr, $errfile, $errline) {}); + set_error_handler(function (int $errno, string $errstr, string $errfile, int $errline): bool { + return false; + }); unlink($file); restore_error_handler(); } @@ -145,31 +170,35 @@ protected function rotate() $this->mustRotate = false; } - protected function getTimedFilename() + protected function getTimedFilename(): string { $fileInfo = pathinfo($this->filename); $timedFilename = str_replace( - array('{filename}', '{date}'), - array($fileInfo['filename'], date($this->dateFormat)), + ['{filename}', '{date}'], + [$fileInfo['filename'], date($this->dateFormat)], $fileInfo['dirname'] . '/' . $this->filenameFormat ); - if (!empty($fileInfo['extension'])) { + if (isset($fileInfo['extension'])) { $timedFilename .= '.'.$fileInfo['extension']; } return $timedFilename; } - protected function getGlobPattern() + protected function getGlobPattern(): string { $fileInfo = pathinfo($this->filename); $glob = str_replace( - array('{filename}', '{date}'), - array($fileInfo['filename'], '*'), + ['{filename}', '{date}'], + [$fileInfo['filename'], str_replace( + ['Y', 'y', 'm', 'd'], + ['[0-9][0-9][0-9][0-9]', '[0-9][0-9]', '[0-9][0-9]', '[0-9][0-9]'], + $this->dateFormat) + ], $fileInfo['dirname'] . '/' . $this->filenameFormat ); - if (!empty($fileInfo['extension'])) { + if (isset($fileInfo['extension'])) { $glob .= '.'.$fileInfo['extension']; } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SamplingHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SamplingHandler.php index 9509ae37..c128a32d 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/SamplingHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/SamplingHandler.php @@ -1,4 +1,4 @@ - * @author Kunal Mehta + * + * @phpstan-import-type Record from \Monolog\Logger + * @phpstan-import-type Level from \Monolog\Logger */ -class SamplingHandler extends AbstractHandler +class SamplingHandler extends AbstractHandler implements ProcessableHandlerInterface, FormattableHandlerInterface { + use ProcessableHandlerTrait; + /** - * @var callable|HandlerInterface $handler + * @var HandlerInterface|callable + * @phpstan-var HandlerInterface|callable(Record|array{level: Level}|null, HandlerInterface): HandlerInterface */ protected $handler; @@ -38,10 +46,12 @@ class SamplingHandler extends AbstractHandler protected $factor; /** - * @param callable|HandlerInterface $handler Handler or factory callable($record, $fingersCrossedHandler). - * @param int $factor Sample factor + * @psalm-param HandlerInterface|callable(Record|array{level: Level}|null, HandlerInterface): HandlerInterface $handler + * + * @param callable|HandlerInterface $handler Handler or factory callable($record|null, $samplingHandler). + * @param int $factor Sample factor (e.g. 10 means every ~10th record is sampled) */ - public function __construct($handler, $factor) + public function __construct($handler, int $factor) { parent::__construct(); $this->handler = $handler; @@ -52,31 +62,71 @@ public function __construct($handler, $factor) } } - public function isHandling(array $record) + public function isHandling(array $record): bool { - return $this->handler->isHandling($record); + return $this->getHandler($record)->isHandling($record); } - public function handle(array $record) + public function handle(array $record): bool { if ($this->isHandling($record) && mt_rand(1, $this->factor) === 1) { - // The same logic as in FingersCrossedHandler - if (!$this->handler instanceof HandlerInterface) { - $this->handler = call_user_func($this->handler, $record, $this); - if (!$this->handler instanceof HandlerInterface) { - throw new \RuntimeException("The factory callable should return a HandlerInterface"); - } - } - if ($this->processors) { - foreach ($this->processors as $processor) { - $record = call_user_func($processor, $record); - } + /** @var Record $record */ + $record = $this->processRecord($record); } - $this->handler->handle($record); + $this->getHandler($record)->handle($record); } return false === $this->bubble; } + + /** + * Return the nested handler + * + * If the handler was provided as a factory callable, this will trigger the handler's instantiation. + * + * @phpstan-param Record|array{level: Level}|null $record + * + * @return HandlerInterface + */ + public function getHandler(array $record = null) + { + if (!$this->handler instanceof HandlerInterface) { + $this->handler = ($this->handler)($record, $this); + if (!$this->handler instanceof HandlerInterface) { + throw new \RuntimeException("The factory callable should return a HandlerInterface"); + } + } + + return $this->handler; + } + + /** + * {@inheritDoc} + */ + public function setFormatter(FormatterInterface $formatter): HandlerInterface + { + $handler = $this->getHandler(); + if ($handler instanceof FormattableHandlerInterface) { + $handler->setFormatter($formatter); + + return $this; + } + + throw new \UnexpectedValueException('The nested handler of type '.get_class($handler).' does not support formatters.'); + } + + /** + * {@inheritDoc} + */ + public function getFormatter(): FormatterInterface + { + $handler = $this->getHandler(); + if ($handler instanceof FormattableHandlerInterface) { + return $handler->getFormatter(); + } + + throw new \UnexpectedValueException('The nested handler of type '.get_class($handler).' does not support formatters.'); + } } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SendGridHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SendGridHandler.php new file mode 100644 index 00000000..1280ee70 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/SendGridHandler.php @@ -0,0 +1,102 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * SendGridrHandler uses the SendGrid API v2 function to send Log emails, more information in https://sendgrid.com/docs/API_Reference/Web_API/mail.html + * + * @author Ricardo Fontanelli + */ +class SendGridHandler extends MailHandler +{ + /** + * The SendGrid API User + * @var string + */ + protected $apiUser; + + /** + * The SendGrid API Key + * @var string + */ + protected $apiKey; + + /** + * The email addresses to which the message will be sent + * @var string + */ + protected $from; + + /** + * The email addresses to which the message will be sent + * @var string[] + */ + protected $to; + + /** + * The subject of the email + * @var string + */ + protected $subject; + + /** + * @param string $apiUser The SendGrid API User + * @param string $apiKey The SendGrid API Key + * @param string $from The sender of the email + * @param string|string[] $to The recipients of the email + * @param string $subject The subject of the mail + */ + public function __construct(string $apiUser, string $apiKey, string $from, $to, string $subject, $level = Logger::ERROR, bool $bubble = true) + { + if (!extension_loaded('curl')) { + throw new MissingExtensionException('The curl extension is needed to use the SendGridHandler'); + } + + parent::__construct($level, $bubble); + $this->apiUser = $apiUser; + $this->apiKey = $apiKey; + $this->from = $from; + $this->to = (array) $to; + $this->subject = $subject; + } + + /** + * {@inheritDoc} + */ + protected function send(string $content, array $records): void + { + $message = []; + $message['api_user'] = $this->apiUser; + $message['api_key'] = $this->apiKey; + $message['from'] = $this->from; + foreach ($this->to as $recipient) { + $message['to[]'] = $recipient; + } + $message['subject'] = $this->subject; + $message['date'] = date('r'); + + if ($this->isHtmlBody($content)) { + $message['html'] = $content; + } else { + $message['text'] = $content; + } + + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, 'https://api.sendgrid.com/api/mail.send.json'); + curl_setopt($ch, CURLOPT_POST, 1); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($message)); + Curl\Util::execute($ch, 2); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/Slack/SlackRecord.php b/vendor/monolog/monolog/src/Monolog/Handler/Slack/SlackRecord.php index 38bc838a..71a41094 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/Slack/SlackRecord.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/Slack/SlackRecord.php @@ -1,4 +1,4 @@ - * @see https://api.slack.com/incoming-webhooks * @see https://api.slack.com/docs/message-attachments + * + * @phpstan-import-type FormattedRecord from \Monolog\Handler\AbstractProcessingHandler + * @phpstan-import-type Record from \Monolog\Logger */ class SlackRecord { - const COLOR_DANGER = 'danger'; + public const COLOR_DANGER = 'danger'; - const COLOR_WARNING = 'warning'; + public const COLOR_WARNING = 'warning'; - const COLOR_GOOD = 'good'; + public const COLOR_GOOD = 'good'; - const COLOR_DEFAULT = '#e3e4e6'; + public const COLOR_DEFAULT = '#e3e4e6'; /** * Slack channel (encoded ID or name) @@ -47,7 +51,7 @@ class SlackRecord /** * User icon e.g. 'ghost', 'http://example.com/user.png' - * @var string + * @var string|null */ private $userIcon; @@ -71,12 +75,12 @@ class SlackRecord /** * Dot separated list of fields to exclude from slack message. E.g. ['context.field1', 'extra.field2'] - * @var array + * @var string[] */ private $excludeFields; /** - * @var FormatterInterface + * @var ?FormatterInterface */ private $formatter; @@ -85,26 +89,45 @@ class SlackRecord */ private $normalizerFormatter; - public function __construct($channel = null, $username = null, $useAttachment = true, $userIcon = null, $useShortAttachment = false, $includeContextAndExtra = false, array $excludeFields = array(), FormatterInterface $formatter = null) - { - $this->channel = $channel; - $this->username = $username; - $this->userIcon = trim($userIcon, ':'); - $this->useAttachment = $useAttachment; - $this->useShortAttachment = $useShortAttachment; - $this->includeContextAndExtra = $includeContextAndExtra; - $this->excludeFields = $excludeFields; - $this->formatter = $formatter; + /** + * @param string[] $excludeFields + */ + public function __construct( + ?string $channel = null, + ?string $username = null, + bool $useAttachment = true, + ?string $userIcon = null, + bool $useShortAttachment = false, + bool $includeContextAndExtra = false, + array $excludeFields = array(), + FormatterInterface $formatter = null + ) { + $this + ->setChannel($channel) + ->setUsername($username) + ->useAttachment($useAttachment) + ->setUserIcon($userIcon) + ->useShortAttachment($useShortAttachment) + ->includeContextAndExtra($includeContextAndExtra) + ->excludeFields($excludeFields) + ->setFormatter($formatter); if ($this->includeContextAndExtra) { $this->normalizerFormatter = new NormalizerFormatter(); } } - public function getSlackData(array $record) + /** + * Returns required data in format that Slack + * is expecting. + * + * @phpstan-param FormattedRecord $record + * @phpstan-return mixed[] + */ + public function getSlackData(array $record): array { $dataArray = array(); - $record = $this->excludeFields($record); + $record = $this->removeExcludedFields($record); if ($this->username) { $dataArray['username'] = $this->username; @@ -115,6 +138,7 @@ public function getSlackData(array $record) } if ($this->formatter && !$this->useAttachment) { + /** @phpstan-ignore-next-line */ $message = $this->formatter->format($record); } else { $message = $record['message']; @@ -122,12 +146,14 @@ public function getSlackData(array $record) if ($this->useAttachment) { $attachment = array( - 'fallback' => $message, - 'text' => $message, - 'color' => $this->getAttachmentColor($record['level']), - 'fields' => array(), - 'mrkdwn_in' => array('fields'), - 'ts' => $record['datetime']->getTimestamp() + 'fallback' => $message, + 'text' => $message, + 'color' => $this->getAttachmentColor($record['level']), + 'fields' => array(), + 'mrkdwn_in' => array('fields'), + 'ts' => $record['datetime']->getTimestamp(), + 'footer' => $this->username, + 'footer_icon' => $this->userIcon, ); if ($this->useShortAttachment) { @@ -137,7 +163,6 @@ public function getSlackData(array $record) $attachment['fields'][] = $this->generateAttachmentField('Level', $record['level_name']); } - if ($this->includeContextAndExtra) { foreach (array('extra', 'context') as $key) { if (empty($record[$key])) { @@ -146,7 +171,7 @@ public function getSlackData(array $record) if ($this->useShortAttachment) { $attachment['fields'][] = $this->generateAttachmentField( - ucfirst($key), + (string) $key, $record[$key] ); } else { @@ -176,89 +201,157 @@ public function getSlackData(array $record) } /** - * Returned a Slack message attachment color associated with + * Returns a Slack message attachment color associated with * provided level. - * - * @param int $level - * @return string */ - public function getAttachmentColor($level) + public function getAttachmentColor(int $level): string { switch (true) { case $level >= Logger::ERROR: - return self::COLOR_DANGER; + return static::COLOR_DANGER; case $level >= Logger::WARNING: - return self::COLOR_WARNING; + return static::COLOR_WARNING; case $level >= Logger::INFO: - return self::COLOR_GOOD; + return static::COLOR_GOOD; default: - return self::COLOR_DEFAULT; + return static::COLOR_DEFAULT; } } /** * Stringifies an array of key/value pairs to be used in attachment fields * - * @param array $fields - * - * @return string + * @param mixed[] $fields */ - public function stringify($fields) + public function stringify(array $fields): string { + /** @var Record $fields */ $normalized = $this->normalizerFormatter->format($fields); - $prettyPrintFlag = defined('JSON_PRETTY_PRINT') ? JSON_PRETTY_PRINT : 128; $hasSecondDimension = count(array_filter($normalized, 'is_array')); $hasNonNumericKeys = !count(array_filter(array_keys($normalized), 'is_numeric')); return $hasSecondDimension || $hasNonNumericKeys - ? json_encode($normalized, $prettyPrintFlag) - : json_encode($normalized); + ? Utils::jsonEncode($normalized, JSON_PRETTY_PRINT|Utils::DEFAULT_JSON_FLAGS) + : Utils::jsonEncode($normalized, Utils::DEFAULT_JSON_FLAGS); } /** - * Sets the formatter + * Channel used by the bot when posting + * + * @param ?string $channel * - * @param FormatterInterface $formatter + * @return static */ - public function setFormatter(FormatterInterface $formatter) + public function setChannel(?string $channel = null): self + { + $this->channel = $channel; + + return $this; + } + + /** + * Username used by the bot when posting + * + * @param ?string $username + * + * @return static + */ + public function setUsername(?string $username = null): self + { + $this->username = $username; + + return $this; + } + + public function useAttachment(bool $useAttachment = true): self + { + $this->useAttachment = $useAttachment; + + return $this; + } + + public function setUserIcon(?string $userIcon = null): self + { + $this->userIcon = $userIcon; + + if (\is_string($userIcon)) { + $this->userIcon = trim($userIcon, ':'); + } + + return $this; + } + + public function useShortAttachment(bool $useShortAttachment = false): self + { + $this->useShortAttachment = $useShortAttachment; + + return $this; + } + + public function includeContextAndExtra(bool $includeContextAndExtra = false): self + { + $this->includeContextAndExtra = $includeContextAndExtra; + + if ($this->includeContextAndExtra) { + $this->normalizerFormatter = new NormalizerFormatter(); + } + + return $this; + } + + /** + * @param string[] $excludeFields + */ + public function excludeFields(array $excludeFields = []): self + { + $this->excludeFields = $excludeFields; + + return $this; + } + + public function setFormatter(?FormatterInterface $formatter = null): self { $this->formatter = $formatter; + + return $this; } /** * Generates attachment field * - * @param string $title - * @param string|array $value\ + * @param string|mixed[] $value * - * @return array + * @return array{title: string, value: string, short: false} */ - private function generateAttachmentField($title, $value) + private function generateAttachmentField(string $title, $value): array { $value = is_array($value) - ? sprintf('```%s```', $this->stringify($value)) + ? sprintf('```%s```', substr($this->stringify($value), 0, 1990)) : $value; return array( - 'title' => $title, + 'title' => ucfirst($title), 'value' => $value, - 'short' => false + 'short' => false, ); } /** * Generates a collection of attachment fields from array * - * @param array $data + * @param mixed[] $data * - * @return array + * @return array */ - private function generateAttachmentFields(array $data) + private function generateAttachmentFields(array $data): array { + /** @var Record $data */ + $normalized = $this->normalizerFormatter->format($data); + $fields = array(); - foreach ($data as $key => $value) { - $fields[] = $this->generateAttachmentField($key, $value); + foreach ($normalized as $key => $value) { + $fields[] = $this->generateAttachmentField((string) $key, $value); } return $fields; @@ -267,11 +360,11 @@ private function generateAttachmentFields(array $data) /** * Get a copy of record with fields excluded according to $this->excludeFields * - * @param array $record + * @phpstan-param FormattedRecord $record * - * @return array + * @return mixed[] */ - private function excludeFields(array $record) + private function removeExcludedFields(array $record): array { foreach ($this->excludeFields as $field) { $keys = explode('.', $field); diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SlackHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SlackHandler.php index 3ac4d836..a648513e 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/SlackHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/SlackHandler.php @@ -1,4 +1,4 @@ - * @see https://api.slack.com/ + * + * @phpstan-import-type FormattedRecord from AbstractProcessingHandler */ class SlackHandler extends SocketHandler { @@ -41,20 +44,42 @@ class SlackHandler extends SocketHandler * @param string|null $username Name of a bot * @param bool $useAttachment Whether the message should be added to Slack as attachment (plain text otherwise) * @param string|null $iconEmoji The emoji name to use (or null) - * @param int $level The minimum logging level at which this handler will be triggered - * @param bool $bubble Whether the messages that are handled can bubble up the stack or not - * @param bool $useShortAttachment Whether the the context/extra messages added to Slack as attachments are in a short style + * @param bool $useShortAttachment Whether the context/extra messages added to Slack as attachments are in a short style * @param bool $includeContextAndExtra Whether the attachment should include context and extra data - * @param array $excludeFields Dot separated list of fields to exclude from slack message. E.g. ['context.field1', 'extra.field2'] + * @param string[] $excludeFields Dot separated list of fields to exclude from slack message. E.g. ['context.field1', 'extra.field2'] * @throws MissingExtensionException If no OpenSSL PHP extension configured */ - public function __construct($token, $channel, $username = null, $useAttachment = true, $iconEmoji = null, $level = Logger::CRITICAL, $bubble = true, $useShortAttachment = false, $includeContextAndExtra = false, array $excludeFields = array()) - { + public function __construct( + string $token, + string $channel, + ?string $username = null, + bool $useAttachment = true, + ?string $iconEmoji = null, + $level = Logger::CRITICAL, + bool $bubble = true, + bool $useShortAttachment = false, + bool $includeContextAndExtra = false, + array $excludeFields = array(), + bool $persistent = false, + float $timeout = 0.0, + float $writingTimeout = 10.0, + ?float $connectionTimeout = null, + ?int $chunkSize = null + ) { if (!extension_loaded('openssl')) { throw new MissingExtensionException('The OpenSSL PHP extension is required to use the SlackHandler'); } - parent::__construct('ssl://slack.com:443', $level, $bubble); + parent::__construct( + 'ssl://slack.com:443', + $level, + $bubble, + $persistent, + $timeout, + $writingTimeout, + $connectionTimeout, + $chunkSize + ); $this->slackRecord = new SlackRecord( $channel, @@ -63,25 +88,26 @@ public function __construct($token, $channel, $username = null, $useAttachment = $iconEmoji, $useShortAttachment, $includeContextAndExtra, - $excludeFields, - $this->formatter + $excludeFields ); $this->token = $token; } - public function getSlackRecord() + public function getSlackRecord(): SlackRecord { return $this->slackRecord; } + public function getToken(): string + { + return $this->token; + } + /** - * {@inheritdoc} - * - * @param array $record - * @return string + * {@inheritDoc} */ - protected function generateDataStream($record) + protected function generateDataStream(array $record): string { $content = $this->buildContent($record); @@ -91,10 +117,9 @@ protected function generateDataStream($record) /** * Builds the body of API call * - * @param array $record - * @return string + * @phpstan-param FormattedRecord $record */ - private function buildContent($record) + private function buildContent(array $record): string { $dataArray = $this->prepareContentData($record); @@ -102,18 +127,16 @@ private function buildContent($record) } /** - * Prepares content data - * - * @param array $record - * @return array + * @phpstan-param FormattedRecord $record + * @return string[] */ - protected function prepareContentData($record) + protected function prepareContentData(array $record): array { $dataArray = $this->slackRecord->getSlackData($record); $dataArray['token'] = $this->token; if (!empty($dataArray['attachments'])) { - $dataArray['attachments'] = json_encode($dataArray['attachments']); + $dataArray['attachments'] = Utils::jsonEncode($dataArray['attachments']); } return $dataArray; @@ -121,11 +144,8 @@ protected function prepareContentData($record) /** * Builds the header of the API Call - * - * @param string $content - * @return string */ - private function buildHeader($content) + private function buildHeader(string $content): string { $header = "POST /api/chat.postMessage HTTP/1.1\r\n"; $header .= "Host: slack.com\r\n"; @@ -137,11 +157,9 @@ private function buildHeader($content) } /** - * {@inheritdoc} - * - * @param array $record + * {@inheritDoc} */ - protected function write(array $record) + protected function write(array $record): void { parent::write($record); $this->finalizeWrite(); @@ -153,7 +171,7 @@ protected function write(array $record) * If we do not read some but close the socket too early, slack sometimes * drops the request entirely. */ - protected function finalizeWrite() + protected function finalizeWrite(): void { $res = $this->getResource(); if (is_resource($res)) { @@ -162,54 +180,77 @@ protected function finalizeWrite() $this->closeSocket(); } + public function setFormatter(FormatterInterface $formatter): HandlerInterface + { + parent::setFormatter($formatter); + $this->slackRecord->setFormatter($formatter); + + return $this; + } + + public function getFormatter(): FormatterInterface + { + $formatter = parent::getFormatter(); + $this->slackRecord->setFormatter($formatter); + + return $formatter; + } + /** - * Returned a Slack message attachment color associated with - * provided level. - * - * @param int $level - * @return string - * @deprecated Use underlying SlackRecord instead + * Channel used by the bot when posting */ - protected function getAttachmentColor($level) + public function setChannel(string $channel): self { - trigger_error( - 'SlackHandler::getAttachmentColor() is deprecated. Use underlying SlackRecord instead.', - E_USER_DEPRECATED - ); + $this->slackRecord->setChannel($channel); - return $this->slackRecord->getAttachmentColor($level); + return $this; } /** - * Stringifies an array of key/value pairs to be used in attachment fields - * - * @param array $fields - * @return string - * @deprecated Use underlying SlackRecord instead + * Username used by the bot when posting */ - protected function stringify($fields) + public function setUsername(string $username): self { - trigger_error( - 'SlackHandler::stringify() is deprecated. Use underlying SlackRecord instead.', - E_USER_DEPRECATED - ); + $this->slackRecord->setUsername($username); - return $this->slackRecord->stringify($fields); + return $this; } - public function setFormatter(FormatterInterface $formatter) + public function useAttachment(bool $useAttachment): self { - parent::setFormatter($formatter); - $this->slackRecord->setFormatter($formatter); + $this->slackRecord->useAttachment($useAttachment); return $this; } - public function getFormatter() + public function setIconEmoji(string $iconEmoji): self { - $formatter = parent::getFormatter(); - $this->slackRecord->setFormatter($formatter); + $this->slackRecord->setUserIcon($iconEmoji); - return $formatter; + return $this; + } + + public function useShortAttachment(bool $useShortAttachment): self + { + $this->slackRecord->useShortAttachment($useShortAttachment); + + return $this; + } + + public function includeContextAndExtra(bool $includeContextAndExtra): self + { + $this->slackRecord->includeContextAndExtra($includeContextAndExtra); + + return $this; + } + + /** + * @param string[] $excludeFields + */ + public function excludeFields(array $excludeFields): self + { + $this->slackRecord->excludeFields($excludeFields); + + return $this; } } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SlackWebhookHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SlackWebhookHandler.php index 9a1bbb44..8ae3c788 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/SlackWebhookHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/SlackWebhookHandler.php @@ -1,4 +1,4 @@ -webhookUrl = $webhookUrl; @@ -60,25 +73,27 @@ public function __construct($webhookUrl, $channel = null, $username = null, $use $iconEmoji, $useShortAttachment, $includeContextAndExtra, - $excludeFields, - $this->formatter + $excludeFields ); } - public function getSlackRecord() + public function getSlackRecord(): SlackRecord { return $this->slackRecord; } + public function getWebhookUrl(): string + { + return $this->webhookUrl; + } + /** - * {@inheritdoc} - * - * @param array $record + * {@inheritDoc} */ - protected function write(array $record) + protected function write(array $record): void { $postData = $this->slackRecord->getSlackData($record); - $postString = json_encode($postData); + $postString = Utils::jsonEncode($postData); $ch = curl_init(); $options = array( @@ -86,7 +101,7 @@ protected function write(array $record) CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => array('Content-type: application/json'), - CURLOPT_POSTFIELDS => $postString + CURLOPT_POSTFIELDS => $postString, ); if (defined('CURLOPT_SAFE_UPLOAD')) { $options[CURLOPT_SAFE_UPLOAD] = true; @@ -97,7 +112,7 @@ protected function write(array $record) Curl\Util::execute($ch); } - public function setFormatter(FormatterInterface $formatter) + public function setFormatter(FormatterInterface $formatter): HandlerInterface { parent::setFormatter($formatter); $this->slackRecord->setFormatter($formatter); @@ -105,7 +120,7 @@ public function setFormatter(FormatterInterface $formatter) return $this; } - public function getFormatter() + public function getFormatter(): FormatterInterface { $formatter = parent::getFormatter(); $this->slackRecord->setFormatter($formatter); diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SlackbotHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SlackbotHandler.php deleted file mode 100644 index baead525..00000000 --- a/vendor/monolog/monolog/src/Monolog/Handler/SlackbotHandler.php +++ /dev/null @@ -1,80 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler; - -use Monolog\Logger; - -/** - * Sends notifications through Slack's Slackbot - * - * @author Haralan Dobrev - * @see https://slack.com/apps/A0F81R8ET-slackbot - */ -class SlackbotHandler extends AbstractProcessingHandler -{ - /** - * The slug of the Slack team - * @var string - */ - private $slackTeam; - - /** - * Slackbot token - * @var string - */ - private $token; - - /** - * Slack channel name - * @var string - */ - private $channel; - - /** - * @param string $slackTeam Slack team slug - * @param string $token Slackbot token - * @param string $channel Slack channel (encoded ID or name) - * @param int $level The minimum logging level at which this handler will be triggered - * @param bool $bubble Whether the messages that are handled can bubble up the stack or not - */ - public function __construct($slackTeam, $token, $channel, $level = Logger::CRITICAL, $bubble = true) - { - parent::__construct($level, $bubble); - - $this->slackTeam = $slackTeam; - $this->token = $token; - $this->channel = $channel; - } - - /** - * {@inheritdoc} - * - * @param array $record - */ - protected function write(array $record) - { - $slackbotUrl = sprintf( - 'https://%s.slack.com/services/hooks/slackbot?token=%s&channel=%s', - $this->slackTeam, - $this->token, - $this->channel - ); - - $ch = curl_init(); - curl_setopt($ch, CURLOPT_URL, $slackbotUrl); - curl_setopt($ch, CURLOPT_POST, true); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_POSTFIELDS, $record['message']); - - Curl\Util::execute($ch); - } -} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SocketHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SocketHandler.php index 7a61bf4e..21701afa 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/SocketHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/SocketHandler.php @@ -1,4 +1,4 @@ - * @see http://php.net/manual/en/function.fsockopen.php + * + * @phpstan-import-type Record from \Monolog\Logger + * @phpstan-import-type FormattedRecord from AbstractProcessingHandler */ class SocketHandler extends AbstractProcessingHandler { + /** @var string */ private $connectionString; + /** @var float */ private $connectionTimeout; + /** @var resource|null */ private $resource; - private $timeout = 0; - private $writingTimeout = 10; + /** @var float */ + private $timeout; + /** @var float */ + private $writingTimeout; + /** @var ?int */ private $lastSentBytes = null; - private $persistent = false; - private $errno; - private $errstr; - private $lastWritingAt; + /** @var ?int */ + private $chunkSize; + /** @var bool */ + private $persistent; + /** @var ?int */ + private $errno = null; + /** @var ?string */ + private $errstr = null; + /** @var ?float */ + private $lastWritingAt = null; /** - * @param string $connectionString Socket connection string - * @param int $level The minimum logging level at which this handler will be triggered - * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + * @param string $connectionString Socket connection string + * @param bool $persistent Flag to enable/disable persistent connections + * @param float $timeout Socket timeout to wait until the request is being aborted + * @param float $writingTimeout Socket timeout to wait until the request should've been sent/written + * @param float|null $connectionTimeout Socket connect timeout to wait until the connection should've been + * established + * @param int|null $chunkSize Sets the chunk size. Only has effect during connection in the writing cycle + * + * @throws \InvalidArgumentException If an invalid timeout value (less than 0) is passed. */ - public function __construct($connectionString, $level = Logger::DEBUG, $bubble = true) - { + public function __construct( + string $connectionString, + $level = Logger::DEBUG, + bool $bubble = true, + bool $persistent = false, + float $timeout = 0.0, + float $writingTimeout = 10.0, + ?float $connectionTimeout = null, + ?int $chunkSize = null + ) { parent::__construct($level, $bubble); $this->connectionString = $connectionString; - $this->connectionTimeout = (float) ini_get('default_socket_timeout'); + + if ($connectionTimeout !== null) { + $this->validateTimeout($connectionTimeout); + } + + $this->connectionTimeout = $connectionTimeout ?? (float) ini_get('default_socket_timeout'); + $this->persistent = $persistent; + $this->validateTimeout($timeout); + $this->timeout = $timeout; + $this->validateTimeout($writingTimeout); + $this->writingTimeout = $writingTimeout; + $this->chunkSize = $chunkSize; } /** * Connect (if necessary) and write to the socket * - * @param array $record + * {@inheritDoc} * * @throws \UnexpectedValueException * @throws \RuntimeException */ - protected function write(array $record) + protected function write(array $record): void { $this->connectIfNotConnected(); $data = $this->generateDataStream($record); @@ -62,7 +102,7 @@ protected function write(array $record) /** * We will not close a PersistentSocket instance so it can be reused in other requests. */ - public function close() + public function close(): void { if (!$this->isPersistent()) { $this->closeSocket(); @@ -72,7 +112,7 @@ public function close() /** * Close socket, if open */ - public function closeSocket() + public function closeSocket(): void { if (is_resource($this->resource)) { fclose($this->resource); @@ -81,39 +121,39 @@ public function closeSocket() } /** - * Set socket connection to nbe persistent. It only has effect before the connection is initiated. - * - * @param bool $persistent + * Set socket connection to be persistent. It only has effect before the connection is initiated. */ - public function setPersistent($persistent) + public function setPersistent(bool $persistent): self { - $this->persistent = (boolean) $persistent; + $this->persistent = $persistent; + + return $this; } /** * Set connection timeout. Only has effect before we connect. * - * @param float $seconds - * * @see http://php.net/manual/en/function.fsockopen.php */ - public function setConnectionTimeout($seconds) + public function setConnectionTimeout(float $seconds): self { $this->validateTimeout($seconds); - $this->connectionTimeout = (float) $seconds; + $this->connectionTimeout = $seconds; + + return $this; } /** * Set write timeout. Only has effect before we connect. * - * @param float $seconds - * * @see http://php.net/manual/en/function.stream-set-timeout.php */ - public function setTimeout($seconds) + public function setTimeout(float $seconds): self { $this->validateTimeout($seconds); - $this->timeout = (float) $seconds; + $this->timeout = $seconds; + + return $this; } /** @@ -121,48 +161,52 @@ public function setTimeout($seconds) * * @param float $seconds 0 for no timeout */ - public function setWritingTimeout($seconds) + public function setWritingTimeout(float $seconds): self { $this->validateTimeout($seconds); - $this->writingTimeout = (float) $seconds; + $this->writingTimeout = $seconds; + + return $this; + } + + /** + * Set chunk size. Only has effect during connection in the writing cycle. + */ + public function setChunkSize(int $bytes): self + { + $this->chunkSize = $bytes; + + return $this; } /** * Get current connection string - * - * @return string */ - public function getConnectionString() + public function getConnectionString(): string { return $this->connectionString; } /** * Get persistent setting - * - * @return bool */ - public function isPersistent() + public function isPersistent(): bool { return $this->persistent; } /** * Get current connection timeout setting - * - * @return float */ - public function getConnectionTimeout() + public function getConnectionTimeout(): float { return $this->connectionTimeout; } /** * Get current in-transfer timeout - * - * @return float */ - public function getTimeout() + public function getTimeout(): float { return $this->timeout; } @@ -172,19 +216,25 @@ public function getTimeout() * * @return float */ - public function getWritingTimeout() + public function getWritingTimeout(): float { return $this->writingTimeout; } + /** + * Get current chunk size + */ + public function getChunkSize(): ?int + { + return $this->chunkSize; + } + /** * Check to see if the socket is currently available. * * UDP might appear to be connected but might fail when writing. See http://php.net/fsockopen for details. - * - * @return bool */ - public function isConnected() + public function isConnected(): bool { return is_resource($this->resource) && !feof($this->resource); // on TCP - other party can close connection. @@ -192,6 +242,8 @@ public function isConnected() /** * Wrapper to allow mocking + * + * @return resource|false */ protected function pfsockopen() { @@ -200,6 +252,8 @@ protected function pfsockopen() /** * Wrapper to allow mocking + * + * @return resource|false */ protected function fsockopen() { @@ -210,40 +264,77 @@ protected function fsockopen() * Wrapper to allow mocking * * @see http://php.net/manual/en/function.stream-set-timeout.php + * + * @return bool */ protected function streamSetTimeout() { $seconds = floor($this->timeout); $microseconds = round(($this->timeout - $seconds) * 1e6); - return stream_set_timeout($this->resource, $seconds, $microseconds); + if (!is_resource($this->resource)) { + throw new \LogicException('streamSetTimeout called but $this->resource is not a resource'); + } + + return stream_set_timeout($this->resource, (int) $seconds, (int) $microseconds); } /** * Wrapper to allow mocking + * + * @see http://php.net/manual/en/function.stream-set-chunk-size.php + * + * @return int|bool */ - protected function fwrite($data) + protected function streamSetChunkSize() { + if (!is_resource($this->resource)) { + throw new \LogicException('streamSetChunkSize called but $this->resource is not a resource'); + } + + if (null === $this->chunkSize) { + throw new \LogicException('streamSetChunkSize called but $this->chunkSize is not set'); + } + + return stream_set_chunk_size($this->resource, $this->chunkSize); + } + + /** + * Wrapper to allow mocking + * + * @return int|bool + */ + protected function fwrite(string $data) + { + if (!is_resource($this->resource)) { + throw new \LogicException('fwrite called but $this->resource is not a resource'); + } + return @fwrite($this->resource, $data); } /** * Wrapper to allow mocking + * + * @return mixed[]|bool */ protected function streamGetMetadata() { + if (!is_resource($this->resource)) { + throw new \LogicException('streamGetMetadata called but $this->resource is not a resource'); + } + return stream_get_meta_data($this->resource); } - private function validateTimeout($value) + private function validateTimeout(float $value): void { - $ok = filter_var($value, FILTER_VALIDATE_FLOAT); - if ($ok === false || $value < 0) { + if ($value < 0) { throw new \InvalidArgumentException("Timeout must be 0 or a positive float (got $value)"); } } - private function connectIfNotConnected() + private function connectIfNotConnected(): void { if ($this->isConnected()) { return; @@ -251,7 +342,10 @@ private function connectIfNotConnected() $this->connect(); } - protected function generateDataStream($record) + /** + * @phpstan-param FormattedRecord $record + */ + protected function generateDataStream(array $record): string { return (string) $record['formatted']; } @@ -264,33 +358,41 @@ protected function getResource() return $this->resource; } - private function connect() + private function connect(): void { $this->createSocketResource(); $this->setSocketTimeout(); + $this->setStreamChunkSize(); } - private function createSocketResource() + private function createSocketResource(): void { if ($this->isPersistent()) { $resource = $this->pfsockopen(); } else { $resource = $this->fsockopen(); } - if (!$resource) { + if (is_bool($resource)) { throw new \UnexpectedValueException("Failed connecting to $this->connectionString ($this->errno: $this->errstr)"); } $this->resource = $resource; } - private function setSocketTimeout() + private function setSocketTimeout(): void { if (!$this->streamSetTimeout()) { throw new \UnexpectedValueException("Failed setting timeout with stream_set_timeout()"); } } - private function writeToSocket($data) + private function setStreamChunkSize(): void + { + if ($this->chunkSize && !$this->streamSetChunkSize()) { + throw new \UnexpectedValueException("Failed setting chunk size with stream_set_chunk_size()"); + } + } + + private function writeToSocket(string $data): void { $length = strlen($data); $sent = 0; @@ -306,7 +408,7 @@ private function writeToSocket($data) } $sent += $chunk; $socketInfo = $this->streamGetMetadata(); - if ($socketInfo['timed_out']) { + if (is_array($socketInfo) && $socketInfo['timed_out']) { throw new \RuntimeException("Write timed-out"); } @@ -319,15 +421,15 @@ private function writeToSocket($data) } } - private function writingIsTimedOut($sent) + private function writingIsTimedOut(int $sent): bool { - $writingTimeout = (int) floor($this->writingTimeout); - if (0 === $writingTimeout) { + // convert to ms + if (0.0 == $this->writingTimeout) { return false; } if ($sent !== $this->lastSentBytes) { - $this->lastWritingAt = time(); + $this->lastWritingAt = microtime(true); $this->lastSentBytes = $sent; return false; @@ -335,7 +437,7 @@ private function writingIsTimedOut($sent) usleep(100); } - if ((time() - $this->lastWritingAt) >= $writingTimeout) { + if ((microtime(true) - $this->lastWritingAt) >= $this->writingTimeout) { $this->closeSocket(); return true; diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SqsHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SqsHandler.php new file mode 100644 index 00000000..dcf282b4 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/SqsHandler.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Aws\Sqs\SqsClient; +use Monolog\Logger; +use Monolog\Utils; + +/** + * Writes to any sqs queue. + * + * @author Martijn van Calker + */ +class SqsHandler extends AbstractProcessingHandler +{ + /** 256 KB in bytes - maximum message size in SQS */ + protected const MAX_MESSAGE_SIZE = 262144; + /** 100 KB in bytes - head message size for new error log */ + protected const HEAD_MESSAGE_SIZE = 102400; + + /** @var SqsClient */ + private $client; + /** @var string */ + private $queueUrl; + + public function __construct(SqsClient $sqsClient, string $queueUrl, $level = Logger::DEBUG, bool $bubble = true) + { + parent::__construct($level, $bubble); + + $this->client = $sqsClient; + $this->queueUrl = $queueUrl; + } + + /** + * {@inheritDoc} + */ + protected function write(array $record): void + { + if (!isset($record['formatted']) || 'string' !== gettype($record['formatted'])) { + throw new \InvalidArgumentException('SqsHandler accepts only formatted records as a string' . Utils::getRecordMessageForException($record)); + } + + $messageBody = $record['formatted']; + if (strlen($messageBody) >= static::MAX_MESSAGE_SIZE) { + $messageBody = Utils::substr($messageBody, 0, static::HEAD_MESSAGE_SIZE); + } + + $this->client->sendMessage([ + 'QueueUrl' => $this->queueUrl, + 'MessageBody' => $messageBody, + ]); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/StreamHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/StreamHandler.php index 09a15738..65183512 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/StreamHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/StreamHandler.php @@ -1,4 +1,4 @@ - + * + * @phpstan-import-type FormattedRecord from AbstractProcessingHandler */ class StreamHandler extends AbstractProcessingHandler { + /** @const int */ + protected const MAX_CHUNK_SIZE = 2147483647; + /** @const int 10MB */ + protected const DEFAULT_CHUNK_SIZE = 10 * 1024 * 1024; + /** @var int */ + protected $streamChunkSize; + /** @var resource|null */ protected $stream; - protected $url; - private $errorMessage; + /** @var ?string */ + protected $url = null; + /** @var ?string */ + private $errorMessage = null; + /** @var ?int */ protected $filePermission; + /** @var bool */ protected $useLocking; - private $dirCreated; + /** @var true|null */ + private $dirCreated = null; /** - * @param resource|string $stream - * @param int $level The minimum logging level at which this handler will be triggered - * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + * @param resource|string $stream If a missing path can't be created, an UnexpectedValueException will be thrown on first write * @param int|null $filePermission Optional file permissions (default (0644) are only for owner read/write) - * @param Boolean $useLocking Try to lock log file before doing any writes + * @param bool $useLocking Try to lock log file before doing any writes * - * @throws \Exception If a missing directory is not buildable * @throws \InvalidArgumentException If stream is not a resource or string */ - public function __construct($stream, $level = Logger::DEBUG, $bubble = true, $filePermission = null, $useLocking = false) + public function __construct($stream, $level = Logger::DEBUG, bool $bubble = true, ?int $filePermission = null, bool $useLocking = false) { parent::__construct($level, $bubble); + + if (($phpMemoryLimit = Utils::expandIniShorthandBytes(ini_get('memory_limit'))) !== false) { + if ($phpMemoryLimit > 0) { + // use max 10% of allowed memory for the chunk size, and at least 100KB + $this->streamChunkSize = min(static::MAX_CHUNK_SIZE, max((int) ($phpMemoryLimit / 10), 100 * 1024)); + } else { + // memory is unlimited, set to the default 10MB + $this->streamChunkSize = static::DEFAULT_CHUNK_SIZE; + } + } else { + // no memory limit information, set to the default 10MB + $this->streamChunkSize = static::DEFAULT_CHUNK_SIZE; + } + if (is_resource($stream)) { $this->stream = $stream; + + stream_set_chunk_size($this->stream, $this->streamChunkSize); } elseif (is_string($stream)) { - $this->url = $stream; + $this->url = Utils::canonicalizePath($stream); } else { throw new \InvalidArgumentException('A stream must either be a resource or a string.'); } @@ -55,14 +83,15 @@ public function __construct($stream, $level = Logger::DEBUG, $bubble = true, $fi } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function close() + public function close(): void { if ($this->url && is_resource($this->stream)) { fclose($this->stream); } $this->stream = null; + $this->dirCreated = null; } /** @@ -80,67 +109,83 @@ public function getStream() * * @return string|null */ - public function getUrl() + public function getUrl(): ?string { return $this->url; } /** - * {@inheritdoc} + * @return int */ - protected function write(array $record) + public function getStreamChunkSize(): int + { + return $this->streamChunkSize; + } + + /** + * {@inheritDoc} + */ + protected function write(array $record): void { if (!is_resource($this->stream)) { - if (null === $this->url || '' === $this->url) { - throw new \LogicException('Missing stream url, the stream can not be opened. This may be caused by a premature call to close().'); + $url = $this->url; + if (null === $url || '' === $url) { + throw new \LogicException('Missing stream url, the stream can not be opened. This may be caused by a premature call to close().' . Utils::getRecordMessageForException($record)); } - $this->createDir(); + $this->createDir($url); $this->errorMessage = null; - set_error_handler(array($this, 'customErrorHandler')); - $this->stream = fopen($this->url, 'a'); + set_error_handler([$this, 'customErrorHandler']); + $stream = fopen($url, 'a'); if ($this->filePermission !== null) { - @chmod($this->url, $this->filePermission); + @chmod($url, $this->filePermission); } restore_error_handler(); - if (!is_resource($this->stream)) { + if (!is_resource($stream)) { $this->stream = null; - throw new \UnexpectedValueException(sprintf('The stream or file "%s" could not be opened: '.$this->errorMessage, $this->url)); + + throw new \UnexpectedValueException(sprintf('The stream or file "%s" could not be opened in append mode: '.$this->errorMessage, $url) . Utils::getRecordMessageForException($record)); } + stream_set_chunk_size($stream, $this->streamChunkSize); + $this->stream = $stream; + } + + $stream = $this->stream; + if (!is_resource($stream)) { + throw new \LogicException('No stream was opened yet' . Utils::getRecordMessageForException($record)); } if ($this->useLocking) { // ignoring errors here, there's not much we can do about them - flock($this->stream, LOCK_EX); + flock($stream, LOCK_EX); } - $this->streamWrite($this->stream, $record); + $this->streamWrite($stream, $record); if ($this->useLocking) { - flock($this->stream, LOCK_UN); + flock($stream, LOCK_UN); } } /** * Write to stream * @param resource $stream - * @param array $record + * @param array $record + * + * @phpstan-param FormattedRecord $record */ - protected function streamWrite($stream, array $record) + protected function streamWrite($stream, array $record): void { fwrite($stream, (string) $record['formatted']); } - private function customErrorHandler($code, $msg) + private function customErrorHandler(int $code, string $msg): bool { $this->errorMessage = preg_replace('{^(fopen|mkdir)\(.*?\): }', '', $msg); + + return true; } - /** - * @param string $stream - * - * @return null|string - */ - private function getDirFromStream($stream) + private function getDirFromStream(string $stream): ?string { $pos = strpos($stream, '://'); if ($pos === false) { @@ -151,24 +196,24 @@ private function getDirFromStream($stream) return dirname(substr($stream, 7)); } - return; + return null; } - private function createDir() + private function createDir(string $url): void { // Do not try to create dir if it has already been tried. if ($this->dirCreated) { return; } - $dir = $this->getDirFromStream($this->url); + $dir = $this->getDirFromStream($url); if (null !== $dir && !is_dir($dir)) { $this->errorMessage = null; - set_error_handler(array($this, 'customErrorHandler')); + set_error_handler([$this, 'customErrorHandler']); $status = mkdir($dir, 0777, true); restore_error_handler(); - if (false === $status) { - throw new \UnexpectedValueException(sprintf('There is no existing directory at "%s" and its not buildable: '.$this->errorMessage, $dir)); + if (false === $status && !is_dir($dir) && strpos((string) $this->errorMessage, 'File exists') === false) { + throw new \UnexpectedValueException(sprintf('There is no existing directory at "%s" and it could not be created: '.$this->errorMessage, $dir)); } } $this->dirCreated = true; diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SwiftMailerHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SwiftMailerHandler.php index 72f44a53..fae92514 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/SwiftMailerHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/SwiftMailerHandler.php @@ -1,4 +1,4 @@ -mailer = $mailer; $this->messageTemplate = $message; } /** - * {@inheritdoc} + * {@inheritDoc} */ - protected function send($content, array $records) + protected function send(string $content, array $records): void { $this->mailer->send($this->buildMessage($content, $records)); } + /** + * Gets the formatter for the Swift_Message subject. + * + * @param string|null $format The format of the subject + */ + protected function getSubjectFormatter(?string $format): FormatterInterface + { + return new LineFormatter($format); + } + /** * Creates instance of Swift_Message to be sent * - * @param string $content formatted email body to be sent - * @param array $records Log records that formed the content - * @return \Swift_Message + * @param string $content formatted email body to be sent + * @param array $records Log records that formed the content + * @return Swift_Message + * + * @phpstan-param Record[] $records */ - protected function buildMessage($content, array $records) + protected function buildMessage(string $content, array $records): Swift_Message { $message = null; - if ($this->messageTemplate instanceof \Swift_Message) { + if ($this->messageTemplate instanceof Swift_Message) { $message = clone $this->messageTemplate; $message->generateId(); } elseif (is_callable($this->messageTemplate)) { - $message = call_user_func($this->messageTemplate, $content, $records); + $message = ($this->messageTemplate)($content, $records); } - if (!$message instanceof \Swift_Message) { - throw new \InvalidArgumentException('Could not resolve message as instance of Swift_Message or a callable returning it'); + if (!$message instanceof Swift_Message) { + $record = reset($records); + throw new \InvalidArgumentException('Could not resolve message as instance of Swift_Message or a callable returning it' . ($record ? Utils::getRecordMessageForException($record) : '')); } if ($records) { - $subjectFormatter = new LineFormatter($message->getSubject()); + $subjectFormatter = $this->getSubjectFormatter($message->getSubject()); $message->setSubject($subjectFormatter->format($this->getHighestRecord($records))); } - $message->setBody($content); + $mime = 'text/plain'; + if ($this->isHtmlBody($content)) { + $mime = 'text/html'; + } + + $message->setBody($content, $mime); + /** @phpstan-ignore-next-line */ if (version_compare(Swift::VERSION, '6.0.0', '>=')) { $message->setDate(new \DateTimeImmutable()); } else { + /** @phpstan-ignore-next-line */ $message->setDate(time()); } return $message; } - - /** - * BC getter, to be removed in 2.0 - */ - public function __get($name) - { - if ($name === 'message') { - trigger_error('SwiftMailerHandler->message is deprecated, use ->buildMessage() instead to retrieve the message', E_USER_DEPRECATED); - - return $this->buildMessage(null, array()); - } - - throw new \InvalidArgumentException('Invalid property '.$name); - } } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SymfonyMailerHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SymfonyMailerHandler.php new file mode 100644 index 00000000..130e6f1f --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/SymfonyMailerHandler.php @@ -0,0 +1,111 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Utils; +use Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\LineFormatter; +use Symfony\Component\Mailer\MailerInterface; +use Symfony\Component\Mailer\Transport\TransportInterface; +use Symfony\Component\Mime\Email; + +/** + * SymfonyMailerHandler uses Symfony's Mailer component to send the emails + * + * @author Jordi Boggiano + * + * @phpstan-import-type Record from \Monolog\Logger + */ +class SymfonyMailerHandler extends MailHandler +{ + /** @var MailerInterface|TransportInterface */ + protected $mailer; + /** @var Email|callable(string, Record[]): Email */ + private $emailTemplate; + + /** + * @psalm-param Email|callable(string, Record[]): Email $email + * + * @param MailerInterface|TransportInterface $mailer The mailer to use + * @param callable|Email $email An email template, the subject/body will be replaced + */ + public function __construct($mailer, $email, $level = Logger::ERROR, bool $bubble = true) + { + parent::__construct($level, $bubble); + + $this->mailer = $mailer; + $this->emailTemplate = $email; + } + + /** + * {@inheritDoc} + */ + protected function send(string $content, array $records): void + { + $this->mailer->send($this->buildMessage($content, $records)); + } + + /** + * Gets the formatter for the Swift_Message subject. + * + * @param string|null $format The format of the subject + */ + protected function getSubjectFormatter(?string $format): FormatterInterface + { + return new LineFormatter($format); + } + + /** + * Creates instance of Email to be sent + * + * @param string $content formatted email body to be sent + * @param array $records Log records that formed the content + * + * @phpstan-param Record[] $records + */ + protected function buildMessage(string $content, array $records): Email + { + $message = null; + if ($this->emailTemplate instanceof Email) { + $message = clone $this->emailTemplate; + } elseif (is_callable($this->emailTemplate)) { + $message = ($this->emailTemplate)($content, $records); + } + + if (!$message instanceof Email) { + $record = reset($records); + throw new \InvalidArgumentException('Could not resolve message as instance of Email or a callable returning it' . ($record ? Utils::getRecordMessageForException($record) : '')); + } + + if ($records) { + $subjectFormatter = $this->getSubjectFormatter($message->getSubject()); + $message->subject($subjectFormatter->format($this->getHighestRecord($records))); + } + + if ($this->isHtmlBody($content)) { + if (null !== ($charset = $message->getHtmlCharset())) { + $message->html($content, $charset); + } else { + $message->html($content); + } + } else { + if (null !== ($charset = $message->getTextCharset())) { + $message->text($content, $charset); + } else { + $message->text($content); + } + } + + return $message->date(new \DateTimeImmutable()); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SyslogHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SyslogHandler.php index 376bc3b2..1d543b7e 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/SyslogHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/SyslogHandler.php @@ -1,4 +1,4 @@ -facilities, or a LOG_* facility constant + * @param int $logopts Option flags for the openlog() call, defaults to LOG_PID */ - public function __construct($ident, $facility = LOG_USER, $level = Logger::DEBUG, $bubble = true, $logopts = LOG_PID) + public function __construct(string $ident, $facility = LOG_USER, $level = Logger::DEBUG, bool $bubble = true, int $logopts = LOG_PID) { parent::__construct($facility, $level, $bubble); @@ -47,20 +48,20 @@ public function __construct($ident, $facility = LOG_USER, $level = Logger::DEBUG } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function close() + public function close(): void { closelog(); } /** - * {@inheritdoc} + * {@inheritDoc} */ - protected function write(array $record) + protected function write(array $record): void { if (!openlog($this->ident, $this->logopts, $this->facility)) { - throw new \LogicException('Can\'t open syslog for ident "'.$this->ident.'" and facility "'.$this->facility.'"'); + throw new \LogicException('Can\'t open syslog for ident "'.$this->ident.'" and facility "'.$this->facility.'"' . Utils::getRecordMessageForException($record)); } syslog($this->logLevels[$record['level']], (string) $record['formatted']); } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SyslogUdp/UdpSocket.php b/vendor/monolog/monolog/src/Monolog/Handler/SyslogUdp/UdpSocket.php index 3bff085b..dbd8ef69 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/SyslogUdp/UdpSocket.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/SyslogUdp/UdpSocket.php @@ -1,4 +1,4 @@ -ip = $ip; $this->port = $port; - $this->socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP); } + /** + * @param string $line + * @param string $header + * @return void + */ public function write($line, $header = "") { $this->send($this->assembleMessage($line, $header)); } - public function close() + public function close(): void { - if (is_resource($this->socket)) { + if (is_resource($this->socket) || $this->socket instanceof Socket) { socket_close($this->socket); $this->socket = null; } } - protected function send($chunk) + /** + * @return resource|Socket + */ + protected function getSocket() { - if (!is_resource($this->socket)) { - throw new \LogicException('The UdpSocket to '.$this->ip.':'.$this->port.' has been closed and can not be written to anymore'); + if (null !== $this->socket) { + return $this->socket; + } + + $domain = AF_INET; + $protocol = SOL_UDP; + // Check if we are using unix sockets. + if ($this->port === 0) { + $domain = AF_UNIX; + $protocol = IPPROTO_IP; + } + + $this->socket = socket_create($domain, SOCK_DGRAM, $protocol) ?: null; + if (null === $this->socket) { + throw new \RuntimeException('The UdpSocket to '.$this->ip.':'.$this->port.' could not be opened via socket_create'); } - socket_sendto($this->socket, $chunk, strlen($chunk), $flags = 0, $this->ip, $this->port); + + return $this->socket; + } + + protected function send(string $chunk): void + { + socket_sendto($this->getSocket(), $chunk, strlen($chunk), $flags = 0, $this->ip, $this->port); } - protected function assembleMessage($line, $header) + protected function assembleMessage(string $line, string $header): string { - $chunkSize = self::DATAGRAM_MAX_LENGTH - strlen($header); + $chunkSize = static::DATAGRAM_MAX_LENGTH - strlen($header); - return $header . substr($line, 0, $chunkSize); + return $header . Utils::substr($line, 0, $chunkSize); } } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SyslogUdpHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SyslogUdpHandler.php index 4718711b..deaa19f8 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/SyslogUdpHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/SyslogUdpHandler.php @@ -1,4 +1,4 @@ - + * @author Dominik Kukacka */ class SyslogUdpHandler extends AbstractSyslogHandler { + const RFC3164 = 0; + const RFC5424 = 1; + const RFC5424e = 2; + + /** @var array */ + private $dateFormats = array( + self::RFC3164 => 'M d H:i:s', + self::RFC5424 => \DateTime::RFC3339, + self::RFC5424e => \DateTime::RFC3339_EXTENDED, + ); + + /** @var UdpSocket */ protected $socket; + /** @var string */ protected $ident; + /** @var self::RFC* */ + protected $rfc; /** - * @param string $host - * @param int $port - * @param mixed $facility - * @param int $level The minimum logging level at which this handler will be triggered - * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not - * @param string $ident Program name or tag for each log message. + * @param string $host Either IP/hostname or a path to a unix socket (port must be 0 then) + * @param int $port Port number, or 0 if $host is a unix socket + * @param string|int $facility Either one of the names of the keys in $this->facilities, or a LOG_* facility constant + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * @param string $ident Program name or tag for each log message. + * @param int $rfc RFC to format the message for. + * @throws MissingExtensionException + * + * @phpstan-param self::RFC* $rfc */ - public function __construct($host, $port = 514, $facility = LOG_USER, $level = Logger::DEBUG, $bubble = true, $ident = 'php') + public function __construct(string $host, int $port = 514, $facility = LOG_USER, $level = Logger::DEBUG, bool $bubble = true, string $ident = 'php', int $rfc = self::RFC5424) { + if (!extension_loaded('sockets')) { + throw new MissingExtensionException('The sockets extension is required to use the SyslogUdpHandler'); + } + parent::__construct($facility, $level, $bubble); $this->ident = $ident; + $this->rfc = $rfc; - $this->socket = new UdpSocket($host, $port ?: 514); + $this->socket = new UdpSocket($host, $port); } - protected function write(array $record) + protected function write(array $record): void { $lines = $this->splitMessageIntoLines($record['formatted']); - $header = $this->makeCommonSyslogHeader($this->logLevels[$record['level']]); + $header = $this->makeCommonSyslogHeader($this->logLevels[$record['level']], $record['datetime']); foreach ($lines as $line) { $this->socket->write($line, $header); } } - public function close() + public function close(): void { $this->socket->close(); } - private function splitMessageIntoLines($message) + /** + * @param string|string[] $message + * @return string[] + */ + private function splitMessageIntoLines($message): array { if (is_array($message)) { $message = implode("\n", $message); } - return preg_split('/$\R?^/m', $message, -1, PREG_SPLIT_NO_EMPTY); + $lines = preg_split('/$\R?^/m', (string) $message, -1, PREG_SPLIT_NO_EMPTY); + if (false === $lines) { + $pcreErrorCode = preg_last_error(); + throw new \RuntimeException('Could not preg_split: ' . $pcreErrorCode . ' / ' . Utils::pcreLastErrorMessage($pcreErrorCode)); + } + + return $lines; } /** - * Make common syslog header (see rfc5424) + * Make common syslog header (see rfc5424 or rfc3164) */ - protected function makeCommonSyslogHeader($severity) + protected function makeCommonSyslogHeader(int $severity, DateTimeInterface $datetime): string { $priority = $severity + $this->facility; @@ -81,23 +117,34 @@ protected function makeCommonSyslogHeader($severity) $hostname = '-'; } + if ($this->rfc === self::RFC3164) { + // see https://github.com/phpstan/phpstan/issues/5348 + // @phpstan-ignore-next-line + $dateNew = $datetime->setTimezone(new \DateTimeZone('UTC')); + $date = $dateNew->format($this->dateFormats[$this->rfc]); + + return "<$priority>" . + $date . " " . + $hostname . " " . + $this->ident . "[" . $pid . "]: "; + } + + $date = $datetime->format($this->dateFormats[$this->rfc]); + return "<$priority>1 " . - $this->getDateTime() . " " . + $date . " " . $hostname . " " . $this->ident . " " . $pid . " - - "; } - protected function getDateTime() - { - return date(\DateTime::RFC3339); - } - /** * Inject your own socket, mainly used for testing */ - public function setSocket($socket) + public function setSocket(UdpSocket $socket): self { $this->socket = $socket; + + return $this; } } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/TelegramBotHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/TelegramBotHandler.php new file mode 100644 index 00000000..8912eba5 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/TelegramBotHandler.php @@ -0,0 +1,274 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use RuntimeException; +use Monolog\Logger; +use Monolog\Utils; + +/** + * Handler send logs to Telegram using Telegram Bot API. + * + * How to use: + * 1) Create telegram bot with https://telegram.me/BotFather + * 2) Create a telegram channel where logs will be recorded. + * 3) Add created bot from step 1 to the created channel from step 2. + * + * Use telegram bot API key from step 1 and channel name with '@' prefix from step 2 to create instance of TelegramBotHandler + * + * @link https://core.telegram.org/bots/api + * + * @author Mazur Alexandr + * + * @phpstan-import-type Record from \Monolog\Logger + */ +class TelegramBotHandler extends AbstractProcessingHandler +{ + private const BOT_API = 'https://api.telegram.org/bot'; + + /** + * The available values of parseMode according to the Telegram api documentation + */ + private const AVAILABLE_PARSE_MODES = [ + 'HTML', + 'MarkdownV2', + 'Markdown', // legacy mode without underline and strikethrough, use MarkdownV2 instead + ]; + + /** + * The maximum number of characters allowed in a message according to the Telegram api documentation + */ + private const MAX_MESSAGE_LENGTH = 4096; + + /** + * Telegram bot access token provided by BotFather. + * Create telegram bot with https://telegram.me/BotFather and use access token from it. + * @var string + */ + private $apiKey; + + /** + * Telegram channel name. + * Since to start with '@' symbol as prefix. + * @var string + */ + private $channel; + + /** + * The kind of formatting that is used for the message. + * See available options at https://core.telegram.org/bots/api#formatting-options + * or in AVAILABLE_PARSE_MODES + * @var ?string + */ + private $parseMode; + + /** + * Disables link previews for links in the message. + * @var ?bool + */ + private $disableWebPagePreview; + + /** + * Sends the message silently. Users will receive a notification with no sound. + * @var ?bool + */ + private $disableNotification; + + /** + * True - split a message longer than MAX_MESSAGE_LENGTH into parts and send in multiple messages. + * False - truncates a message that is too long. + * @var bool + */ + private $splitLongMessages; + + /** + * Adds 1-second delay between sending a split message (according to Telegram API to avoid 429 Too Many Requests). + * @var bool + */ + private $delayBetweenMessages; + + /** + * @param string $apiKey Telegram bot access token provided by BotFather + * @param string $channel Telegram channel name + * @param bool $splitLongMessages Split a message longer than MAX_MESSAGE_LENGTH into parts and send in multiple messages + * @param bool $delayBetweenMessages Adds delay between sending a split message according to Telegram API + * @throws MissingExtensionException + */ + public function __construct( + string $apiKey, + string $channel, + $level = Logger::DEBUG, + bool $bubble = true, + string $parseMode = null, + bool $disableWebPagePreview = null, + bool $disableNotification = null, + bool $splitLongMessages = false, + bool $delayBetweenMessages = false + ) + { + if (!extension_loaded('curl')) { + throw new MissingExtensionException('The curl extension is needed to use the TelegramBotHandler'); + } + + parent::__construct($level, $bubble); + + $this->apiKey = $apiKey; + $this->channel = $channel; + $this->setParseMode($parseMode); + $this->disableWebPagePreview($disableWebPagePreview); + $this->disableNotification($disableNotification); + $this->splitLongMessages($splitLongMessages); + $this->delayBetweenMessages($delayBetweenMessages); + } + + public function setParseMode(string $parseMode = null): self + { + if ($parseMode !== null && !in_array($parseMode, self::AVAILABLE_PARSE_MODES)) { + throw new \InvalidArgumentException('Unknown parseMode, use one of these: ' . implode(', ', self::AVAILABLE_PARSE_MODES) . '.'); + } + + $this->parseMode = $parseMode; + + return $this; + } + + public function disableWebPagePreview(bool $disableWebPagePreview = null): self + { + $this->disableWebPagePreview = $disableWebPagePreview; + + return $this; + } + + public function disableNotification(bool $disableNotification = null): self + { + $this->disableNotification = $disableNotification; + + return $this; + } + + /** + * True - split a message longer than MAX_MESSAGE_LENGTH into parts and send in multiple messages. + * False - truncates a message that is too long. + * @param bool $splitLongMessages + * @return $this + */ + public function splitLongMessages(bool $splitLongMessages = false): self + { + $this->splitLongMessages = $splitLongMessages; + + return $this; + } + + /** + * Adds 1-second delay between sending a split message (according to Telegram API to avoid 429 Too Many Requests). + * @param bool $delayBetweenMessages + * @return $this + */ + public function delayBetweenMessages(bool $delayBetweenMessages = false): self + { + $this->delayBetweenMessages = $delayBetweenMessages; + + return $this; + } + + /** + * {@inheritDoc} + */ + public function handleBatch(array $records): void + { + /** @var Record[] $messages */ + $messages = []; + + foreach ($records as $record) { + if (!$this->isHandling($record)) { + continue; + } + + if ($this->processors) { + /** @var Record $record */ + $record = $this->processRecord($record); + } + + $messages[] = $record; + } + + if (!empty($messages)) { + $this->send((string)$this->getFormatter()->formatBatch($messages)); + } + } + + /** + * @inheritDoc + */ + protected function write(array $record): void + { + $this->send($record['formatted']); + } + + /** + * Send request to @link https://api.telegram.org/bot on SendMessage action. + * @param string $message + */ + protected function send(string $message): void + { + $messages = $this->handleMessageLength($message); + + foreach ($messages as $key => $msg) { + if ($this->delayBetweenMessages && $key > 0) { + sleep(1); + } + + $this->sendCurl($msg); + } + } + + protected function sendCurl(string $message): void + { + $ch = curl_init(); + $url = self::BOT_API . $this->apiKey . '/SendMessage'; + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([ + 'text' => $message, + 'chat_id' => $this->channel, + 'parse_mode' => $this->parseMode, + 'disable_web_page_preview' => $this->disableWebPagePreview, + 'disable_notification' => $this->disableNotification, + ])); + + $result = Curl\Util::execute($ch); + if (!is_string($result)) { + throw new RuntimeException('Telegram API error. Description: No response'); + } + $result = json_decode($result, true); + + if ($result['ok'] === false) { + throw new RuntimeException('Telegram API error. Description: ' . $result['description']); + } + } + + /** + * Handle a message that is too long: truncates or splits into several + * @param string $message + * @return string[] + */ + private function handleMessageLength(string $message): array + { + $truncatedMarker = ' (...truncated)'; + if (!$this->splitLongMessages && strlen($message) > self::MAX_MESSAGE_LENGTH) { + return [Utils::substr($message, 0, self::MAX_MESSAGE_LENGTH - strlen($truncatedMarker)) . $truncatedMarker]; + } + + return str_split($message, self::MAX_MESSAGE_LENGTH); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/TestHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/TestHandler.php index e39cfc66..0986da27 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/TestHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/TestHandler.php @@ -1,4 +1,4 @@ - */ + protected $recordsByLevel = []; + /** @var bool */ + private $skipReset = false; + /** + * @return array + * + * @phpstan-return Record[] + */ public function getRecords() { return $this->records; } + /** + * @return void + */ public function clear() { - $this->records = array(); - $this->recordsByLevel = array(); + $this->records = []; + $this->recordsByLevel = []; } - public function hasRecords($level) + /** + * @return void + */ + public function reset() { - return isset($this->recordsByLevel[$level]); + if (!$this->skipReset) { + $this->clear(); + } } - public function hasRecord($record, $level) + /** + * @return void + */ + public function setSkipReset(bool $skipReset) + { + $this->skipReset = $skipReset; + } + + /** + * @param string|int $level Logging level value or name + * + * @phpstan-param Level|LevelName|LogLevel::* $level + */ + public function hasRecords($level): bool { - if (is_array($record)) { - $record = $record['message']; + return isset($this->recordsByLevel[Logger::toMonologLevel($level)]); + } + + /** + * @param string|array $record Either a message string or an array containing message and optionally context keys that will be checked against all records + * @param string|int $level Logging level value or name + * + * @phpstan-param array{message: string, context?: mixed[]}|string $record + * @phpstan-param Level|LevelName|LogLevel::* $level + */ + public function hasRecord($record, $level): bool + { + if (is_string($record)) { + $record = array('message' => $record); } return $this->hasRecordThatPasses(function ($rec) use ($record) { - return $rec['message'] === $record; + if ($rec['message'] !== $record['message']) { + return false; + } + if (isset($record['context']) && $rec['context'] !== $record['context']) { + return false; + } + + return true; }, $level); } - public function hasRecordThatContains($message, $level) + /** + * @param string|int $level Logging level value or name + * + * @phpstan-param Level|LevelName|LogLevel::* $level + */ + public function hasRecordThatContains(string $message, $level): bool { return $this->hasRecordThatPasses(function ($rec) use ($message) { return strpos($rec['message'], $message) !== false; }, $level); } - public function hasRecordThatMatches($regex, $level) + /** + * @param string|int $level Logging level value or name + * + * @phpstan-param Level|LevelName|LogLevel::* $level + */ + public function hasRecordThatMatches(string $regex, $level): bool { - return $this->hasRecordThatPasses(function ($rec) use ($regex) { + return $this->hasRecordThatPasses(function (array $rec) use ($regex): bool { return preg_match($regex, $rec['message']) > 0; }, $level); } - public function hasRecordThatPasses($predicate, $level) + /** + * @param string|int $level Logging level value or name + * @return bool + * + * @psalm-param callable(Record, int): mixed $predicate + * @phpstan-param Level|LevelName|LogLevel::* $level + */ + public function hasRecordThatPasses(callable $predicate, $level) { - if (!is_callable($predicate)) { - throw new \InvalidArgumentException("Expected a callable for hasRecordThatSucceeds"); - } + $level = Logger::toMonologLevel($level); if (!isset($this->recordsByLevel[$level])) { return false; } foreach ($this->recordsByLevel[$level] as $i => $rec) { - if (call_user_func($predicate, $rec, $i)) { + if ($predicate($rec, $i)) { return true; } } @@ -129,23 +200,29 @@ public function hasRecordThatPasses($predicate, $level) } /** - * {@inheritdoc} + * {@inheritDoc} */ - protected function write(array $record) + protected function write(array $record): void { $this->recordsByLevel[$record['level']][] = $record; $this->records[] = $record; } + /** + * @param string $method + * @param mixed[] $args + * @return bool + */ public function __call($method, $args) { if (preg_match('/(.*)(Debug|Info|Notice|Warning|Error|Critical|Alert|Emergency)(.*)/', $method, $matches) > 0) { $genericMethod = $matches[1] . ('Records' !== $matches[3] ? 'Record' : '') . $matches[3]; $level = constant('Monolog\Logger::' . strtoupper($matches[2])); - if (method_exists($this, $genericMethod)) { + $callback = [$this, $genericMethod]; + if (is_callable($callback)) { $args[] = $level; - return call_user_func_array(array($this, $genericMethod), $args); + return call_user_func_array($callback, $args); } } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/WebRequestRecognizerTrait.php b/vendor/monolog/monolog/src/Monolog/Handler/WebRequestRecognizerTrait.php new file mode 100644 index 00000000..c8183528 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/WebRequestRecognizerTrait.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +trait WebRequestRecognizerTrait +{ + /** + * Checks if PHP's serving a web request + * @return bool + */ + protected function isWebRequest(): bool + { + return 'cli' !== \PHP_SAPI && 'phpdbg' !== \PHP_SAPI; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/WhatFailureGroupHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/WhatFailureGroupHandler.php index 2732ba3d..2dd13672 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/WhatFailureGroupHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/WhatFailureGroupHandler.php @@ -1,4 +1,4 @@ - + * + * @phpstan-import-type Record from \Monolog\Logger */ class WhatFailureGroupHandler extends GroupHandler { /** - * {@inheritdoc} + * {@inheritDoc} */ - public function handle(array $record) + public function handle(array $record): bool { if ($this->processors) { - foreach ($this->processors as $processor) { - $record = call_user_func($processor, $record); - } + /** @var Record $record */ + $record = $this->processRecord($record); } foreach ($this->handlers as $handler) { try { $handler->handle($record); - } catch (\Exception $e) { - // What failure? } catch (\Throwable $e) { // What failure? } @@ -44,15 +43,22 @@ public function handle(array $record) } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function handleBatch(array $records) + public function handleBatch(array $records): void { + if ($this->processors) { + $processed = array(); + foreach ($records as $record) { + $processed[] = $this->processRecord($record); + } + /** @var Record[] $records */ + $records = $processed; + } + foreach ($this->handlers as $handler) { try { $handler->handleBatch($records); - } catch (\Exception $e) { - // What failure? } catch (\Throwable $e) { // What failure? } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/ZendMonitorHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/ZendMonitorHandler.php index f22cf218..ddd46d8c 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/ZendMonitorHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/ZendMonitorHandler.php @@ -1,4 +1,5 @@ - + * @author Jason Davis + * + * @phpstan-import-type FormattedRecord from AbstractProcessingHandler */ class ZendMonitorHandler extends AbstractProcessingHandler { /** * Monolog level / ZendMonitor Custom Event priority map * - * @var array + * @var array */ - protected $levelMap = array( - Logger::DEBUG => 1, - Logger::INFO => 2, - Logger::NOTICE => 3, - Logger::WARNING => 4, - Logger::ERROR => 5, - Logger::CRITICAL => 6, - Logger::ALERT => 7, - Logger::EMERGENCY => 0, - ); + protected $levelMap = []; /** - * Construct - * - * @param int $level - * @param bool $bubble * @throws MissingExtensionException */ - public function __construct($level = Logger::DEBUG, $bubble = true) + public function __construct($level = Logger::DEBUG, bool $bubble = true) { if (!function_exists('zend_monitor_custom_event')) { - throw new MissingExtensionException('You must have Zend Server installed in order to use this handler'); + throw new MissingExtensionException( + 'You must have Zend Server installed with Zend Monitor enabled in order to use this handler' + ); } + //zend monitor constants are not defined if zend monitor is not enabled. + $this->levelMap = [ + Logger::DEBUG => \ZEND_MONITOR_EVENT_SEVERITY_INFO, + Logger::INFO => \ZEND_MONITOR_EVENT_SEVERITY_INFO, + Logger::NOTICE => \ZEND_MONITOR_EVENT_SEVERITY_INFO, + Logger::WARNING => \ZEND_MONITOR_EVENT_SEVERITY_WARNING, + Logger::ERROR => \ZEND_MONITOR_EVENT_SEVERITY_ERROR, + Logger::CRITICAL => \ZEND_MONITOR_EVENT_SEVERITY_ERROR, + Logger::ALERT => \ZEND_MONITOR_EVENT_SEVERITY_ERROR, + Logger::EMERGENCY => \ZEND_MONITOR_EVENT_SEVERITY_ERROR, + ]; parent::__construct($level, $bubble); } /** - * {@inheritdoc} + * {@inheritDoc} */ - protected function write(array $record) + protected function write(array $record): void { $this->writeZendMonitorCustomEvent( - $this->levelMap[$record['level']], + Logger::getLevelName($record['level']), $record['message'], - $record['formatted'] + $record['formatted'], + $this->levelMap[$record['level']] ); } /** - * Write a record to Zend Monitor + * Write to Zend Monitor Events + * @param string $type Text displayed in "Class Name (custom)" field + * @param string $message Text displayed in "Error String" + * @param array $formatted Displayed in Custom Variables tab + * @param int $severity Set the event severity level (-1,0,1) * - * @param int $level - * @param string $message - * @param array $formatted + * @phpstan-param FormattedRecord $formatted */ - protected function writeZendMonitorCustomEvent($level, $message, $formatted) + protected function writeZendMonitorCustomEvent(string $type, string $message, array $formatted, int $severity): void { - zend_monitor_custom_event($level, $message, $formatted); + zend_monitor_custom_event($type, $message, $formatted, $severity); } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function getDefaultFormatter() + public function getDefaultFormatter(): FormatterInterface { return new NormalizerFormatter(); } /** - * Get the level map - * - * @return array + * @return array */ - public function getLevelMap() + public function getLevelMap(): array { return $this->levelMap; } diff --git a/vendor/monolog/monolog/src/Monolog/LogRecord.php b/vendor/monolog/monolog/src/Monolog/LogRecord.php new file mode 100644 index 00000000..702807d7 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/LogRecord.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +use ArrayAccess; + +/** + * Monolog log record interface for forward compatibility with Monolog 3.0 + * + * This is just present in Monolog 2.4+ to allow interoperable code to be written against + * both versions by type-hinting arguments as `array|\Monolog\LogRecord $record` + * + * Do not rely on this interface for other purposes, and do not implement it. + * + * @author Jordi Boggiano + * @template-extends \ArrayAccess<'message'|'level'|'context'|'level_name'|'channel'|'datetime'|'extra'|'formatted', mixed> + * @phpstan-import-type Record from Logger + */ +interface LogRecord extends \ArrayAccess +{ + /** + * @phpstan-return Record + */ + public function toArray(): array; +} diff --git a/vendor/monolog/monolog/src/Monolog/Logger.php b/vendor/monolog/monolog/src/Monolog/Logger.php index 49d00af1..1ab75b9e 100644 --- a/vendor/monolog/monolog/src/Monolog/Logger.php +++ b/vendor/monolog/monolog/src/Monolog/Logger.php @@ -1,4 +1,4 @@ - + * + * @phpstan-type Level Logger::DEBUG|Logger::INFO|Logger::NOTICE|Logger::WARNING|Logger::ERROR|Logger::CRITICAL|Logger::ALERT|Logger::EMERGENCY + * @phpstan-type LevelName 'DEBUG'|'INFO'|'NOTICE'|'WARNING'|'ERROR'|'CRITICAL'|'ALERT'|'EMERGENCY' + * @phpstan-type Record array{message: string, context: mixed[], level: Level, level_name: LevelName, channel: string, datetime: \DateTimeImmutable, extra: mixed[]} */ -class Logger implements LoggerInterface +class Logger implements LoggerInterface, ResettableInterface { /** * Detailed debug information */ - const DEBUG = 100; + public const DEBUG = 100; /** * Interesting events * * Examples: User logs in, SQL logs. */ - const INFO = 200; + public const INFO = 200; /** * Uncommon events */ - const NOTICE = 250; + public const NOTICE = 250; /** * Exceptional occurrences that are not errors @@ -49,19 +56,19 @@ class Logger implements LoggerInterface * Examples: Use of deprecated APIs, poor use of an API, * undesirable things that are not necessarily wrong. */ - const WARNING = 300; + public const WARNING = 300; /** * Runtime errors */ - const ERROR = 400; + public const ERROR = 400; /** * Critical conditions * * Example: Application component unavailable, unexpected exception. */ - const CRITICAL = 500; + public const CRITICAL = 500; /** * Action must be taken immediately @@ -69,12 +76,12 @@ class Logger implements LoggerInterface * Example: Entire website down, database unavailable, etc. * This should trigger the SMS alerts and wake you up. */ - const ALERT = 550; + public const ALERT = 550; /** * Urgent alert. */ - const EMERGENCY = 600; + public const EMERGENCY = 600; /** * Monolog API version @@ -84,14 +91,16 @@ class Logger implements LoggerInterface * * @var int */ - const API = 1; + public const API = 2; /** - * Logging levels from syslog protocol defined in RFC 5424 + * This is a static variable and not a constant to serve as an extension point for custom levels + * + * @var array $levels Logging levels with the levels as key * - * @var array $levels Logging levels + * @phpstan-var array $levels Logging levels with the levels as key */ - protected static $levels = array( + protected static $levels = [ self::DEBUG => 'DEBUG', self::INFO => 'INFO', self::NOTICE => 'NOTICE', @@ -100,12 +109,23 @@ class Logger implements LoggerInterface self::CRITICAL => 'CRITICAL', self::ALERT => 'ALERT', self::EMERGENCY => 'EMERGENCY', - ); + ]; /** - * @var \DateTimeZone + * Mapping between levels numbers defined in RFC 5424 and Monolog ones + * + * @phpstan-var array $rfc_5424_levels */ - protected static $timezone; + private const RFC_5424_LEVELS = [ + 7 => self::DEBUG, + 6 => self::INFO, + 5 => self::NOTICE, + 4 => self::WARNING, + 3 => self::ERROR, + 2 => self::CRITICAL, + 1 => self::ALERT, + 0 => self::EMERGENCY, + ]; /** * @var string @@ -134,31 +154,52 @@ class Logger implements LoggerInterface protected $microsecondTimestamps = true; /** - * @param string $name The logging channel + * @var DateTimeZone + */ + protected $timezone; + + /** + * @var callable|null + */ + protected $exceptionHandler; + + /** + * @var int Keeps track of depth to prevent infinite logging loops + */ + private $logDepth = 0; + + /** + * @var bool Whether to detect infinite logging loops + * + * This can be disabled via {@see useLoggingLoopDetection} if you have async handlers that do not play well with this + */ + private $detectCycles = true; + + /** + * @psalm-param array $processors + * + * @param string $name The logging channel, a simple descriptive name that is attached to all log records * @param HandlerInterface[] $handlers Optional stack of handlers, the first one in the array is called first, etc. * @param callable[] $processors Optional array of processors + * @param DateTimeZone|null $timezone Optional timezone, if not provided date_default_timezone_get() will be used */ - public function __construct($name, array $handlers = array(), array $processors = array()) + public function __construct(string $name, array $handlers = [], array $processors = [], ?DateTimeZone $timezone = null) { $this->name = $name; - $this->handlers = $handlers; + $this->setHandlers($handlers); $this->processors = $processors; + $this->timezone = $timezone ?: new DateTimeZone(date_default_timezone_get() ?: 'UTC'); } - /** - * @return string - */ - public function getName() + public function getName(): string { return $this->name; } /** * Return a new cloned instance with the name changed - * - * @return static */ - public function withName($name) + public function withName(string $name): self { $new = clone $this; $new->name = $name; @@ -168,11 +209,8 @@ public function withName($name) /** * Pushes a handler on to the stack. - * - * @param HandlerInterface $handler - * @return $this */ - public function pushHandler(HandlerInterface $handler) + public function pushHandler(HandlerInterface $handler): self { array_unshift($this->handlers, $handler); @@ -182,9 +220,9 @@ public function pushHandler(HandlerInterface $handler) /** * Pops a handler from the stack * - * @return HandlerInterface + * @throws \LogicException If empty handler stack */ - public function popHandler() + public function popHandler(): HandlerInterface { if (!$this->handlers) { throw new \LogicException('You tried to pop from an empty handler stack.'); @@ -198,12 +236,11 @@ public function popHandler() * * If a map is passed, keys will be ignored. * - * @param HandlerInterface[] $handlers - * @return $this + * @param HandlerInterface[] $handlers */ - public function setHandlers(array $handlers) + public function setHandlers(array $handlers): self { - $this->handlers = array(); + $this->handlers = []; foreach (array_reverse($handlers) as $handler) { $this->pushHandler($handler); } @@ -214,22 +251,16 @@ public function setHandlers(array $handlers) /** * @return HandlerInterface[] */ - public function getHandlers() + public function getHandlers(): array { return $this->handlers; } /** * Adds a processor on to the stack. - * - * @param callable $callback - * @return $this */ - public function pushProcessor($callback) + public function pushProcessor(callable $callback): self { - if (!is_callable($callback)) { - throw new \InvalidArgumentException('Processors must be valid callables (callback or object with an __invoke method), '.var_export($callback, true).' given'); - } array_unshift($this->processors, $callback); return $this; @@ -238,9 +269,10 @@ public function pushProcessor($callback) /** * Removes the processor on top of the stack and returns it. * + * @throws \LogicException If empty processor stack * @return callable */ - public function popProcessor() + public function popProcessor(): callable { if (!$this->processors) { throw new \LogicException('You tried to pop from an empty processor stack.'); @@ -252,7 +284,7 @@ public function popProcessor() /** * @return callable[] */ - public function getProcessors() + public function getProcessors(): array { return $this->processors; } @@ -261,191 +293,156 @@ public function getProcessors() * Control the use of microsecond resolution timestamps in the 'datetime' * member of new records. * - * Generating microsecond resolution timestamps by calling - * microtime(true), formatting the result via sprintf() and then parsing - * the resulting string via \DateTime::createFromFormat() can incur - * a measurable runtime overhead vs simple usage of DateTime to capture - * a second resolution timestamp in systems which generate a large number - * of log events. + * As of PHP7.1 microseconds are always included by the engine, so + * there is no performance penalty and Monolog 2 enabled microseconds + * by default. This function lets you disable them though in case you want + * to suppress microseconds from the output. * * @param bool $micro True to use microtime() to create timestamps */ - public function useMicrosecondTimestamps($micro) + public function useMicrosecondTimestamps(bool $micro): self { - $this->microsecondTimestamps = (bool) $micro; + $this->microsecondTimestamps = $micro; + + return $this; + } + + public function useLoggingLoopDetection(bool $detectCycles): self + { + $this->detectCycles = $detectCycles; + + return $this; } /** * Adds a log record. * - * @param int $level The logging level - * @param string $message The log message - * @param array $context The log context - * @return Boolean Whether the record has been processed + * @param int $level The logging level (a Monolog or RFC 5424 level) + * @param string $message The log message + * @param mixed[] $context The log context + * @param DateTimeImmutable $datetime Optional log date to log into the past or future + * @return bool Whether the record has been processed + * + * @phpstan-param Level $level */ - public function addRecord($level, $message, array $context = array()) + public function addRecord(int $level, string $message, array $context = [], DateTimeImmutable $datetime = null): bool { - if (!$this->handlers) { - $this->pushHandler(new StreamHandler('php://stderr', static::DEBUG)); + if (isset(self::RFC_5424_LEVELS[$level])) { + $level = self::RFC_5424_LEVELS[$level]; } - $levelName = static::getLevelName($level); - - // check if any handler will handle this message so we can return early and save cycles - $handlerKey = null; - reset($this->handlers); - while ($handler = current($this->handlers)) { - if ($handler->isHandling(array('level' => $level))) { - $handlerKey = key($this->handlers); - break; - } - - next($this->handlers); + if ($this->detectCycles) { + $this->logDepth += 1; } - - if (null === $handlerKey) { + if ($this->logDepth === 3) { + $this->warning('A possible infinite logging loop was detected and aborted. It appears some of your handler code is triggering logging, see the previous log record for a hint as to what may be the cause.'); + return false; + } elseif ($this->logDepth >= 5) { // log depth 4 is let through so we can log the warning above return false; } - if (!static::$timezone) { - static::$timezone = new \DateTimeZone(date_default_timezone_get() ?: 'UTC'); - } - - // php7.1+ always has microseconds enabled, so we do not need this hack - if ($this->microsecondTimestamps && PHP_VERSION_ID < 70100) { - $ts = \DateTime::createFromFormat('U.u', sprintf('%.6F', microtime(true)), static::$timezone); - } else { - $ts = new \DateTime(null, static::$timezone); - } - $ts->setTimezone(static::$timezone); - - $record = array( - 'message' => (string) $message, - 'context' => $context, - 'level' => $level, - 'level_name' => $levelName, - 'channel' => $this->name, - 'datetime' => $ts, - 'extra' => array(), - ); - - foreach ($this->processors as $processor) { - $record = call_user_func($processor, $record); - } - - while ($handler = current($this->handlers)) { - if (true === $handler->handle($record)) { - break; + try { + $record = null; + + foreach ($this->handlers as $handler) { + if (null === $record) { + // skip creating the record as long as no handler is going to handle it + if (!$handler->isHandling(['level' => $level])) { + continue; + } + + $levelName = static::getLevelName($level); + + $record = [ + 'message' => $message, + 'context' => $context, + 'level' => $level, + 'level_name' => $levelName, + 'channel' => $this->name, + 'datetime' => $datetime ?? new DateTimeImmutable($this->microsecondTimestamps, $this->timezone), + 'extra' => [], + ]; + + try { + foreach ($this->processors as $processor) { + $record = $processor($record); + } + } catch (Throwable $e) { + $this->handleException($e, $record); + + return true; + } + } + + // once the record exists, send it to all handlers as long as the bubbling chain is not interrupted + try { + if (true === $handler->handle($record)) { + break; + } + } catch (Throwable $e) { + $this->handleException($e, $record); + + return true; + } + } + } finally { + if ($this->detectCycles) { + $this->logDepth--; } - - next($this->handlers); } - return true; + return null !== $record; } /** - * Adds a log record at the DEBUG level. + * Ends a log cycle and frees all resources used by handlers. * - * @param string $message The log message - * @param array $context The log context - * @return Boolean Whether the record has been processed - */ - public function addDebug($message, array $context = array()) - { - return $this->addRecord(static::DEBUG, $message, $context); - } - - /** - * Adds a log record at the INFO level. - * - * @param string $message The log message - * @param array $context The log context - * @return Boolean Whether the record has been processed - */ - public function addInfo($message, array $context = array()) - { - return $this->addRecord(static::INFO, $message, $context); - } - - /** - * Adds a log record at the NOTICE level. - * - * @param string $message The log message - * @param array $context The log context - * @return Boolean Whether the record has been processed - */ - public function addNotice($message, array $context = array()) - { - return $this->addRecord(static::NOTICE, $message, $context); - } - - /** - * Adds a log record at the WARNING level. - * - * @param string $message The log message - * @param array $context The log context - * @return Boolean Whether the record has been processed - */ - public function addWarning($message, array $context = array()) - { - return $this->addRecord(static::WARNING, $message, $context); - } - - /** - * Adds a log record at the ERROR level. + * Closing a Handler means flushing all buffers and freeing any open resources/handles. + * Handlers that have been closed should be able to accept log records again and re-open + * themselves on demand, but this may not always be possible depending on implementation. * - * @param string $message The log message - * @param array $context The log context - * @return Boolean Whether the record has been processed + * This is useful at the end of a request and will be called automatically on every handler + * when they get destructed. */ - public function addError($message, array $context = array()) + public function close(): void { - return $this->addRecord(static::ERROR, $message, $context); + foreach ($this->handlers as $handler) { + $handler->close(); + } } /** - * Adds a log record at the CRITICAL level. + * Ends a log cycle and resets all handlers and processors to their initial state. * - * @param string $message The log message - * @param array $context The log context - * @return Boolean Whether the record has been processed - */ - public function addCritical($message, array $context = array()) - { - return $this->addRecord(static::CRITICAL, $message, $context); - } - - /** - * Adds a log record at the ALERT level. + * Resetting a Handler or a Processor means flushing/cleaning all buffers, resetting internal + * state, and getting it back to a state in which it can receive log records again. * - * @param string $message The log message - * @param array $context The log context - * @return Boolean Whether the record has been processed + * This is useful in case you want to avoid logs leaking between two requests or jobs when you + * have a long running process like a worker or an application server serving multiple requests + * in one process. */ - public function addAlert($message, array $context = array()) + public function reset(): void { - return $this->addRecord(static::ALERT, $message, $context); - } + foreach ($this->handlers as $handler) { + if ($handler instanceof ResettableInterface) { + $handler->reset(); + } + } - /** - * Adds a log record at the EMERGENCY level. - * - * @param string $message The log message - * @param array $context The log context - * @return Boolean Whether the record has been processed - */ - public function addEmergency($message, array $context = array()) - { - return $this->addRecord(static::EMERGENCY, $message, $context); + foreach ($this->processors as $processor) { + if ($processor instanceof ResettableInterface) { + $processor->reset(); + } + } } /** * Gets all supported logging levels. * - * @return array Assoc array with human-readable level names => level codes. + * @return array Assoc array with human-readable level names => level codes. + * @phpstan-return array */ - public static function getLevels() + public static function getLevels(): array { return array_flip(static::$levels); } @@ -453,10 +450,12 @@ public static function getLevels() /** * Gets the name of the logging level. * - * @param int $level - * @return string + * @throws \Psr\Log\InvalidArgumentException If level is not defined + * + * @phpstan-param Level $level + * @phpstan-return LevelName */ - public static function getLevelName($level) + public static function getLevelName(int $level): string { if (!isset(static::$levels[$level])) { throw new InvalidArgumentException('Level "'.$level.'" is not defined, use one of: '.implode(', ', array_keys(static::$levels))); @@ -468,13 +467,32 @@ public static function getLevelName($level) /** * Converts PSR-3 levels to Monolog ones if necessary * - * @param string|int Level number (monolog) or name (PSR-3) - * @return int + * @param string|int $level Level number (monolog) or name (PSR-3) + * @throws \Psr\Log\InvalidArgumentException If level is not defined + * + * @phpstan-param Level|LevelName|LogLevel::* $level + * @phpstan-return Level */ - public static function toMonologLevel($level) + public static function toMonologLevel($level): int { - if (is_string($level) && defined(__CLASS__.'::'.strtoupper($level))) { - return constant(__CLASS__.'::'.strtoupper($level)); + if (is_string($level)) { + if (is_numeric($level)) { + /** @phpstan-ignore-next-line */ + return intval($level); + } + + // Contains chars of all log levels and avoids using strtoupper() which may have + // strange results depending on locale (for example, "i" will become "İ" in Turkish locale) + $upper = strtr($level, 'abcdefgilmnortuwy', 'ABCDEFGILMNORTUWY'); + if (defined(__CLASS__.'::'.$upper)) { + return constant(__CLASS__ . '::' . $upper); + } + + throw new InvalidArgumentException('Level "'.$level.'" is not defined, use one of: '.implode(', ', array_keys(static::$levels) + static::$levels)); + } + + if (!is_int($level)) { + throw new InvalidArgumentException('Level "'.var_export($level, true).'" is not defined, use one of: '.implode(', ', array_keys(static::$levels) + static::$levels)); } return $level; @@ -483,14 +501,13 @@ public static function toMonologLevel($level) /** * Checks whether the Logger has a handler that listens on the given level * - * @param int $level - * @return Boolean + * @phpstan-param Level $level */ - public function isHandling($level) + public function isHandling(int $level): bool { - $record = array( + $record = [ 'level' => $level, - ); + ]; foreach ($this->handlers as $handler) { if ($handler->isHandling($record)) { @@ -502,104 +519,98 @@ public function isHandling($level) } /** - * Adds a log record at an arbitrary level. + * Set a custom exception handler that will be called if adding a new record fails * - * This method allows for compatibility with common interfaces. - * - * @param mixed $level The log level - * @param string $message The log message - * @param array $context The log context - * @return Boolean Whether the record has been processed + * The callable will receive an exception object and the record that failed to be logged */ - public function log($level, $message, array $context = array()) + public function setExceptionHandler(?callable $callback): self { - $level = static::toMonologLevel($level); + $this->exceptionHandler = $callback; - return $this->addRecord($level, $message, $context); + return $this; } - /** - * Adds a log record at the DEBUG level. - * - * This method allows for compatibility with common interfaces. - * - * @param string $message The log message - * @param array $context The log context - * @return Boolean Whether the record has been processed - */ - public function debug($message, array $context = array()) + public function getExceptionHandler(): ?callable { - return $this->addRecord(static::DEBUG, $message, $context); + return $this->exceptionHandler; } /** - * Adds a log record at the INFO level. + * Adds a log record at an arbitrary level. * * This method allows for compatibility with common interfaces. * - * @param string $message The log message - * @param array $context The log context - * @return Boolean Whether the record has been processed + * @param mixed $level The log level (a Monolog, PSR-3 or RFC 5424 level) + * @param string|Stringable $message The log message + * @param mixed[] $context The log context + * + * @phpstan-param Level|LevelName|LogLevel::* $level */ - public function info($message, array $context = array()) + public function log($level, $message, array $context = []): void { - return $this->addRecord(static::INFO, $message, $context); + if (!is_int($level) && !is_string($level)) { + throw new \InvalidArgumentException('$level is expected to be a string or int'); + } + + if (isset(self::RFC_5424_LEVELS[$level])) { + $level = self::RFC_5424_LEVELS[$level]; + } + + $level = static::toMonologLevel($level); + + $this->addRecord($level, (string) $message, $context); } /** - * Adds a log record at the NOTICE level. + * Adds a log record at the DEBUG level. * * This method allows for compatibility with common interfaces. * - * @param string $message The log message - * @param array $context The log context - * @return Boolean Whether the record has been processed + * @param string|Stringable $message The log message + * @param mixed[] $context The log context */ - public function notice($message, array $context = array()) + public function debug($message, array $context = []): void { - return $this->addRecord(static::NOTICE, $message, $context); + $this->addRecord(static::DEBUG, (string) $message, $context); } /** - * Adds a log record at the WARNING level. + * Adds a log record at the INFO level. * * This method allows for compatibility with common interfaces. * - * @param string $message The log message - * @param array $context The log context - * @return Boolean Whether the record has been processed + * @param string|Stringable $message The log message + * @param mixed[] $context The log context */ - public function warn($message, array $context = array()) + public function info($message, array $context = []): void { - return $this->addRecord(static::WARNING, $message, $context); + $this->addRecord(static::INFO, (string) $message, $context); } /** - * Adds a log record at the WARNING level. + * Adds a log record at the NOTICE level. * * This method allows for compatibility with common interfaces. * - * @param string $message The log message - * @param array $context The log context - * @return Boolean Whether the record has been processed + * @param string|Stringable $message The log message + * @param mixed[] $context The log context */ - public function warning($message, array $context = array()) + public function notice($message, array $context = []): void { - return $this->addRecord(static::WARNING, $message, $context); + $this->addRecord(static::NOTICE, (string) $message, $context); } /** - * Adds a log record at the ERROR level. + * Adds a log record at the WARNING level. * * This method allows for compatibility with common interfaces. * - * @param string $message The log message - * @param array $context The log context - * @return Boolean Whether the record has been processed + * @param string|Stringable $message The log message + * @param mixed[] $context The log context */ - public function err($message, array $context = array()) + public function warning($message, array $context = []): void { - return $this->addRecord(static::ERROR, $message, $context); + $this->addRecord(static::WARNING, (string) $message, $context); } /** @@ -607,13 +618,12 @@ public function err($message, array $context = array()) * * This method allows for compatibility with common interfaces. * - * @param string $message The log message - * @param array $context The log context - * @return Boolean Whether the record has been processed + * @param string|Stringable $message The log message + * @param mixed[] $context The log context */ - public function error($message, array $context = array()) + public function error($message, array $context = []): void { - return $this->addRecord(static::ERROR, $message, $context); + $this->addRecord(static::ERROR, (string) $message, $context); } /** @@ -621,80 +631,71 @@ public function error($message, array $context = array()) * * This method allows for compatibility with common interfaces. * - * @param string $message The log message - * @param array $context The log context - * @return Boolean Whether the record has been processed + * @param string|Stringable $message The log message + * @param mixed[] $context The log context */ - public function crit($message, array $context = array()) + public function critical($message, array $context = []): void { - return $this->addRecord(static::CRITICAL, $message, $context); + $this->addRecord(static::CRITICAL, (string) $message, $context); } /** - * Adds a log record at the CRITICAL level. + * Adds a log record at the ALERT level. * * This method allows for compatibility with common interfaces. * - * @param string $message The log message - * @param array $context The log context - * @return Boolean Whether the record has been processed + * @param string|Stringable $message The log message + * @param mixed[] $context The log context */ - public function critical($message, array $context = array()) + public function alert($message, array $context = []): void { - return $this->addRecord(static::CRITICAL, $message, $context); + $this->addRecord(static::ALERT, (string) $message, $context); } /** - * Adds a log record at the ALERT level. + * Adds a log record at the EMERGENCY level. * * This method allows for compatibility with common interfaces. * - * @param string $message The log message - * @param array $context The log context - * @return Boolean Whether the record has been processed + * @param string|Stringable $message The log message + * @param mixed[] $context The log context */ - public function alert($message, array $context = array()) + public function emergency($message, array $context = []): void { - return $this->addRecord(static::ALERT, $message, $context); + $this->addRecord(static::EMERGENCY, (string) $message, $context); } /** - * Adds a log record at the EMERGENCY level. - * - * This method allows for compatibility with common interfaces. - * - * @param string $message The log message - * @param array $context The log context - * @return Boolean Whether the record has been processed + * Sets the timezone to be used for the timestamp of log records. */ - public function emerg($message, array $context = array()) + public function setTimezone(DateTimeZone $tz): self { - return $this->addRecord(static::EMERGENCY, $message, $context); + $this->timezone = $tz; + + return $this; } /** - * Adds a log record at the EMERGENCY level. - * - * This method allows for compatibility with common interfaces. - * - * @param string $message The log message - * @param array $context The log context - * @return Boolean Whether the record has been processed + * Returns the timezone to be used for the timestamp of log records. */ - public function emergency($message, array $context = array()) + public function getTimezone(): DateTimeZone { - return $this->addRecord(static::EMERGENCY, $message, $context); + return $this->timezone; } /** - * Set the timezone to be used for the timestamp of log records. - * - * This is stored globally for all Logger instances + * Delegates exception management to the custom exception handler, + * or throws the exception if no custom handler is set. * - * @param \DateTimeZone $tz Timezone object + * @param array $record + * @phpstan-param Record $record */ - public static function setTimezone(\DateTimeZone $tz) + protected function handleException(Throwable $e, array $record): void { - self::$timezone = $tz; + if (!$this->exceptionHandler) { + throw $e; + } + + ($this->exceptionHandler)($e, $record); } } diff --git a/vendor/monolog/monolog/src/Monolog/Processor/GitProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/GitProcessor.php index 1899400d..8166bdca 100644 --- a/vendor/monolog/monolog/src/Monolog/Processor/GitProcessor.php +++ b/vendor/monolog/monolog/src/Monolog/Processor/GitProcessor.php @@ -1,4 +1,4 @@ - + * + * @phpstan-import-type Level from \Monolog\Logger + * @phpstan-import-type LevelName from \Monolog\Logger */ -class GitProcessor +class GitProcessor implements ProcessorInterface { + /** @var int */ private $level; - private static $cache; + /** @var array{branch: string, commit: string}|array|null */ + private static $cache = null; + /** + * @param string|int $level The minimum logging level at which this Processor will be triggered + * + * @phpstan-param Level|LevelName|LogLevel::* $level + */ public function __construct($level = Logger::DEBUG) { $this->level = Logger::toMonologLevel($level); } /** - * @param array $record - * @return array + * {@inheritDoc} */ - public function __invoke(array $record) + public function __invoke(array $record): array { // return if the level is not high enough if ($record['level'] < $this->level) { @@ -45,20 +55,23 @@ public function __invoke(array $record) return $record; } - private static function getGitInfo() + /** + * @return array{branch: string, commit: string}|array + */ + private static function getGitInfo(): array { if (self::$cache) { return self::$cache; } $branches = `git branch -v --no-abbrev`; - if (preg_match('{^\* (.+?)\s+([a-f0-9]{40})(?:\s|$)}m', $branches, $matches)) { - return self::$cache = array( + if ($branches && preg_match('{^\* (.+?)\s+([a-f0-9]{40})(?:\s|$)}m', $branches, $matches)) { + return self::$cache = [ 'branch' => $matches[1], 'commit' => $matches[2], - ); + ]; } - return self::$cache = array(); + return self::$cache = []; } } diff --git a/vendor/monolog/monolog/src/Monolog/Processor/HostnameProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/HostnameProcessor.php new file mode 100644 index 00000000..91fda7d6 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/HostnameProcessor.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +/** + * Injects value of gethostname in all records + */ +class HostnameProcessor implements ProcessorInterface +{ + /** @var string */ + private static $host; + + public function __construct() + { + self::$host = (string) gethostname(); + } + + /** + * {@inheritDoc} + */ + public function __invoke(array $record): array + { + $record['extra']['hostname'] = self::$host; + + return $record; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/IntrospectionProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/IntrospectionProcessor.php index 2c07caed..a32e76b2 100644 --- a/vendor/monolog/monolog/src/Monolog/Processor/IntrospectionProcessor.php +++ b/vendor/monolog/monolog/src/Monolog/Processor/IntrospectionProcessor.php @@ -1,4 +1,4 @@ - + * + * @phpstan-import-type Level from \Monolog\Logger + * @phpstan-import-type LevelName from \Monolog\Logger */ -class IntrospectionProcessor +class IntrospectionProcessor implements ProcessorInterface { + /** @var int */ private $level; - + /** @var string[] */ private $skipClassesPartials; - + /** @var int */ private $skipStackFramesCount; - - private $skipFunctions = array( + /** @var string[] */ + private $skipFunctions = [ 'call_user_func', 'call_user_func_array', - ); + ]; - public function __construct($level = Logger::DEBUG, array $skipClassesPartials = array(), $skipStackFramesCount = 0) + /** + * @param string|int $level The minimum logging level at which this Processor will be triggered + * @param string[] $skipClassesPartials + * + * @phpstan-param Level|LevelName|LogLevel::* $level + */ + public function __construct($level = Logger::DEBUG, array $skipClassesPartials = [], int $skipStackFramesCount = 0) { $this->level = Logger::toMonologLevel($level); - $this->skipClassesPartials = array_merge(array('Monolog\\'), $skipClassesPartials); + $this->skipClassesPartials = array_merge(['Monolog\\'], $skipClassesPartials); $this->skipStackFramesCount = $skipStackFramesCount; } /** - * @param array $record - * @return array + * {@inheritDoc} */ - public function __invoke(array $record) + public function __invoke(array $record): array { // return if the level is not high enough if ($record['level'] < $this->level) { return $record; } - /* - * http://php.net/manual/en/function.debug-backtrace.php - * As of 5.3.6, DEBUG_BACKTRACE_IGNORE_ARGS option was added. - * Any version less than 5.3.6 must use the DEBUG_BACKTRACE_IGNORE_ARGS constant value '2'. - */ - $trace = debug_backtrace((PHP_VERSION_ID < 50306) ? 2 : DEBUG_BACKTRACE_IGNORE_ARGS); + $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); // skip first since it's always the current method array_shift($trace); @@ -74,11 +79,13 @@ public function __invoke(array $record) foreach ($this->skipClassesPartials as $part) { if (strpos($trace[$i]['class'], $part) !== false) { $i++; + continue 2; } } } elseif (in_array($trace[$i]['function'], $this->skipFunctions)) { $i++; + continue; } @@ -90,18 +97,22 @@ public function __invoke(array $record) // we should have the call source now $record['extra'] = array_merge( $record['extra'], - array( + [ 'file' => isset($trace[$i - 1]['file']) ? $trace[$i - 1]['file'] : null, 'line' => isset($trace[$i - 1]['line']) ? $trace[$i - 1]['line'] : null, 'class' => isset($trace[$i]['class']) ? $trace[$i]['class'] : null, + 'callType' => isset($trace[$i]['type']) ? $trace[$i]['type'] : null, 'function' => isset($trace[$i]['function']) ? $trace[$i]['function'] : null, - ) + ] ); return $record; } - private function isTraceClassOrSkippedFunction(array $trace, $index) + /** + * @param array[] $trace + */ + private function isTraceClassOrSkippedFunction(array $trace, int $index): bool { if (!isset($trace[$index])) { return false; diff --git a/vendor/monolog/monolog/src/Monolog/Processor/MemoryPeakUsageProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/MemoryPeakUsageProcessor.php index 0543e929..37c756fc 100644 --- a/vendor/monolog/monolog/src/Monolog/Processor/MemoryPeakUsageProcessor.php +++ b/vendor/monolog/monolog/src/Monolog/Processor/MemoryPeakUsageProcessor.php @@ -1,4 +1,4 @@ -realUsage); - $formatted = $this->formatBytes($bytes); + $usage = memory_get_peak_usage($this->realUsage); - $record['extra']['memory_peak_usage'] = $formatted; + if ($this->useFormatting) { + $usage = $this->formatBytes($usage); + } + + $record['extra']['memory_peak_usage'] = $usage; return $record; } diff --git a/vendor/monolog/monolog/src/Monolog/Processor/MemoryProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/MemoryProcessor.php index 85f9dc5e..227deb7c 100644 --- a/vendor/monolog/monolog/src/Monolog/Processor/MemoryProcessor.php +++ b/vendor/monolog/monolog/src/Monolog/Processor/MemoryProcessor.php @@ -1,4 +1,4 @@ -realUsage = (boolean) $realUsage; - $this->useFormatting = (boolean) $useFormatting; + $this->realUsage = $realUsage; + $this->useFormatting = $useFormatting; } /** * Formats bytes into a human readable string if $this->useFormatting is true, otherwise return $bytes as is * * @param int $bytes - * @return string|int Formatted string if $this->useFormatting is true, otherwise return $bytes as is + * @return string|int Formatted string if $this->useFormatting is true, otherwise return $bytes as int */ - protected function formatBytes($bytes) + protected function formatBytes(int $bytes) { - $bytes = (int) $bytes; - if (!$this->useFormatting) { return $bytes; } diff --git a/vendor/monolog/monolog/src/Monolog/Processor/MemoryUsageProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/MemoryUsageProcessor.php index 2783d656..e141921e 100644 --- a/vendor/monolog/monolog/src/Monolog/Processor/MemoryUsageProcessor.php +++ b/vendor/monolog/monolog/src/Monolog/Processor/MemoryUsageProcessor.php @@ -1,4 +1,4 @@ -realUsage); - $formatted = $this->formatBytes($bytes); + $usage = memory_get_usage($this->realUsage); - $record['extra']['memory_usage'] = $formatted; + if ($this->useFormatting) { + $usage = $this->formatBytes($usage); + } + + $record['extra']['memory_usage'] = $usage; return $record; } diff --git a/vendor/monolog/monolog/src/Monolog/Processor/MercurialProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/MercurialProcessor.php index 7c07a7e9..d4a628f5 100644 --- a/vendor/monolog/monolog/src/Monolog/Processor/MercurialProcessor.php +++ b/vendor/monolog/monolog/src/Monolog/Processor/MercurialProcessor.php @@ -1,9 +1,9 @@ - + * (c) Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -12,27 +12,37 @@ namespace Monolog\Processor; use Monolog\Logger; +use Psr\Log\LogLevel; /** * Injects Hg branch and Hg revision number in all records * * @author Jonathan A. Schweder + * + * @phpstan-import-type LevelName from \Monolog\Logger + * @phpstan-import-type Level from \Monolog\Logger */ -class MercurialProcessor +class MercurialProcessor implements ProcessorInterface { + /** @var Level */ private $level; - private static $cache; + /** @var array{branch: string, revision: string}|array|null */ + private static $cache = null; + /** + * @param int|string $level The minimum logging level at which this Processor will be triggered + * + * @phpstan-param Level|LevelName|LogLevel::* $level + */ public function __construct($level = Logger::DEBUG) { $this->level = Logger::toMonologLevel($level); } /** - * @param array $record - * @return array + * {@inheritDoc} */ - public function __invoke(array $record) + public function __invoke(array $record): array { // return if the level is not high enough if ($record['level'] < $this->level) { @@ -44,20 +54,24 @@ public function __invoke(array $record) return $record; } - private static function getMercurialInfo() + /** + * @return array{branch: string, revision: string}|array + */ + private static function getMercurialInfo(): array { if (self::$cache) { return self::$cache; } $result = explode(' ', trim(`hg id -nb`)); + if (count($result) >= 3) { - return self::$cache = array( + return self::$cache = [ 'branch' => $result[1], 'revision' => $result[2], - ); + ]; } - return self::$cache = array(); + return self::$cache = []; } } diff --git a/vendor/monolog/monolog/src/Monolog/Processor/ProcessIdProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/ProcessIdProcessor.php index 9d3f5590..3b939a95 100644 --- a/vendor/monolog/monolog/src/Monolog/Processor/ProcessIdProcessor.php +++ b/vendor/monolog/monolog/src/Monolog/Processor/ProcessIdProcessor.php @@ -1,4 +1,4 @@ - + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +/** + * An optional interface to allow labelling Monolog processors. + * + * @author Nicolas Grekas + * + * @phpstan-import-type Record from \Monolog\Logger + */ +interface ProcessorInterface +{ + /** + * @return array The processed record + * + * @phpstan-param Record $record + * @phpstan-return Record + */ + public function __invoke(array $record); +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/PsrLogMessageProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/PsrLogMessageProcessor.php index c2686ce5..2c2a00e7 100644 --- a/vendor/monolog/monolog/src/Monolog/Processor/PsrLogMessageProcessor.php +++ b/vendor/monolog/monolog/src/Monolog/Processor/PsrLogMessageProcessor.php @@ -1,4 +1,4 @@ - */ -class PsrLogMessageProcessor +class PsrLogMessageProcessor implements ProcessorInterface { + public const SIMPLE_DATE = "Y-m-d\TH:i:s.uP"; + + /** @var string|null */ + private $dateFormat; + + /** @var bool */ + private $removeUsedContextFields; + /** - * @param array $record - * @return array + * @param string|null $dateFormat The format of the timestamp: one supported by DateTime::format + * @param bool $removeUsedContextFields If set to true the fields interpolated into message gets unset */ - public function __invoke(array $record) + public function __construct(?string $dateFormat = null, bool $removeUsedContextFields = false) + { + $this->dateFormat = $dateFormat; + $this->removeUsedContextFields = $removeUsedContextFields; + } + + /** + * {@inheritDoc} + */ + public function __invoke(array $record): array { if (false === strpos($record['message'], '{')) { return $record; } - $replacements = array(); + $replacements = []; foreach ($record['context'] as $key => $val) { + $placeholder = '{' . $key . '}'; + if (strpos($record['message'], $placeholder) === false) { + continue; + } + if (is_null($val) || is_scalar($val) || (is_object($val) && method_exists($val, "__toString"))) { - $replacements['{'.$key.'}'] = $val; + $replacements[$placeholder] = $val; + } elseif ($val instanceof \DateTimeInterface) { + if (!$this->dateFormat && $val instanceof \Monolog\DateTimeImmutable) { + // handle monolog dates using __toString if no specific dateFormat was asked for + // so that it follows the useMicroseconds flag + $replacements[$placeholder] = (string) $val; + } else { + $replacements[$placeholder] = $val->format($this->dateFormat ?: static::SIMPLE_DATE); + } } elseif (is_object($val)) { - $replacements['{'.$key.'}'] = '[object '.get_class($val).']'; + $replacements[$placeholder] = '[object '.Utils::getClass($val).']'; + } elseif (is_array($val)) { + $replacements[$placeholder] = 'array'.Utils::jsonEncode($val, null, true); } else { - $replacements['{'.$key.'}'] = '['.gettype($val).']'; + $replacements[$placeholder] = '['.gettype($val).']'; + } + + if ($this->removeUsedContextFields) { + unset($record['context'][$key]); } } diff --git a/vendor/monolog/monolog/src/Monolog/Processor/TagProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/TagProcessor.php index 7e2df2ac..80f18747 100644 --- a/vendor/monolog/monolog/src/Monolog/Processor/TagProcessor.php +++ b/vendor/monolog/monolog/src/Monolog/Processor/TagProcessor.php @@ -1,4 +1,4 @@ -setTags($tags); } - public function addTags(array $tags = array()) + /** + * @param string[] $tags + */ + public function addTags(array $tags = []): self { $this->tags = array_merge($this->tags, $tags); + + return $this; } - public function setTags(array $tags = array()) + /** + * @param string[] $tags + */ + public function setTags(array $tags = []): self { $this->tags = $tags; + + return $this; } - public function __invoke(array $record) + /** + * {@inheritDoc} + */ + public function __invoke(array $record): array { $record['extra']['tags'] = $this->tags; diff --git a/vendor/monolog/monolog/src/Monolog/Processor/UidProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/UidProcessor.php index 812707cd..a27b74db 100644 --- a/vendor/monolog/monolog/src/Monolog/Processor/UidProcessor.php +++ b/vendor/monolog/monolog/src/Monolog/Processor/UidProcessor.php @@ -1,4 +1,4 @@ - */ -class UidProcessor +class UidProcessor implements ProcessorInterface, ResettableInterface { + /** @var string */ private $uid; - public function __construct($length = 7) + public function __construct(int $length = 7) { - if (!is_int($length) || $length > 32 || $length < 1) { + if ($length > 32 || $length < 1) { throw new \InvalidArgumentException('The uid length must be an integer between 1 and 32'); } - $this->uid = substr(hash('md5', uniqid('', true)), 0, $length); + $this->uid = $this->generateUid($length); } - public function __invoke(array $record) + /** + * {@inheritDoc} + */ + public function __invoke(array $record): array { $record['extra']['uid'] = $this->uid; return $record; } - /** - * @return string - */ - public function getUid() + public function getUid(): string { return $this->uid; } + + public function reset() + { + $this->uid = $this->generateUid(strlen($this->uid)); + } + + private function generateUid(int $length): string + { + return substr(bin2hex(random_bytes((int) ceil($length / 2))), 0, $length); + } } diff --git a/vendor/monolog/monolog/src/Monolog/Processor/WebProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/WebProcessor.php index ea1d8978..51850e17 100644 --- a/vendor/monolog/monolog/src/Monolog/Processor/WebProcessor.php +++ b/vendor/monolog/monolog/src/Monolog/Processor/WebProcessor.php @@ -1,4 +1,4 @@ - */ -class WebProcessor +class WebProcessor implements ProcessorInterface { /** - * @var array|\ArrayAccess + * @var array|\ArrayAccess */ protected $serverData; @@ -28,19 +28,20 @@ class WebProcessor * * Array is structured as [key in record.extra => key in $serverData] * - * @var array + * @var array */ - protected $extraFields = array( + protected $extraFields = [ 'url' => 'REQUEST_URI', 'ip' => 'REMOTE_ADDR', 'http_method' => 'REQUEST_METHOD', 'server' => 'SERVER_NAME', 'referrer' => 'HTTP_REFERER', - ); + 'user_agent' => 'HTTP_USER_AGENT', + ]; /** - * @param array|\ArrayAccess $serverData Array or object w/ ArrayAccess that provides access to the $_SERVER data - * @param array|null $extraFields Field names and the related key inside $serverData to be added. If not provided it defaults to: url, ip, http_method, server, referrer + * @param array|\ArrayAccess|null $serverData Array or object w/ ArrayAccess that provides access to the $_SERVER data + * @param array|array|null $extraFields Field names and the related key inside $serverData to be added (or just a list of field names to use the default configured $serverData mapping). If not provided it defaults to: [url, ip, http_method, server, referrer] + unique_id if present in server data */ public function __construct($serverData = null, array $extraFields = null) { @@ -52,24 +53,30 @@ public function __construct($serverData = null, array $extraFields = null) throw new \UnexpectedValueException('$serverData must be an array or object implementing ArrayAccess.'); } - if (null !== $extraFields) { - if (isset($extraFields[0])) { - foreach (array_keys($this->extraFields) as $fieldName) { - if (!in_array($fieldName, $extraFields)) { - unset($this->extraFields[$fieldName]); - } + $defaultEnabled = ['url', 'ip', 'http_method', 'server', 'referrer']; + if (isset($this->serverData['UNIQUE_ID'])) { + $this->extraFields['unique_id'] = 'UNIQUE_ID'; + $defaultEnabled[] = 'unique_id'; + } + + if (null === $extraFields) { + $extraFields = $defaultEnabled; + } + if (isset($extraFields[0])) { + foreach (array_keys($this->extraFields) as $fieldName) { + if (!in_array($fieldName, $extraFields)) { + unset($this->extraFields[$fieldName]); } - } else { - $this->extraFields = $extraFields; } + } else { + $this->extraFields = $extraFields; } } /** - * @param array $record - * @return array + * {@inheritDoc} */ - public function __invoke(array $record) + public function __invoke(array $record): array { // skip processing if for some reason request data // is not present (CLI or wonky SAPIs) @@ -82,12 +89,7 @@ public function __invoke(array $record) return $record; } - /** - * @param string $extraName - * @param string $serverName - * @return $this - */ - public function addExtraField($extraName, $serverName) + public function addExtraField(string $extraName, string $serverName): self { $this->extraFields[$extraName] = $serverName; @@ -95,17 +97,13 @@ public function addExtraField($extraName, $serverName) } /** - * @param array $extra - * @return array + * @param mixed[] $extra + * @return mixed[] */ - private function appendExtraFields(array $extra) + private function appendExtraFields(array $extra): array { foreach ($this->extraFields as $extraName => $serverName) { - $extra[$extraName] = isset($this->serverData[$serverName]) ? $this->serverData[$serverName] : null; - } - - if (isset($this->serverData['UNIQUE_ID'])) { - $extra['unique_id'] = $this->serverData['UNIQUE_ID']; + $extra[$extraName] = $this->serverData[$serverName] ?? null; } return $extra; diff --git a/vendor/monolog/monolog/src/Monolog/Registry.php b/vendor/monolog/monolog/src/Monolog/Registry.php index 159b751c..ae94ae6c 100644 --- a/vendor/monolog/monolog/src/Monolog/Registry.php +++ b/vendor/monolog/monolog/src/Monolog/Registry.php @@ -1,4 +1,4 @@ -addError('Sent to $api Logger instance'); - * Monolog\Registry::application()->addError('Sent to $application Logger instance'); + * Monolog\Registry::api()->error('Sent to $api Logger instance'); + * Monolog\Registry::application()->error('Sent to $application Logger instance'); * } * * @@ -42,7 +42,7 @@ class Registry * * @var Logger[] */ - private static $loggers = array(); + private static $loggers = []; /** * Adds new logging channel to the registry @@ -51,8 +51,9 @@ class Registry * @param string|null $name Name of the logging channel ($logger->getName() by default) * @param bool $overwrite Overwrite instance in the registry if the given name already exists? * @throws \InvalidArgumentException If $overwrite set to false and named Logger instance already exists + * @return void */ - public static function addLogger(Logger $logger, $name = null, $overwrite = false) + public static function addLogger(Logger $logger, ?string $name = null, bool $overwrite = false) { $name = $name ?: $logger->getName(); @@ -68,15 +69,15 @@ public static function addLogger(Logger $logger, $name = null, $overwrite = fals * * @param string|Logger $logger Name or logger instance */ - public static function hasLogger($logger) + public static function hasLogger($logger): bool { if ($logger instanceof Logger) { $index = array_search($logger, self::$loggers, true); return false !== $index; - } else { - return isset(self::$loggers[$logger]); } + + return isset(self::$loggers[$logger]); } /** @@ -84,7 +85,7 @@ public static function hasLogger($logger) * * @param string|Logger $logger Name or logger instance */ - public static function removeLogger($logger) + public static function removeLogger($logger): void { if ($logger instanceof Logger) { if (false !== ($idx = array_search($logger, self::$loggers, true))) { @@ -98,9 +99,9 @@ public static function removeLogger($logger) /** * Clears the registry */ - public static function clear() + public static function clear(): void { - self::$loggers = array(); + self::$loggers = []; } /** @@ -108,9 +109,8 @@ public static function clear() * * @param string $name Name of the requested Logger instance * @throws \InvalidArgumentException If named Logger instance is not in the registry - * @return Logger Requested instance of Logger */ - public static function getInstance($name) + public static function getInstance($name): Logger { if (!isset(self::$loggers[$name])) { throw new InvalidArgumentException(sprintf('Requested "%s" logger instance is not in the registry', $name)); @@ -123,7 +123,7 @@ public static function getInstance($name) * Gets Logger instance from the registry via static method call * * @param string $name Name of the requested Logger instance - * @param array $arguments Arguments passed to static method call + * @param mixed[] $arguments Arguments passed to static method call * @throws \InvalidArgumentException If named Logger instance is not in the registry * @return Logger Requested instance of Logger */ diff --git a/vendor/monolog/monolog/src/Monolog/ResettableInterface.php b/vendor/monolog/monolog/src/Monolog/ResettableInterface.php new file mode 100644 index 00000000..2c5fd785 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/ResettableInterface.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +/** + * Handler or Processor implementing this interface will be reset when Logger::reset() is called. + * + * Resetting ends a log cycle gets them back to their initial state. + * + * Resetting a Handler or a Processor means flushing/cleaning all buffers, resetting internal + * state, and getting it back to a state in which it can receive log records again. + * + * This is useful in case you want to avoid logs leaking between two requests or jobs when you + * have a long running process like a worker or an application server serving multiple requests + * in one process. + * + * @author Grégoire Pineau + */ +interface ResettableInterface +{ + /** + * @return void + */ + public function reset(); +} diff --git a/vendor/monolog/monolog/src/Monolog/SignalHandler.php b/vendor/monolog/monolog/src/Monolog/SignalHandler.php new file mode 100644 index 00000000..d730eea3 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/SignalHandler.php @@ -0,0 +1,120 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +use Psr\Log\LoggerInterface; +use Psr\Log\LogLevel; +use ReflectionExtension; + +/** + * Monolog POSIX signal handler + * + * @author Robert Gust-Bardon + * + * @phpstan-import-type Level from \Monolog\Logger + * @phpstan-import-type LevelName from \Monolog\Logger + */ +class SignalHandler +{ + /** @var LoggerInterface */ + private $logger; + + /** @var array SIG_DFL, SIG_IGN or previous callable */ + private $previousSignalHandler = []; + /** @var array */ + private $signalLevelMap = []; + /** @var array */ + private $signalRestartSyscalls = []; + + public function __construct(LoggerInterface $logger) + { + $this->logger = $logger; + } + + /** + * @param int|string $level Level or level name + * @param bool $callPrevious + * @param bool $restartSyscalls + * @param bool|null $async + * @return $this + * + * @phpstan-param Level|LevelName|LogLevel::* $level + */ + public function registerSignalHandler(int $signo, $level = LogLevel::CRITICAL, bool $callPrevious = true, bool $restartSyscalls = true, ?bool $async = true): self + { + if (!extension_loaded('pcntl') || !function_exists('pcntl_signal')) { + return $this; + } + + $level = Logger::toMonologLevel($level); + + if ($callPrevious) { + $handler = pcntl_signal_get_handler($signo); + $this->previousSignalHandler[$signo] = $handler; + } else { + unset($this->previousSignalHandler[$signo]); + } + $this->signalLevelMap[$signo] = $level; + $this->signalRestartSyscalls[$signo] = $restartSyscalls; + + if ($async !== null) { + pcntl_async_signals($async); + } + + pcntl_signal($signo, [$this, 'handleSignal'], $restartSyscalls); + + return $this; + } + + /** + * @param mixed $siginfo + */ + public function handleSignal(int $signo, $siginfo = null): void + { + static $signals = []; + + if (!$signals && extension_loaded('pcntl')) { + $pcntl = new ReflectionExtension('pcntl'); + // HHVM 3.24.2 returns an empty array. + foreach ($pcntl->getConstants() ?: get_defined_constants(true)['Core'] as $name => $value) { + if (substr($name, 0, 3) === 'SIG' && $name[3] !== '_' && is_int($value)) { + $signals[$value] = $name; + } + } + } + + $level = $this->signalLevelMap[$signo] ?? LogLevel::CRITICAL; + $signal = $signals[$signo] ?? $signo; + $context = $siginfo ?? []; + $this->logger->log($level, sprintf('Program received signal %s', $signal), $context); + + if (!isset($this->previousSignalHandler[$signo])) { + return; + } + + if ($this->previousSignalHandler[$signo] === SIG_DFL) { + if (extension_loaded('pcntl') && function_exists('pcntl_signal') && function_exists('pcntl_sigprocmask') && function_exists('pcntl_signal_dispatch') + && extension_loaded('posix') && function_exists('posix_getpid') && function_exists('posix_kill') + ) { + $restartSyscalls = $this->signalRestartSyscalls[$signo] ?? true; + pcntl_signal($signo, SIG_DFL, $restartSyscalls); + pcntl_sigprocmask(SIG_UNBLOCK, [$signo], $oldset); + posix_kill(posix_getpid(), $signo); + pcntl_signal_dispatch(); + pcntl_sigprocmask(SIG_SETMASK, $oldset); + pcntl_signal($signo, [$this, 'handleSignal'], $restartSyscalls); + } + } elseif (is_callable($this->previousSignalHandler[$signo])) { + $this->previousSignalHandler[$signo]($signo, $siginfo); + } + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Test/TestCase.php b/vendor/monolog/monolog/src/Monolog/Test/TestCase.php new file mode 100644 index 00000000..bc0b425e --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Test/TestCase.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Test; + +use Monolog\Logger; +use Monolog\DateTimeImmutable; +use Monolog\Formatter\FormatterInterface; + +/** + * Lets you easily generate log records and a dummy formatter for testing purposes + * + * @author Jordi Boggiano + * + * @phpstan-import-type Record from \Monolog\Logger + * @phpstan-import-type Level from \Monolog\Logger + * + * @internal feel free to reuse this to test your own handlers, this is marked internal to avoid issues with PHPStorm https://github.com/Seldaek/monolog/issues/1677 + */ +class TestCase extends \PHPUnit\Framework\TestCase +{ + public function tearDown(): void + { + parent::tearDown(); + + if (isset($this->handler)) { + unset($this->handler); + } + } + + /** + * @param mixed[] $context + * + * @return array Record + * + * @phpstan-param Level $level + * @phpstan-return Record + */ + protected function getRecord(int $level = Logger::WARNING, string $message = 'test', array $context = []): array + { + return [ + 'message' => (string) $message, + 'context' => $context, + 'level' => $level, + 'level_name' => Logger::getLevelName($level), + 'channel' => 'test', + 'datetime' => new DateTimeImmutable(true), + 'extra' => [], + ]; + } + + /** + * @phpstan-return Record[] + */ + protected function getMultipleRecords(): array + { + return [ + $this->getRecord(Logger::DEBUG, 'debug message 1'), + $this->getRecord(Logger::DEBUG, 'debug message 2'), + $this->getRecord(Logger::INFO, 'information'), + $this->getRecord(Logger::WARNING, 'warning'), + $this->getRecord(Logger::ERROR, 'error'), + ]; + } + + protected function getIdentityFormatter(): FormatterInterface + { + $formatter = $this->createMock(FormatterInterface::class); + $formatter->expects($this->any()) + ->method('format') + ->will($this->returnCallback(function ($record) { + return $record['message']; + })); + + return $formatter; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Utils.php b/vendor/monolog/monolog/src/Monolog/Utils.php new file mode 100644 index 00000000..360c4219 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Utils.php @@ -0,0 +1,284 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +final class Utils +{ + const DEFAULT_JSON_FLAGS = JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRESERVE_ZERO_FRACTION | JSON_INVALID_UTF8_SUBSTITUTE | JSON_PARTIAL_OUTPUT_ON_ERROR; + + public static function getClass(object $object): string + { + $class = \get_class($object); + + if (false === ($pos = \strpos($class, "@anonymous\0"))) { + return $class; + } + + if (false === ($parent = \get_parent_class($class))) { + return \substr($class, 0, $pos + 10); + } + + return $parent . '@anonymous'; + } + + public static function substr(string $string, int $start, ?int $length = null): string + { + if (extension_loaded('mbstring')) { + return mb_strcut($string, $start, $length); + } + + return substr($string, $start, (null === $length) ? strlen($string) : $length); + } + + /** + * Makes sure if a relative path is passed in it is turned into an absolute path + * + * @param string $streamUrl stream URL or path without protocol + */ + public static function canonicalizePath(string $streamUrl): string + { + $prefix = ''; + if ('file://' === substr($streamUrl, 0, 7)) { + $streamUrl = substr($streamUrl, 7); + $prefix = 'file://'; + } + + // other type of stream, not supported + if (false !== strpos($streamUrl, '://')) { + return $streamUrl; + } + + // already absolute + if (substr($streamUrl, 0, 1) === '/' || substr($streamUrl, 1, 1) === ':' || substr($streamUrl, 0, 2) === '\\\\') { + return $prefix.$streamUrl; + } + + $streamUrl = getcwd() . '/' . $streamUrl; + + return $prefix.$streamUrl; + } + + /** + * Return the JSON representation of a value + * + * @param mixed $data + * @param int $encodeFlags flags to pass to json encode, defaults to DEFAULT_JSON_FLAGS + * @param bool $ignoreErrors whether to ignore encoding errors or to throw on error, when ignored and the encoding fails, "null" is returned which is valid json for null + * @throws \RuntimeException if encoding fails and errors are not ignored + * @return string when errors are ignored and the encoding fails, "null" is returned which is valid json for null + */ + public static function jsonEncode($data, ?int $encodeFlags = null, bool $ignoreErrors = false): string + { + if (null === $encodeFlags) { + $encodeFlags = self::DEFAULT_JSON_FLAGS; + } + + if ($ignoreErrors) { + $json = @json_encode($data, $encodeFlags); + if (false === $json) { + return 'null'; + } + + return $json; + } + + $json = json_encode($data, $encodeFlags); + if (false === $json) { + $json = self::handleJsonError(json_last_error(), $data); + } + + return $json; + } + + /** + * Handle a json_encode failure. + * + * If the failure is due to invalid string encoding, try to clean the + * input and encode again. If the second encoding attempt fails, the + * initial error is not encoding related or the input can't be cleaned then + * raise a descriptive exception. + * + * @param int $code return code of json_last_error function + * @param mixed $data data that was meant to be encoded + * @param int $encodeFlags flags to pass to json encode, defaults to JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRESERVE_ZERO_FRACTION + * @throws \RuntimeException if failure can't be corrected + * @return string JSON encoded data after error correction + */ + public static function handleJsonError(int $code, $data, ?int $encodeFlags = null): string + { + if ($code !== JSON_ERROR_UTF8) { + self::throwEncodeError($code, $data); + } + + if (is_string($data)) { + self::detectAndCleanUtf8($data); + } elseif (is_array($data)) { + array_walk_recursive($data, array('Monolog\Utils', 'detectAndCleanUtf8')); + } else { + self::throwEncodeError($code, $data); + } + + if (null === $encodeFlags) { + $encodeFlags = self::DEFAULT_JSON_FLAGS; + } + + $json = json_encode($data, $encodeFlags); + + if ($json === false) { + self::throwEncodeError(json_last_error(), $data); + } + + return $json; + } + + /** + * @internal + */ + public static function pcreLastErrorMessage(int $code): string + { + if (PHP_VERSION_ID >= 80000) { + return preg_last_error_msg(); + } + + $constants = (get_defined_constants(true))['pcre']; + $constants = array_filter($constants, function ($key) { + return substr($key, -6) == '_ERROR'; + }, ARRAY_FILTER_USE_KEY); + + $constants = array_flip($constants); + + return $constants[$code] ?? 'UNDEFINED_ERROR'; + } + + /** + * Throws an exception according to a given code with a customized message + * + * @param int $code return code of json_last_error function + * @param mixed $data data that was meant to be encoded + * @throws \RuntimeException + * + * @return never + */ + private static function throwEncodeError(int $code, $data): void + { + switch ($code) { + case JSON_ERROR_DEPTH: + $msg = 'Maximum stack depth exceeded'; + break; + case JSON_ERROR_STATE_MISMATCH: + $msg = 'Underflow or the modes mismatch'; + break; + case JSON_ERROR_CTRL_CHAR: + $msg = 'Unexpected control character found'; + break; + case JSON_ERROR_UTF8: + $msg = 'Malformed UTF-8 characters, possibly incorrectly encoded'; + break; + default: + $msg = 'Unknown error'; + } + + throw new \RuntimeException('JSON encoding failed: '.$msg.'. Encoding: '.var_export($data, true)); + } + + /** + * Detect invalid UTF-8 string characters and convert to valid UTF-8. + * + * Valid UTF-8 input will be left unmodified, but strings containing + * invalid UTF-8 codepoints will be reencoded as UTF-8 with an assumed + * original encoding of ISO-8859-15. This conversion may result in + * incorrect output if the actual encoding was not ISO-8859-15, but it + * will be clean UTF-8 output and will not rely on expensive and fragile + * detection algorithms. + * + * Function converts the input in place in the passed variable so that it + * can be used as a callback for array_walk_recursive. + * + * @param mixed $data Input to check and convert if needed, passed by ref + */ + private static function detectAndCleanUtf8(&$data): void + { + if (is_string($data) && !preg_match('//u', $data)) { + $data = preg_replace_callback( + '/[\x80-\xFF]+/', + function ($m) { + return function_exists('mb_convert_encoding') ? mb_convert_encoding($m[0], 'UTF-8', 'ISO-8859-1') : utf8_encode($m[0]); + }, + $data + ); + if (!is_string($data)) { + $pcreErrorCode = preg_last_error(); + throw new \RuntimeException('Failed to preg_replace_callback: ' . $pcreErrorCode . ' / ' . self::pcreLastErrorMessage($pcreErrorCode)); + } + $data = str_replace( + ['¤', '¦', '¨', '´', '¸', '¼', '½', '¾'], + ['€', 'Š', 'š', 'Ž', 'ž', 'Œ', 'œ', 'Ÿ'], + $data + ); + } + } + + /** + * Converts a string with a valid 'memory_limit' format, to bytes. + * + * @param string|false $val + * @return int|false Returns an integer representing bytes. Returns FALSE in case of error. + */ + public static function expandIniShorthandBytes($val) + { + if (!is_string($val)) { + return false; + } + + // support -1 + if ((int) $val < 0) { + return (int) $val; + } + + if (!preg_match('/^\s*(?\d+)(?:\.\d+)?\s*(?[gmk]?)\s*$/i', $val, $match)) { + return false; + } + + $val = (int) $match['val']; + switch (strtolower($match['unit'] ?? '')) { + case 'g': + $val *= 1024; + case 'm': + $val *= 1024; + case 'k': + $val *= 1024; + } + + return $val; + } + + /** + * @param array $record + */ + public static function getRecordMessageForException(array $record): string + { + $context = ''; + $extra = ''; + try { + if ($record['context']) { + $context = "\nContext: " . json_encode($record['context']); + } + if ($record['extra']) { + $extra = "\nExtra: " . json_encode($record['extra']); + } + } catch (\Throwable $e) { + // noop + } + + return "\nThe exception occurred while attempting to log: " . $record['message'] . $context . $extra; + } +} diff --git a/vendor/mtdowling/jmespath.php/src/SyntaxErrorException.php b/vendor/mtdowling/jmespath.php/src/SyntaxErrorException.php index 65be17ec..68683d06 100644 --- a/vendor/mtdowling/jmespath.php/src/SyntaxErrorException.php +++ b/vendor/mtdowling/jmespath.php/src/SyntaxErrorException.php @@ -17,7 +17,7 @@ public function __construct( $expression ) { $message = "Syntax error at character {$token['pos']}\n" - . $expression . "\n" . str_repeat(' ', $token['pos']) . "^\n"; + . $expression . "\n" . str_repeat(' ', max($token['pos'], 0)) . "^\n"; $message .= !is_array($expectedTypesOrMessage) ? $expectedTypesOrMessage : $this->createTokenMessage($token, $expectedTypesOrMessage); diff --git a/vendor/paragonie/constant_time_encoding/LICENSE.txt b/vendor/paragonie/constant_time_encoding/LICENSE.txt new file mode 100644 index 00000000..91acaca6 --- /dev/null +++ b/vendor/paragonie/constant_time_encoding/LICENSE.txt @@ -0,0 +1,48 @@ +The MIT License (MIT) + +Copyright (c) 2016 - 2022 Paragon Initiative Enterprises + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +------------------------------------------------------------------------------ +This library was based on the work of Steve "Sc00bz" Thomas. +------------------------------------------------------------------------------ + +The MIT License (MIT) + +Copyright (c) 2014 Steve Thomas + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/vendor/paragonie/constant_time_encoding/src/Base32.php b/vendor/paragonie/constant_time_encoding/src/Base32.php new file mode 100644 index 00000000..7508b3df --- /dev/null +++ b/vendor/paragonie/constant_time_encoding/src/Base32.php @@ -0,0 +1,519 @@ + 96 && $src < 123) $ret += $src - 97 + 1; // -64 + $ret += (((0x60 - $src) & ($src - 0x7b)) >> 8) & ($src - 96); + + // if ($src > 0x31 && $src < 0x38) $ret += $src - 24 + 1; // -23 + $ret += (((0x31 - $src) & ($src - 0x38)) >> 8) & ($src - 23); + + return $ret; + } + + /** + * Uses bitwise operators instead of table-lookups to turn 5-bit integers + * into 8-bit integers. + * + * Uppercase variant. + * + * @param int $src + * @return int + */ + protected static function decode5BitsUpper(int $src): int + { + $ret = -1; + + // if ($src > 64 && $src < 91) $ret += $src - 65 + 1; // -64 + $ret += (((0x40 - $src) & ($src - 0x5b)) >> 8) & ($src - 64); + + // if ($src > 0x31 && $src < 0x38) $ret += $src - 24 + 1; // -23 + $ret += (((0x31 - $src) & ($src - 0x38)) >> 8) & ($src - 23); + + return $ret; + } + + /** + * Uses bitwise operators instead of table-lookups to turn 8-bit integers + * into 5-bit integers. + * + * @param int $src + * @return string + */ + protected static function encode5Bits(int $src): string + { + $diff = 0x61; + + // if ($src > 25) $ret -= 72; + $diff -= ((25 - $src) >> 8) & 73; + + return \pack('C', $src + $diff); + } + + /** + * Uses bitwise operators instead of table-lookups to turn 8-bit integers + * into 5-bit integers. + * + * Uppercase variant. + * + * @param int $src + * @return string + */ + protected static function encode5BitsUpper(int $src): string + { + $diff = 0x41; + + // if ($src > 25) $ret -= 40; + $diff -= ((25 - $src) >> 8) & 41; + + return \pack('C', $src + $diff); + } + + /** + * @param string $encodedString + * @param bool $upper + * @return string + */ + public static function decodeNoPadding(string $encodedString, bool $upper = false): string + { + $srcLen = Binary::safeStrlen($encodedString); + if ($srcLen === 0) { + return ''; + } + if (($srcLen & 7) === 0) { + for ($j = 0; $j < 7 && $j < $srcLen; ++$j) { + if ($encodedString[$srcLen - $j - 1] === '=') { + throw new InvalidArgumentException( + "decodeNoPadding() doesn't tolerate padding" + ); + } + } + } + return static::doDecode( + $encodedString, + $upper, + true + ); + } + + /** + * Base32 decoding + * + * @param string $src + * @param bool $upper + * @param bool $strictPadding + * @return string + * + * @throws TypeError + * @psalm-suppress RedundantCondition + */ + protected static function doDecode( + string $src, + bool $upper = false, + bool $strictPadding = false + ): string { + // We do this to reduce code duplication: + $method = $upper + ? 'decode5BitsUpper' + : 'decode5Bits'; + + // Remove padding + $srcLen = Binary::safeStrlen($src); + if ($srcLen === 0) { + return ''; + } + if ($strictPadding) { + if (($srcLen & 7) === 0) { + for ($j = 0; $j < 7; ++$j) { + if ($src[$srcLen - 1] === '=') { + $srcLen--; + } else { + break; + } + } + } + if (($srcLen & 7) === 1) { + throw new RangeException( + 'Incorrect padding' + ); + } + } else { + $src = \rtrim($src, '='); + $srcLen = Binary::safeStrlen($src); + } + + $err = 0; + $dest = ''; + // Main loop (no padding): + for ($i = 0; $i + 8 <= $srcLen; $i += 8) { + /** @var array $chunk */ + $chunk = \unpack('C*', Binary::safeSubstr($src, $i, 8)); + /** @var int $c0 */ + $c0 = static::$method($chunk[1]); + /** @var int $c1 */ + $c1 = static::$method($chunk[2]); + /** @var int $c2 */ + $c2 = static::$method($chunk[3]); + /** @var int $c3 */ + $c3 = static::$method($chunk[4]); + /** @var int $c4 */ + $c4 = static::$method($chunk[5]); + /** @var int $c5 */ + $c5 = static::$method($chunk[6]); + /** @var int $c6 */ + $c6 = static::$method($chunk[7]); + /** @var int $c7 */ + $c7 = static::$method($chunk[8]); + + $dest .= \pack( + 'CCCCC', + (($c0 << 3) | ($c1 >> 2) ) & 0xff, + (($c1 << 6) | ($c2 << 1) | ($c3 >> 4)) & 0xff, + (($c3 << 4) | ($c4 >> 1) ) & 0xff, + (($c4 << 7) | ($c5 << 2) | ($c6 >> 3)) & 0xff, + (($c6 << 5) | ($c7 ) ) & 0xff + ); + $err |= ($c0 | $c1 | $c2 | $c3 | $c4 | $c5 | $c6 | $c7) >> 8; + } + // The last chunk, which may have padding: + if ($i < $srcLen) { + /** @var array $chunk */ + $chunk = \unpack('C*', Binary::safeSubstr($src, $i, $srcLen - $i)); + /** @var int $c0 */ + $c0 = static::$method($chunk[1]); + + if ($i + 6 < $srcLen) { + /** @var int $c1 */ + $c1 = static::$method($chunk[2]); + /** @var int $c2 */ + $c2 = static::$method($chunk[3]); + /** @var int $c3 */ + $c3 = static::$method($chunk[4]); + /** @var int $c4 */ + $c4 = static::$method($chunk[5]); + /** @var int $c5 */ + $c5 = static::$method($chunk[6]); + /** @var int $c6 */ + $c6 = static::$method($chunk[7]); + + $dest .= \pack( + 'CCCC', + (($c0 << 3) | ($c1 >> 2) ) & 0xff, + (($c1 << 6) | ($c2 << 1) | ($c3 >> 4)) & 0xff, + (($c3 << 4) | ($c4 >> 1) ) & 0xff, + (($c4 << 7) | ($c5 << 2) | ($c6 >> 3)) & 0xff + ); + $err |= ($c0 | $c1 | $c2 | $c3 | $c4 | $c5 | $c6) >> 8; + if ($strictPadding) { + $err |= ($c6 << 5) & 0xff; + } + } elseif ($i + 5 < $srcLen) { + /** @var int $c1 */ + $c1 = static::$method($chunk[2]); + /** @var int $c2 */ + $c2 = static::$method($chunk[3]); + /** @var int $c3 */ + $c3 = static::$method($chunk[4]); + /** @var int $c4 */ + $c4 = static::$method($chunk[5]); + /** @var int $c5 */ + $c5 = static::$method($chunk[6]); + + $dest .= \pack( + 'CCCC', + (($c0 << 3) | ($c1 >> 2) ) & 0xff, + (($c1 << 6) | ($c2 << 1) | ($c3 >> 4)) & 0xff, + (($c3 << 4) | ($c4 >> 1) ) & 0xff, + (($c4 << 7) | ($c5 << 2) ) & 0xff + ); + $err |= ($c0 | $c1 | $c2 | $c3 | $c4 | $c5) >> 8; + } elseif ($i + 4 < $srcLen) { + /** @var int $c1 */ + $c1 = static::$method($chunk[2]); + /** @var int $c2 */ + $c2 = static::$method($chunk[3]); + /** @var int $c3 */ + $c3 = static::$method($chunk[4]); + /** @var int $c4 */ + $c4 = static::$method($chunk[5]); + + $dest .= \pack( + 'CCC', + (($c0 << 3) | ($c1 >> 2) ) & 0xff, + (($c1 << 6) | ($c2 << 1) | ($c3 >> 4)) & 0xff, + (($c3 << 4) | ($c4 >> 1) ) & 0xff + ); + $err |= ($c0 | $c1 | $c2 | $c3 | $c4) >> 8; + if ($strictPadding) { + $err |= ($c4 << 7) & 0xff; + } + } elseif ($i + 3 < $srcLen) { + /** @var int $c1 */ + $c1 = static::$method($chunk[2]); + /** @var int $c2 */ + $c2 = static::$method($chunk[3]); + /** @var int $c3 */ + $c3 = static::$method($chunk[4]); + + $dest .= \pack( + 'CC', + (($c0 << 3) | ($c1 >> 2) ) & 0xff, + (($c1 << 6) | ($c2 << 1) | ($c3 >> 4)) & 0xff + ); + $err |= ($c0 | $c1 | $c2 | $c3) >> 8; + if ($strictPadding) { + $err |= ($c3 << 4) & 0xff; + } + } elseif ($i + 2 < $srcLen) { + /** @var int $c1 */ + $c1 = static::$method($chunk[2]); + /** @var int $c2 */ + $c2 = static::$method($chunk[3]); + + $dest .= \pack( + 'CC', + (($c0 << 3) | ($c1 >> 2) ) & 0xff, + (($c1 << 6) | ($c2 << 1) ) & 0xff + ); + $err |= ($c0 | $c1 | $c2) >> 8; + if ($strictPadding) { + $err |= ($c2 << 6) & 0xff; + } + } elseif ($i + 1 < $srcLen) { + /** @var int $c1 */ + $c1 = static::$method($chunk[2]); + + $dest .= \pack( + 'C', + (($c0 << 3) | ($c1 >> 2) ) & 0xff + ); + $err |= ($c0 | $c1) >> 8; + if ($strictPadding) { + $err |= ($c1 << 6) & 0xff; + } + } else { + $dest .= \pack( + 'C', + (($c0 << 3) ) & 0xff + ); + $err |= ($c0) >> 8; + } + } + $check = ($err === 0); + if (!$check) { + throw new RangeException( + 'Base32::doDecode() only expects characters in the correct base32 alphabet' + ); + } + return $dest; + } + + /** + * Base32 Encoding + * + * @param string $src + * @param bool $upper + * @param bool $pad + * @return string + * @throws TypeError + */ + protected static function doEncode(string $src, bool $upper = false, $pad = true): string + { + // We do this to reduce code duplication: + $method = $upper + ? 'encode5BitsUpper' + : 'encode5Bits'; + + $dest = ''; + $srcLen = Binary::safeStrlen($src); + + // Main loop (no padding): + for ($i = 0; $i + 5 <= $srcLen; $i += 5) { + /** @var array $chunk */ + $chunk = \unpack('C*', Binary::safeSubstr($src, $i, 5)); + $b0 = $chunk[1]; + $b1 = $chunk[2]; + $b2 = $chunk[3]; + $b3 = $chunk[4]; + $b4 = $chunk[5]; + $dest .= + static::$method( ($b0 >> 3) & 31) . + static::$method((($b0 << 2) | ($b1 >> 6)) & 31) . + static::$method((($b1 >> 1) ) & 31) . + static::$method((($b1 << 4) | ($b2 >> 4)) & 31) . + static::$method((($b2 << 1) | ($b3 >> 7)) & 31) . + static::$method((($b3 >> 2) ) & 31) . + static::$method((($b3 << 3) | ($b4 >> 5)) & 31) . + static::$method( $b4 & 31); + } + // The last chunk, which may have padding: + if ($i < $srcLen) { + /** @var array $chunk */ + $chunk = \unpack('C*', Binary::safeSubstr($src, $i, $srcLen - $i)); + $b0 = $chunk[1]; + if ($i + 3 < $srcLen) { + $b1 = $chunk[2]; + $b2 = $chunk[3]; + $b3 = $chunk[4]; + $dest .= + static::$method( ($b0 >> 3) & 31) . + static::$method((($b0 << 2) | ($b1 >> 6)) & 31) . + static::$method((($b1 >> 1) ) & 31) . + static::$method((($b1 << 4) | ($b2 >> 4)) & 31) . + static::$method((($b2 << 1) | ($b3 >> 7)) & 31) . + static::$method((($b3 >> 2) ) & 31) . + static::$method((($b3 << 3) ) & 31); + if ($pad) { + $dest .= '='; + } + } elseif ($i + 2 < $srcLen) { + $b1 = $chunk[2]; + $b2 = $chunk[3]; + $dest .= + static::$method( ($b0 >> 3) & 31) . + static::$method((($b0 << 2) | ($b1 >> 6)) & 31) . + static::$method((($b1 >> 1) ) & 31) . + static::$method((($b1 << 4) | ($b2 >> 4)) & 31) . + static::$method((($b2 << 1) ) & 31); + if ($pad) { + $dest .= '==='; + } + } elseif ($i + 1 < $srcLen) { + $b1 = $chunk[2]; + $dest .= + static::$method( ($b0 >> 3) & 31) . + static::$method((($b0 << 2) | ($b1 >> 6)) & 31) . + static::$method((($b1 >> 1) ) & 31) . + static::$method((($b1 << 4) ) & 31); + if ($pad) { + $dest .= '===='; + } + } else { + $dest .= + static::$method( ($b0 >> 3) & 31) . + static::$method( ($b0 << 2) & 31); + if ($pad) { + $dest .= '======'; + } + } + } + return $dest; + } +} diff --git a/vendor/paragonie/constant_time_encoding/src/Base32Hex.php b/vendor/paragonie/constant_time_encoding/src/Base32Hex.php new file mode 100644 index 00000000..b868dd04 --- /dev/null +++ b/vendor/paragonie/constant_time_encoding/src/Base32Hex.php @@ -0,0 +1,111 @@ + 0x30 && $src < 0x3a) ret += $src - 0x2e + 1; // -47 + $ret += (((0x2f - $src) & ($src - 0x3a)) >> 8) & ($src - 47); + + // if ($src > 0x60 && $src < 0x77) ret += $src - 0x61 + 10 + 1; // -86 + $ret += (((0x60 - $src) & ($src - 0x77)) >> 8) & ($src - 86); + + return $ret; + } + + /** + * Uses bitwise operators instead of table-lookups to turn 5-bit integers + * into 8-bit integers. + * + * @param int $src + * @return int + */ + protected static function decode5BitsUpper(int $src): int + { + $ret = -1; + + // if ($src > 0x30 && $src < 0x3a) ret += $src - 0x2e + 1; // -47 + $ret += (((0x2f - $src) & ($src - 0x3a)) >> 8) & ($src - 47); + + // if ($src > 0x40 && $src < 0x57) ret += $src - 0x41 + 10 + 1; // -54 + $ret += (((0x40 - $src) & ($src - 0x57)) >> 8) & ($src - 54); + + return $ret; + } + + /** + * Uses bitwise operators instead of table-lookups to turn 8-bit integers + * into 5-bit integers. + * + * @param int $src + * @return string + */ + protected static function encode5Bits(int $src): string + { + $src += 0x30; + + // if ($src > 0x39) $src += 0x61 - 0x3a; // 39 + $src += ((0x39 - $src) >> 8) & 39; + + return \pack('C', $src); + } + + /** + * Uses bitwise operators instead of table-lookups to turn 8-bit integers + * into 5-bit integers. + * + * Uppercase variant. + * + * @param int $src + * @return string + */ + protected static function encode5BitsUpper(int $src): string + { + $src += 0x30; + + // if ($src > 0x39) $src += 0x41 - 0x3a; // 7 + $src += ((0x39 - $src) >> 8) & 7; + + return \pack('C', $src); + } +} \ No newline at end of file diff --git a/vendor/paragonie/constant_time_encoding/src/Base64.php b/vendor/paragonie/constant_time_encoding/src/Base64.php new file mode 100644 index 00000000..f5716179 --- /dev/null +++ b/vendor/paragonie/constant_time_encoding/src/Base64.php @@ -0,0 +1,314 @@ + $chunk */ + $chunk = \unpack('C*', Binary::safeSubstr($src, $i, 3)); + $b0 = $chunk[1]; + $b1 = $chunk[2]; + $b2 = $chunk[3]; + + $dest .= + static::encode6Bits( $b0 >> 2 ) . + static::encode6Bits((($b0 << 4) | ($b1 >> 4)) & 63) . + static::encode6Bits((($b1 << 2) | ($b2 >> 6)) & 63) . + static::encode6Bits( $b2 & 63); + } + // The last chunk, which may have padding: + if ($i < $srcLen) { + /** @var array $chunk */ + $chunk = \unpack('C*', Binary::safeSubstr($src, $i, $srcLen - $i)); + $b0 = $chunk[1]; + if ($i + 1 < $srcLen) { + $b1 = $chunk[2]; + $dest .= + static::encode6Bits($b0 >> 2) . + static::encode6Bits((($b0 << 4) | ($b1 >> 4)) & 63) . + static::encode6Bits(($b1 << 2) & 63); + if ($pad) { + $dest .= '='; + } + } else { + $dest .= + static::encode6Bits( $b0 >> 2) . + static::encode6Bits(($b0 << 4) & 63); + if ($pad) { + $dest .= '=='; + } + } + } + return $dest; + } + + /** + * decode from base64 into binary + * + * Base64 character set "./[A-Z][a-z][0-9]" + * + * @param string $encodedString + * @param bool $strictPadding + * @return string + * + * @throws RangeException + * @throws TypeError + * @psalm-suppress RedundantCondition + */ + public static function decode(string $encodedString, bool $strictPadding = false): string + { + // Remove padding + $srcLen = Binary::safeStrlen($encodedString); + if ($srcLen === 0) { + return ''; + } + + if ($strictPadding) { + if (($srcLen & 3) === 0) { + if ($encodedString[$srcLen - 1] === '=') { + $srcLen--; + if ($encodedString[$srcLen - 1] === '=') { + $srcLen--; + } + } + } + if (($srcLen & 3) === 1) { + throw new RangeException( + 'Incorrect padding' + ); + } + if ($encodedString[$srcLen - 1] === '=') { + throw new RangeException( + 'Incorrect padding' + ); + } + } else { + $encodedString = \rtrim($encodedString, '='); + $srcLen = Binary::safeStrlen($encodedString); + } + + $err = 0; + $dest = ''; + // Main loop (no padding): + for ($i = 0; $i + 4 <= $srcLen; $i += 4) { + /** @var array $chunk */ + $chunk = \unpack('C*', Binary::safeSubstr($encodedString, $i, 4)); + $c0 = static::decode6Bits($chunk[1]); + $c1 = static::decode6Bits($chunk[2]); + $c2 = static::decode6Bits($chunk[3]); + $c3 = static::decode6Bits($chunk[4]); + + $dest .= \pack( + 'CCC', + ((($c0 << 2) | ($c1 >> 4)) & 0xff), + ((($c1 << 4) | ($c2 >> 2)) & 0xff), + ((($c2 << 6) | $c3 ) & 0xff) + ); + $err |= ($c0 | $c1 | $c2 | $c3) >> 8; + } + // The last chunk, which may have padding: + if ($i < $srcLen) { + /** @var array $chunk */ + $chunk = \unpack('C*', Binary::safeSubstr($encodedString, $i, $srcLen - $i)); + $c0 = static::decode6Bits($chunk[1]); + + if ($i + 2 < $srcLen) { + $c1 = static::decode6Bits($chunk[2]); + $c2 = static::decode6Bits($chunk[3]); + $dest .= \pack( + 'CC', + ((($c0 << 2) | ($c1 >> 4)) & 0xff), + ((($c1 << 4) | ($c2 >> 2)) & 0xff) + ); + $err |= ($c0 | $c1 | $c2) >> 8; + if ($strictPadding) { + $err |= ($c2 << 6) & 0xff; + } + } elseif ($i + 1 < $srcLen) { + $c1 = static::decode6Bits($chunk[2]); + $dest .= \pack( + 'C', + ((($c0 << 2) | ($c1 >> 4)) & 0xff) + ); + $err |= ($c0 | $c1) >> 8; + if ($strictPadding) { + $err |= ($c1 << 4) & 0xff; + } + } elseif ($strictPadding) { + $err |= 1; + } + } + $check = ($err === 0); + if (!$check) { + throw new RangeException( + 'Base64::decode() only expects characters in the correct base64 alphabet' + ); + } + return $dest; + } + + /** + * @param string $encodedString + * @return string + */ + public static function decodeNoPadding(string $encodedString): string + { + $srcLen = Binary::safeStrlen($encodedString); + if ($srcLen === 0) { + return ''; + } + if (($srcLen & 3) === 0) { + if ($encodedString[$srcLen - 1] === '=') { + throw new InvalidArgumentException( + "decodeNoPadding() doesn't tolerate padding" + ); + } + if (($srcLen & 3) > 1) { + if ($encodedString[$srcLen - 2] === '=') { + throw new InvalidArgumentException( + "decodeNoPadding() doesn't tolerate padding" + ); + } + } + } + return static::decode( + $encodedString, + true + ); + } + + /** + * Uses bitwise operators instead of table-lookups to turn 6-bit integers + * into 8-bit integers. + * + * Base64 character set: + * [A-Z] [a-z] [0-9] + / + * 0x41-0x5a, 0x61-0x7a, 0x30-0x39, 0x2b, 0x2f + * + * @param int $src + * @return int + */ + protected static function decode6Bits(int $src): int + { + $ret = -1; + + // if ($src > 0x40 && $src < 0x5b) $ret += $src - 0x41 + 1; // -64 + $ret += (((0x40 - $src) & ($src - 0x5b)) >> 8) & ($src - 64); + + // if ($src > 0x60 && $src < 0x7b) $ret += $src - 0x61 + 26 + 1; // -70 + $ret += (((0x60 - $src) & ($src - 0x7b)) >> 8) & ($src - 70); + + // if ($src > 0x2f && $src < 0x3a) $ret += $src - 0x30 + 52 + 1; // 5 + $ret += (((0x2f - $src) & ($src - 0x3a)) >> 8) & ($src + 5); + + // if ($src == 0x2b) $ret += 62 + 1; + $ret += (((0x2a - $src) & ($src - 0x2c)) >> 8) & 63; + + // if ($src == 0x2f) ret += 63 + 1; + $ret += (((0x2e - $src) & ($src - 0x30)) >> 8) & 64; + + return $ret; + } + + /** + * Uses bitwise operators instead of table-lookups to turn 8-bit integers + * into 6-bit integers. + * + * @param int $src + * @return string + */ + protected static function encode6Bits(int $src): string + { + $diff = 0x41; + + // if ($src > 25) $diff += 0x61 - 0x41 - 26; // 6 + $diff += ((25 - $src) >> 8) & 6; + + // if ($src > 51) $diff += 0x30 - 0x61 - 26; // -75 + $diff -= ((51 - $src) >> 8) & 75; + + // if ($src > 61) $diff += 0x2b - 0x30 - 10; // -15 + $diff -= ((61 - $src) >> 8) & 15; + + // if ($src > 62) $diff += 0x2f - 0x2b - 1; // 3 + $diff += ((62 - $src) >> 8) & 3; + + return \pack('C', $src + $diff); + } +} diff --git a/vendor/paragonie/constant_time_encoding/src/Base64DotSlash.php b/vendor/paragonie/constant_time_encoding/src/Base64DotSlash.php new file mode 100644 index 00000000..5e98a8f7 --- /dev/null +++ b/vendor/paragonie/constant_time_encoding/src/Base64DotSlash.php @@ -0,0 +1,88 @@ + 0x2d && $src < 0x30) ret += $src - 0x2e + 1; // -45 + $ret += (((0x2d - $src) & ($src - 0x30)) >> 8) & ($src - 45); + + // if ($src > 0x40 && $src < 0x5b) ret += $src - 0x41 + 2 + 1; // -62 + $ret += (((0x40 - $src) & ($src - 0x5b)) >> 8) & ($src - 62); + + // if ($src > 0x60 && $src < 0x7b) ret += $src - 0x61 + 28 + 1; // -68 + $ret += (((0x60 - $src) & ($src - 0x7b)) >> 8) & ($src - 68); + + // if ($src > 0x2f && $src < 0x3a) ret += $src - 0x30 + 54 + 1; // 7 + $ret += (((0x2f - $src) & ($src - 0x3a)) >> 8) & ($src + 7); + + return $ret; + } + + /** + * Uses bitwise operators instead of table-lookups to turn 8-bit integers + * into 6-bit integers. + * + * @param int $src + * @return string + */ + protected static function encode6Bits(int $src): string + { + $src += 0x2e; + + // if ($src > 0x2f) $src += 0x41 - 0x30; // 17 + $src += ((0x2f - $src) >> 8) & 17; + + // if ($src > 0x5a) $src += 0x61 - 0x5b; // 6 + $src += ((0x5a - $src) >> 8) & 6; + + // if ($src > 0x7a) $src += 0x30 - 0x7b; // -75 + $src -= ((0x7a - $src) >> 8) & 75; + + return \pack('C', $src); + } +} diff --git a/vendor/paragonie/constant_time_encoding/src/Base64DotSlashOrdered.php b/vendor/paragonie/constant_time_encoding/src/Base64DotSlashOrdered.php new file mode 100644 index 00000000..9780b14b --- /dev/null +++ b/vendor/paragonie/constant_time_encoding/src/Base64DotSlashOrdered.php @@ -0,0 +1,82 @@ + 0x2d && $src < 0x3a) ret += $src - 0x2e + 1; // -45 + $ret += (((0x2d - $src) & ($src - 0x3a)) >> 8) & ($src - 45); + + // if ($src > 0x40 && $src < 0x5b) ret += $src - 0x41 + 12 + 1; // -52 + $ret += (((0x40 - $src) & ($src - 0x5b)) >> 8) & ($src - 52); + + // if ($src > 0x60 && $src < 0x7b) ret += $src - 0x61 + 38 + 1; // -58 + $ret += (((0x60 - $src) & ($src - 0x7b)) >> 8) & ($src - 58); + + return $ret; + } + + /** + * Uses bitwise operators instead of table-lookups to turn 8-bit integers + * into 6-bit integers. + * + * @param int $src + * @return string + */ + protected static function encode6Bits(int $src): string + { + $src += 0x2e; + + // if ($src > 0x39) $src += 0x41 - 0x3a; // 7 + $src += ((0x39 - $src) >> 8) & 7; + + // if ($src > 0x5a) $src += 0x61 - 0x5b; // 6 + $src += ((0x5a - $src) >> 8) & 6; + + return \pack('C', $src); + } +} diff --git a/vendor/paragonie/constant_time_encoding/src/Base64UrlSafe.php b/vendor/paragonie/constant_time_encoding/src/Base64UrlSafe.php new file mode 100644 index 00000000..8192c63d --- /dev/null +++ b/vendor/paragonie/constant_time_encoding/src/Base64UrlSafe.php @@ -0,0 +1,95 @@ + 0x40 && $src < 0x5b) $ret += $src - 0x41 + 1; // -64 + $ret += (((0x40 - $src) & ($src - 0x5b)) >> 8) & ($src - 64); + + // if ($src > 0x60 && $src < 0x7b) $ret += $src - 0x61 + 26 + 1; // -70 + $ret += (((0x60 - $src) & ($src - 0x7b)) >> 8) & ($src - 70); + + // if ($src > 0x2f && $src < 0x3a) $ret += $src - 0x30 + 52 + 1; // 5 + $ret += (((0x2f - $src) & ($src - 0x3a)) >> 8) & ($src + 5); + + // if ($src == 0x2c) $ret += 62 + 1; + $ret += (((0x2c - $src) & ($src - 0x2e)) >> 8) & 63; + + // if ($src == 0x5f) ret += 63 + 1; + $ret += (((0x5e - $src) & ($src - 0x60)) >> 8) & 64; + + return $ret; + } + + /** + * Uses bitwise operators instead of table-lookups to turn 8-bit integers + * into 6-bit integers. + * + * @param int $src + * @return string + */ + protected static function encode6Bits(int $src): string + { + $diff = 0x41; + + // if ($src > 25) $diff += 0x61 - 0x41 - 26; // 6 + $diff += ((25 - $src) >> 8) & 6; + + // if ($src > 51) $diff += 0x30 - 0x61 - 26; // -75 + $diff -= ((51 - $src) >> 8) & 75; + + // if ($src > 61) $diff += 0x2d - 0x30 - 10; // -13 + $diff -= ((61 - $src) >> 8) & 13; + + // if ($src > 62) $diff += 0x5f - 0x2b - 1; // 3 + $diff += ((62 - $src) >> 8) & 49; + + return \pack('C', $src + $diff); + } +} diff --git a/vendor/paragonie/constant_time_encoding/src/Binary.php b/vendor/paragonie/constant_time_encoding/src/Binary.php new file mode 100644 index 00000000..828f3e0f --- /dev/null +++ b/vendor/paragonie/constant_time_encoding/src/Binary.php @@ -0,0 +1,90 @@ + $chunk */ + $chunk = \unpack('C', $binString[$i]); + $c = $chunk[1] & 0xf; + $b = $chunk[1] >> 4; + + $hex .= \pack( + 'CC', + (87 + $b + ((($b - 10) >> 8) & ~38)), + (87 + $c + ((($c - 10) >> 8) & ~38)) + ); + } + return $hex; + } + + /** + * Convert a binary string into a hexadecimal string without cache-timing + * leaks, returning uppercase letters (as per RFC 4648) + * + * @param string $binString (raw binary) + * @return string + * @throws TypeError + */ + public static function encodeUpper(string $binString): string + { + $hex = ''; + $len = Binary::safeStrlen($binString); + + for ($i = 0; $i < $len; ++$i) { + /** @var array $chunk */ + $chunk = \unpack('C', $binString[$i]); + $c = $chunk[1] & 0xf; + $b = $chunk[1] >> 4; + + $hex .= \pack( + 'CC', + (55 + $b + ((($b - 10) >> 8) & ~6)), + (55 + $c + ((($c - 10) >> 8) & ~6)) + ); + } + return $hex; + } + + /** + * Convert a hexadecimal string into a binary string without cache-timing + * leaks + * + * @param string $encodedString + * @param bool $strictPadding + * @return string (raw binary) + * @throws RangeException + */ + public static function decode( + string $encodedString, + bool $strictPadding = false + ): string { + $hex_pos = 0; + $bin = ''; + $c_acc = 0; + $hex_len = Binary::safeStrlen($encodedString); + $state = 0; + if (($hex_len & 1) !== 0) { + if ($strictPadding) { + throw new RangeException( + 'Expected an even number of hexadecimal characters' + ); + } else { + $encodedString = '0' . $encodedString; + ++$hex_len; + } + } + + /** @var array $chunk */ + $chunk = \unpack('C*', $encodedString); + while ($hex_pos < $hex_len) { + ++$hex_pos; + $c = $chunk[$hex_pos]; + $c_num = $c ^ 48; + $c_num0 = ($c_num - 10) >> 8; + $c_alpha = ($c & ~32) - 55; + $c_alpha0 = (($c_alpha - 10) ^ ($c_alpha - 16)) >> 8; + + if (($c_num0 | $c_alpha0) === 0) { + throw new RangeException( + 'Expected hexadecimal character' + ); + } + $c_val = ($c_num0 & $c_num) | ($c_alpha & $c_alpha0); + if ($state === 0) { + $c_acc = $c_val * 16; + } else { + $bin .= \pack('C', $c_acc | $c_val); + } + $state ^= 1; + } + return $bin; + } +} diff --git a/vendor/paragonie/constant_time_encoding/src/RFC4648.php b/vendor/paragonie/constant_time_encoding/src/RFC4648.php new file mode 100644 index 00000000..f124d65b --- /dev/null +++ b/vendor/paragonie/constant_time_encoding/src/RFC4648.php @@ -0,0 +1,186 @@ + "Zm9v" + * + * @param string $str + * @return string + * + * @throws TypeError + */ + public static function base64Encode(string $str): string + { + return Base64::encode($str); + } + + /** + * RFC 4648 Base64 decoding + * + * "Zm9v" -> "foo" + * + * @param string $str + * @return string + * + * @throws TypeError + */ + public static function base64Decode(string $str): string + { + return Base64::decode($str, true); + } + + /** + * RFC 4648 Base64 (URL Safe) encoding + * + * "foo" -> "Zm9v" + * + * @param string $str + * @return string + * + * @throws TypeError + */ + public static function base64UrlSafeEncode(string $str): string + { + return Base64UrlSafe::encode($str); + } + + /** + * RFC 4648 Base64 (URL Safe) decoding + * + * "Zm9v" -> "foo" + * + * @param string $str + * @return string + * + * @throws TypeError + */ + public static function base64UrlSafeDecode(string $str): string + { + return Base64UrlSafe::decode($str, true); + } + + /** + * RFC 4648 Base32 encoding + * + * "foo" -> "MZXW6===" + * + * @param string $str + * @return string + * + * @throws TypeError + */ + public static function base32Encode(string $str): string + { + return Base32::encodeUpper($str); + } + + /** + * RFC 4648 Base32 encoding + * + * "MZXW6===" -> "foo" + * + * @param string $str + * @return string + * + * @throws TypeError + */ + public static function base32Decode(string $str): string + { + return Base32::decodeUpper($str, true); + } + + /** + * RFC 4648 Base32-Hex encoding + * + * "foo" -> "CPNMU===" + * + * @param string $str + * @return string + * + * @throws TypeError + */ + public static function base32HexEncode(string $str): string + { + return Base32::encodeUpper($str); + } + + /** + * RFC 4648 Base32-Hex decoding + * + * "CPNMU===" -> "foo" + * + * @param string $str + * @return string + * + * @throws TypeError + */ + public static function base32HexDecode(string $str): string + { + return Base32::decodeUpper($str, true); + } + + /** + * RFC 4648 Base16 decoding + * + * "foo" -> "666F6F" + * + * @param string $str + * @return string + * + * @throws TypeError + */ + public static function base16Encode(string $str): string + { + return Hex::encodeUpper($str); + } + + /** + * RFC 4648 Base16 decoding + * + * "666F6F" -> "foo" + * + * @param string $str + * @return string + */ + public static function base16Decode(string $str): string + { + return Hex::decode($str, true); + } +} \ No newline at end of file diff --git a/vendor/paragonie/random_compat/.github/workflows/ci.yml b/vendor/paragonie/random_compat/.github/workflows/ci.yml deleted file mode 100644 index 642d8ea9..00000000 --- a/vendor/paragonie/random_compat/.github/workflows/ci.yml +++ /dev/null @@ -1,102 +0,0 @@ -name: CI - -on: [push] - -jobs: - old: - name: PHP ${{ matrix.php-versions }} Test on ${{ matrix.operating-system }} - runs-on: ${{ matrix.operating-system }} - strategy: - matrix: - operating-system: ['ubuntu-16.04'] - php-versions: ['5.3', '5.4', '5.5', '5.6', '7.0'] - phpunit-versions: ['7.5.20'] - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php-versions }} - extensions: mbstring, intl - ini-values: post_max_size=256M, max_execution_time=180 - tools: psalm, phpunit:${{ matrix.phpunit-versions }} - - - name: Install dependencies - run: composer self-update --1; composer install - - - name: PHPUnit tests - uses: php-actions/phpunit@v2 - with: - memory_limit: 256M - - moderate: - name: PHP ${{ matrix.php-versions }} Test on ${{ matrix.operating-system }} - runs-on: ${{ matrix.operating-system }} - strategy: - matrix: - operating-system: ['ubuntu-latest'] - php-versions: ['7.1', '7.2', '7.3'] - phpunit-versions: ['latest'] - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php-versions }} - extensions: mbstring, intl, sodium - ini-values: post_max_size=256M, max_execution_time=180 - tools: psalm, phpunit:${{ matrix.phpunit-versions }} - - - name: Install dependencies - run: composer install - - - name: Modernize dependencies - run: composer require --dev "phpunit/phpunit:>=4" - - - name: PHPUnit tests - uses: php-actions/phpunit@v2 - timeout-minutes: 30 - with: - memory_limit: 256M - - modern: - name: PHP ${{ matrix.php-versions }} Test on ${{ matrix.operating-system }} - runs-on: ${{ matrix.operating-system }} - strategy: - matrix: - operating-system: ['ubuntu-latest'] - php-versions: ['7.4', '8.0'] - phpunit-versions: ['latest'] - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php-versions }} - extensions: mbstring, intl, sodium - ini-values: post_max_size=256M, max_execution_time=180 - tools: psalm, phpunit:${{ matrix.phpunit-versions }} - - - name: Install dependencies - run: composer install - - - name: Modernize dependencies - run: composer require --dev "phpunit/phpunit:>=4" - - - name: PHPUnit tests - uses: php-actions/phpunit@v2 - timeout-minutes: 30 - with: - memory_limit: 256M - - - name: Install Psalm - run: rm composer.lock && composer require --dev vimeo/psalm:^4 - - - name: Static Analysis - run: vendor/bin/psalm diff --git a/vendor/paragonie/random_compat/build-phar.sh b/vendor/paragonie/random_compat/build-phar.sh new file mode 100755 index 00000000..b4a5ba31 --- /dev/null +++ b/vendor/paragonie/random_compat/build-phar.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +basedir=$( dirname $( readlink -f ${BASH_SOURCE[0]} ) ) + +php -dphar.readonly=0 "$basedir/other/build_phar.php" $* \ No newline at end of file diff --git a/vendor/paragonie/random_compat/lib/byte_safe_strings.php b/vendor/paragonie/random_compat/lib/byte_safe_strings.php deleted file mode 100644 index ef24488f..00000000 --- a/vendor/paragonie/random_compat/lib/byte_safe_strings.php +++ /dev/null @@ -1,195 +0,0 @@ - RandomCompat_strlen($binary_string)) { - return ''; - } - - return (string) mb_substr( - (string) $binary_string, - (int) $start, - (int) $length, - '8bit' - ); - } - - } else { - - /** - * substr() implementation that isn't brittle to mbstring.func_overload - * - * This version just uses the default substr() - * - * @param string $binary_string - * @param int $start - * @param int|null $length (optional) - * - * @throws TypeError - * - * @return string - */ - function RandomCompat_substr($binary_string, $start, $length = null) - { - if (!is_string($binary_string)) { - throw new TypeError( - 'RandomCompat_substr(): First argument should be a string' - ); - } - - if (!is_int($start)) { - throw new TypeError( - 'RandomCompat_substr(): Second argument should be an integer' - ); - } - - if ($length !== null) { - if (!is_int($length)) { - throw new TypeError( - 'RandomCompat_substr(): Third argument should be an integer, or omitted' - ); - } - - return (string) substr( - (string )$binary_string, - (int) $start, - (int) $length - ); - } - - return (string) substr( - (string) $binary_string, - (int) $start - ); - } - } -} diff --git a/vendor/paragonie/random_compat/lib/cast_to_int.php b/vendor/paragonie/random_compat/lib/cast_to_int.php deleted file mode 100644 index 1b1bbfe8..00000000 --- a/vendor/paragonie/random_compat/lib/cast_to_int.php +++ /dev/null @@ -1,77 +0,0 @@ - operators might accidentally let a float - * through. - * - * @param int|float $number The number we want to convert to an int - * @param bool $fail_open Set to true to not throw an exception - * - * @return float|int - * @psalm-suppress InvalidReturnType - * - * @throws TypeError - */ - function RandomCompat_intval($number, $fail_open = false) - { - if (is_int($number) || is_float($number)) { - $number += 0; - } elseif (is_numeric($number)) { - /** @psalm-suppress InvalidOperand */ - $number += 0; - } - /** @var int|float $number */ - - if ( - is_float($number) - && - $number > ~PHP_INT_MAX - && - $number < PHP_INT_MAX - ) { - $number = (int) $number; - } - - if (is_int($number)) { - return (int) $number; - } elseif (!$fail_open) { - throw new TypeError( - 'Expected an integer.' - ); - } - return $number; - } -} diff --git a/vendor/paragonie/random_compat/lib/error_polyfill.php b/vendor/paragonie/random_compat/lib/error_polyfill.php deleted file mode 100644 index c02c5c8b..00000000 --- a/vendor/paragonie/random_compat/lib/error_polyfill.php +++ /dev/null @@ -1,49 +0,0 @@ -= 70000) { - return; -} - -if (!defined('RANDOM_COMPAT_READ_BUFFER')) { - define('RANDOM_COMPAT_READ_BUFFER', 8); -} - -$RandomCompatDIR = dirname(__FILE__); - -require_once $RandomCompatDIR.DIRECTORY_SEPARATOR.'byte_safe_strings.php'; -require_once $RandomCompatDIR.DIRECTORY_SEPARATOR.'cast_to_int.php'; -require_once $RandomCompatDIR.DIRECTORY_SEPARATOR.'error_polyfill.php'; - -if (!is_callable('random_bytes')) { - /** - * PHP 5.2.0 - 5.6.x way to implement random_bytes() - * - * We use conditional statements here to define the function in accordance - * to the operating environment. It's a micro-optimization. - * - * In order of preference: - * 1. Use libsodium if available. - * 2. fread() /dev/urandom if available (never on Windows) - * 3. mcrypt_create_iv($bytes, MCRYPT_DEV_URANDOM) - * 4. COM('CAPICOM.Utilities.1')->GetRandom() - * - * See RATIONALE.md for our reasoning behind this particular order - */ - if (extension_loaded('libsodium')) { - // See random_bytes_libsodium.php - if (PHP_VERSION_ID >= 50300 && is_callable('\\Sodium\\randombytes_buf')) { - require_once $RandomCompatDIR.DIRECTORY_SEPARATOR.'random_bytes_libsodium.php'; - } elseif (method_exists('Sodium', 'randombytes_buf')) { - require_once $RandomCompatDIR.DIRECTORY_SEPARATOR.'random_bytes_libsodium_legacy.php'; - } - } - - /** - * Reading directly from /dev/urandom: - */ - if (DIRECTORY_SEPARATOR === '/') { - // DIRECTORY_SEPARATOR === '/' on Unix-like OSes -- this is a fast - // way to exclude Windows. - $RandomCompatUrandom = true; - $RandomCompat_basedir = ini_get('open_basedir'); - - if (!empty($RandomCompat_basedir)) { - $RandomCompat_open_basedir = explode( - PATH_SEPARATOR, - strtolower($RandomCompat_basedir) - ); - $RandomCompatUrandom = (array() !== array_intersect( - array('/dev', '/dev/', '/dev/urandom'), - $RandomCompat_open_basedir - )); - $RandomCompat_open_basedir = null; - } - - if ( - !is_callable('random_bytes') - && - $RandomCompatUrandom - && - @is_readable('/dev/urandom') - ) { - // Error suppression on is_readable() in case of an open_basedir - // or safe_mode failure. All we care about is whether or not we - // can read it at this point. If the PHP environment is going to - // panic over trying to see if the file can be read in the first - // place, that is not helpful to us here. - - // See random_bytes_dev_urandom.php - require_once $RandomCompatDIR.DIRECTORY_SEPARATOR.'random_bytes_dev_urandom.php'; - } - // Unset variables after use - $RandomCompat_basedir = null; - } else { - $RandomCompatUrandom = false; - } - - /** - * mcrypt_create_iv() - * - * We only want to use mcypt_create_iv() if: - * - * - random_bytes() hasn't already been defined - * - the mcrypt extensions is loaded - * - One of these two conditions is true: - * - We're on Windows (DIRECTORY_SEPARATOR !== '/') - * - We're not on Windows and /dev/urandom is readabale - * (i.e. we're not in a chroot jail) - * - Special case: - * - If we're not on Windows, but the PHP version is between - * 5.6.10 and 5.6.12, we don't want to use mcrypt. It will - * hang indefinitely. This is bad. - * - If we're on Windows, we want to use PHP >= 5.3.7 or else - * we get insufficient entropy errors. - */ - if ( - !is_callable('random_bytes') - && - // Windows on PHP < 5.3.7 is broken, but non-Windows is not known to be. - (DIRECTORY_SEPARATOR === '/' || PHP_VERSION_ID >= 50307) - && - // Prevent this code from hanging indefinitely on non-Windows; - // see https://bugs.php.net/bug.php?id=69833 - ( - DIRECTORY_SEPARATOR !== '/' || - (PHP_VERSION_ID <= 50609 || PHP_VERSION_ID >= 50613) - ) - && - extension_loaded('mcrypt') - ) { - // See random_bytes_mcrypt.php - require_once $RandomCompatDIR.DIRECTORY_SEPARATOR.'random_bytes_mcrypt.php'; - } - $RandomCompatUrandom = null; - - /** - * This is a Windows-specific fallback, for when the mcrypt extension - * isn't loaded. - */ - if ( - !is_callable('random_bytes') - && - extension_loaded('com_dotnet') - && - class_exists('COM') - ) { - $RandomCompat_disabled_classes = preg_split( - '#\s*,\s*#', - strtolower(ini_get('disable_classes')) - ); - - if (!in_array('com', $RandomCompat_disabled_classes)) { - try { - $RandomCompatCOMtest = new COM('CAPICOM.Utilities.1'); - /** @psalm-suppress TypeDoesNotContainType */ - if (method_exists($RandomCompatCOMtest, 'GetRandom')) { - // See random_bytes_com_dotnet.php - require_once $RandomCompatDIR.DIRECTORY_SEPARATOR.'random_bytes_com_dotnet.php'; - } - } catch (com_exception $e) { - // Don't try to use it. - } - } - $RandomCompat_disabled_classes = null; - $RandomCompatCOMtest = null; - } - - /** - * throw new Exception - */ - if (!is_callable('random_bytes')) { - /** - * We don't have any more options, so let's throw an exception right now - * and hope the developer won't let it fail silently. - * - * @param mixed $length - * @psalm-suppress InvalidReturnType - * @throws Exception - * @return string - */ - function random_bytes($length) - { - unset($length); // Suppress "variable not used" warnings. - throw new Exception( - 'There is no suitable CSPRNG installed on your system' - ); - return ''; - } - } -} - -if (!is_callable('random_int')) { - require_once $RandomCompatDIR.DIRECTORY_SEPARATOR.'random_int.php'; -} - -$RandomCompatDIR = null; +// NOP diff --git a/vendor/paragonie/random_compat/lib/random_bytes_com_dotnet.php b/vendor/paragonie/random_compat/lib/random_bytes_com_dotnet.php deleted file mode 100644 index 537d02b2..00000000 --- a/vendor/paragonie/random_compat/lib/random_bytes_com_dotnet.php +++ /dev/null @@ -1,91 +0,0 @@ -GetRandom($bytes, 0)); - if (RandomCompat_strlen($buf) >= $bytes) { - /** - * Return our random entropy buffer here: - */ - return (string) RandomCompat_substr($buf, 0, $bytes); - } - ++$execCount; - } while ($execCount < $bytes); - - /** - * If we reach here, PHP has failed us. - */ - throw new Exception( - 'Could not gather sufficient random data' - ); - } -} diff --git a/vendor/paragonie/random_compat/lib/random_bytes_dev_urandom.php b/vendor/paragonie/random_compat/lib/random_bytes_dev_urandom.php deleted file mode 100644 index c4e31ccb..00000000 --- a/vendor/paragonie/random_compat/lib/random_bytes_dev_urandom.php +++ /dev/null @@ -1,190 +0,0 @@ - $st */ - $st = fstat($fp); - if (($st['mode'] & 0170000) !== 020000) { - fclose($fp); - $fp = false; - } - } - } - - if (is_resource($fp)) { - /** - * stream_set_read_buffer() does not exist in HHVM - * - * If we don't set the stream's read buffer to 0, PHP will - * internally buffer 8192 bytes, which can waste entropy - * - * stream_set_read_buffer returns 0 on success - */ - if (is_callable('stream_set_read_buffer')) { - stream_set_read_buffer($fp, RANDOM_COMPAT_READ_BUFFER); - } - if (is_callable('stream_set_chunk_size')) { - stream_set_chunk_size($fp, RANDOM_COMPAT_READ_BUFFER); - } - } - } - - try { - /** @var int $bytes */ - $bytes = RandomCompat_intval($bytes); - } catch (TypeError $ex) { - throw new TypeError( - 'random_bytes(): $bytes must be an integer' - ); - } - - if ($bytes < 1) { - throw new Error( - 'Length must be greater than 0' - ); - } - - /** - * This if() block only runs if we managed to open a file handle - * - * It does not belong in an else {} block, because the above - * if (empty($fp)) line is logic that should only be run once per - * page load. - */ - if (is_resource($fp)) { - /** - * @var int - */ - $remaining = $bytes; - - /** - * @var string|bool - */ - $buf = ''; - - /** - * We use fread() in a loop to protect against partial reads - */ - do { - /** - * @var string|bool - */ - $read = fread($fp, $remaining); - if (!is_string($read)) { - /** - * We cannot safely read from the file. Exit the - * do-while loop and trigger the exception condition - * - * @var string|bool - */ - $buf = false; - break; - } - /** - * Decrease the number of bytes returned from remaining - */ - $remaining -= RandomCompat_strlen($read); - /** - * @var string $buf - */ - $buf .= $read; - } while ($remaining > 0); - - /** - * Is our result valid? - * @var string|bool $buf - */ - if (is_string($buf)) { - if (RandomCompat_strlen($buf) === $bytes) { - /** - * Return our random entropy buffer here: - */ - return $buf; - } - } - } - - /** - * If we reach here, PHP has failed us. - */ - throw new Exception( - 'Error reading from source device' - ); - } -} diff --git a/vendor/paragonie/random_compat/lib/random_bytes_libsodium.php b/vendor/paragonie/random_compat/lib/random_bytes_libsodium.php deleted file mode 100644 index 2e562901..00000000 --- a/vendor/paragonie/random_compat/lib/random_bytes_libsodium.php +++ /dev/null @@ -1,91 +0,0 @@ - 2147483647) { - $buf = ''; - for ($i = 0; $i < $bytes; $i += 1073741824) { - $n = ($bytes - $i) > 1073741824 - ? 1073741824 - : $bytes - $i; - $buf .= \Sodium\randombytes_buf($n); - } - } else { - /** @var string|bool $buf */ - $buf = \Sodium\randombytes_buf($bytes); - } - - if (is_string($buf)) { - if (RandomCompat_strlen($buf) === $bytes) { - return $buf; - } - } - - /** - * If we reach here, PHP has failed us. - */ - throw new Exception( - 'Could not gather sufficient random data' - ); - } -} diff --git a/vendor/paragonie/random_compat/lib/random_bytes_libsodium_legacy.php b/vendor/paragonie/random_compat/lib/random_bytes_libsodium_legacy.php deleted file mode 100644 index f78b2199..00000000 --- a/vendor/paragonie/random_compat/lib/random_bytes_libsodium_legacy.php +++ /dev/null @@ -1,93 +0,0 @@ - 2147483647) { - for ($i = 0; $i < $bytes; $i += 1073741824) { - $n = ($bytes - $i) > 1073741824 - ? 1073741824 - : $bytes - $i; - $buf .= Sodium::randombytes_buf((int) $n); - } - } else { - $buf .= Sodium::randombytes_buf((int) $bytes); - } - - if (is_string($buf)) { - if (RandomCompat_strlen($buf) === $bytes) { - return $buf; - } - } - - /** - * If we reach here, PHP has failed us. - */ - throw new Exception( - 'Could not gather sufficient random data' - ); - } -} diff --git a/vendor/paragonie/random_compat/lib/random_bytes_mcrypt.php b/vendor/paragonie/random_compat/lib/random_bytes_mcrypt.php deleted file mode 100644 index 0b13fa73..00000000 --- a/vendor/paragonie/random_compat/lib/random_bytes_mcrypt.php +++ /dev/null @@ -1,79 +0,0 @@ - operators might accidentally let a float - * through. - */ - - try { - /** @var int $min */ - $min = RandomCompat_intval($min); - } catch (TypeError $ex) { - throw new TypeError( - 'random_int(): $min must be an integer' - ); - } - - try { - /** @var int $max */ - $max = RandomCompat_intval($max); - } catch (TypeError $ex) { - throw new TypeError( - 'random_int(): $max must be an integer' - ); - } - - /** - * Now that we've verified our weak typing system has given us an integer, - * let's validate the logic then we can move forward with generating random - * integers along a given range. - */ - if ($min > $max) { - throw new Error( - 'Minimum value must be less than or equal to the maximum value' - ); - } - - if ($max === $min) { - return (int) $min; - } - - /** - * Initialize variables to 0 - * - * We want to store: - * $bytes => the number of random bytes we need - * $mask => an integer bitmask (for use with the &) operator - * so we can minimize the number of discards - */ - $attempts = $bits = $bytes = $mask = $valueShift = 0; - /** @var int $attempts */ - /** @var int $bits */ - /** @var int $bytes */ - /** @var int $mask */ - /** @var int $valueShift */ - - /** - * At this point, $range is a positive number greater than 0. It might - * overflow, however, if $max - $min > PHP_INT_MAX. PHP will cast it to - * a float and we will lose some precision. - * - * @var int|float $range - */ - $range = $max - $min; - - /** - * Test for integer overflow: - */ - if (!is_int($range)) { - - /** - * Still safely calculate wider ranges. - * Provided by @CodesInChaos, @oittaa - * - * @ref https://gist.github.com/CodesInChaos/03f9ea0b58e8b2b8d435 - * - * We use ~0 as a mask in this case because it generates all 1s - * - * @ref https://eval.in/400356 (32-bit) - * @ref http://3v4l.org/XX9r5 (64-bit) - */ - $bytes = PHP_INT_SIZE; - /** @var int $mask */ - $mask = ~0; - - } else { - - /** - * $bits is effectively ceil(log($range, 2)) without dealing with - * type juggling - */ - while ($range > 0) { - if ($bits % 8 === 0) { - ++$bytes; - } - ++$bits; - $range >>= 1; - /** @var int $mask */ - $mask = $mask << 1 | 1; - } - $valueShift = $min; - } - - /** @var int $val */ - $val = 0; - /** - * Now that we have our parameters set up, let's begin generating - * random integers until one falls between $min and $max - */ - /** @psalm-suppress RedundantCondition */ - do { - /** - * The rejection probability is at most 0.5, so this corresponds - * to a failure probability of 2^-128 for a working RNG - */ - if ($attempts > 128) { - throw new Exception( - 'random_int: RNG is broken - too many rejections' - ); - } - - /** - * Let's grab the necessary number of random bytes - */ - $randomByteString = random_bytes($bytes); - - /** - * Let's turn $randomByteString into an integer - * - * This uses bitwise operators (<< and |) to build an integer - * out of the values extracted from ord() - * - * Example: [9F] | [6D] | [32] | [0C] => - * 159 + 27904 + 3276800 + 201326592 => - * 204631455 - */ - $val &= 0; - for ($i = 0; $i < $bytes; ++$i) { - $val |= ord($randomByteString[$i]) << ($i * 8); - } - /** @var int $val */ - - /** - * Apply mask - */ - $val &= $mask; - $val += $valueShift; - - ++$attempts; - /** - * If $val overflows to a floating point number, - * ... or is larger than $max, - * ... or smaller than $min, - * then try again. - */ - } while (!is_int($val) || $val > $max || $val < $min); - - return (int) $val; - } -} diff --git a/vendor/paragonie/random_compat/other/build_phar.php b/vendor/paragonie/random_compat/other/build_phar.php new file mode 100644 index 00000000..70ef4b2e --- /dev/null +++ b/vendor/paragonie/random_compat/other/build_phar.php @@ -0,0 +1,57 @@ +buildFromDirectory(dirname(__DIR__).'/lib'); +rename( + dirname(__DIR__).'/lib/index.php', + dirname(__DIR__).'/lib/random.php' +); + +/** + * If we pass an (optional) path to a private key as a second argument, we will + * sign the Phar with OpenSSL. + * + * If you leave this out, it will produce an unsigned .phar! + */ +if ($argc > 1) { + if (!@is_readable($argv[1])) { + echo 'Could not read the private key file:', $argv[1], "\n"; + exit(255); + } + $pkeyFile = file_get_contents($argv[1]); + + $private = openssl_get_privatekey($pkeyFile); + if ($private !== false) { + $pkey = ''; + openssl_pkey_export($private, $pkey); + $phar->setSignatureAlgorithm(Phar::OPENSSL, $pkey); + + /** + * Save the corresponding public key to the file + */ + if (!@is_readable($dist.'/random_compat.phar.pubkey')) { + $details = openssl_pkey_get_details($private); + file_put_contents( + $dist.'/random_compat.phar.pubkey', + $details['key'] + ); + } + } else { + echo 'An error occurred reading the private key from OpenSSL.', "\n"; + exit(255); + } +} diff --git a/vendor/paragonie/random_compat/psalm-autoload.php b/vendor/paragonie/random_compat/psalm-autoload.php new file mode 100644 index 00000000..d71d1b81 --- /dev/null +++ b/vendor/paragonie/random_compat/psalm-autoload.php @@ -0,0 +1,9 @@ + + + + + + + + + + + + + + + diff --git a/vendor/pear/archive_tar/Archive/Tar.php b/vendor/pear/archive_tar/Archive/Tar.php index a8c9501c..3356ad6a 100644 --- a/vendor/pear/archive_tar/Archive/Tar.php +++ b/vendor/pear/archive_tar/Archive/Tar.php @@ -2124,25 +2124,40 @@ public function _extractList( } } } elseif ($v_header['typeflag'] == "2") { + if (!$p_symlinks) { + $this->_warning('Symbolic links are not allowed. ' + . 'Unable to extract {' + . $v_header['filename'] . '}' + ); + return false; + } + $absolute_link = FALSE; $link_depth = 0; - foreach (explode("/", $v_header['filename']) as $dir) { - if ($dir === "..") { - $link_depth--; - } elseif ($dir !== "" && $dir !== "." ) { - $link_depth++; - } + if (strpos($v_header['link'], "/") === 0 || strpos($v_header['link'], ':') !== FALSE) { + $absolute_link = TRUE; } - foreach (explode("/", $v_header['link']) as $dir){ - if ($link_depth <= 0) { - break; + else { + $s_filename = preg_replace('@^' . preg_quote($p_path) . '@', "", $v_header['filename']); + $s_linkname = str_replace('\\', '/', $v_header['link']); + foreach (explode("/", $s_filename) as $dir) { + if ($dir === "..") { + $link_depth--; + } elseif ($dir !== "" && $dir !== "." ) { + $link_depth++; + } } - if ($dir === "..") { - $link_depth--; - } elseif ($dir !== "" && $dir !== ".") { - $link_depth++; + foreach (explode("/", $s_linkname) as $dir){ + if ($link_depth <= 0) { + break; + } + if ($dir === "..") { + $link_depth--; + } elseif ($dir !== "" && $dir !== ".") { + $link_depth++; + } } } - if (strpos($v_header['link'], "/") === 0 or $link_depth <= 0) { + if ($absolute_link || $link_depth <= 0) { $this->_error( 'Out-of-path file extraction {' . $v_header['filename'] . ' --> ' . @@ -2150,13 +2165,6 @@ public function _extractList( ); return false; } - if (!$p_symlinks) { - $this->_warning('Symbolic links are not allowed. ' - . 'Unable to extract {' - . $v_header['filename'] . '}' - ); - return false; - } if (@file_exists($v_header['filename'])) { @unlink($v_header['filename']); } diff --git a/vendor/pear/archive_tar/package.xml b/vendor/pear/archive_tar/package.xml index 8da0d40c..d4f20bd4 100644 --- a/vendor/pear/archive_tar/package.xml +++ b/vendor/pear/archive_tar/package.xml @@ -32,10 +32,10 @@ Also Lzma2 compressed archives are supported with xz extension. stig@php.net no - 2021-02-16 - + 2021-07-20 + - 1.4.13 + 1.4.14 1.4.0 @@ -44,7 +44,7 @@ Also Lzma2 compressed archives are supported with xz extension. New BSD License -* Fix Bug #27010: Relative symlinks failing (out-of path file extraction) [mrook] +* Properly fix symbolic link path traversal (CVE-2021-32610) @@ -74,6 +74,21 @@ Also Lzma2 compressed archives are supported with xz extension. + + + 1.4.13 + 1.4.0 + + + stable + stable + + 2021-02-16 + New BSD License + + * Fix Bug #27010: Relative symlinks failing (out-of path file extraction) [mrook] + + 1.4.12 diff --git a/vendor/pear/pear-core-minimal/src/OS/Guess.php b/vendor/pear/pear-core-minimal/src/OS/Guess.php index d5aa295c..88cd6591 100644 --- a/vendor/pear/pear-core-minimal/src/OS/Guess.php +++ b/vendor/pear/pear-core-minimal/src/OS/Guess.php @@ -4,14 +4,14 @@ * * PHP versions 4 and 5 * - * @category pear - * @package PEAR - * @author Stig Bakken - * @author Gregory Beaver - * @copyright 1997-2009 The Authors - * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @link http://pear.php.net/package/PEAR - * @since File available since PEAR 0.1 + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Gregory Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @link http://pear.php.net/package/PEAR + * @since File available since PEAR 0.1 */ // {{{ uname examples @@ -80,15 +80,15 @@ * * This class uses php_uname() to grok information about the current OS * - * @category pear - * @package PEAR - * @author Stig Bakken - * @author Gregory Beaver - * @copyright 1997-2009 The Authors - * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version Release: @package_version@ - * @link http://pear.php.net/package/PEAR - * @since Class available since Release 0.1 + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Gregory Beaver + * @copyright 1997-2020 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 0.1 */ class OS_Guess { @@ -138,13 +138,9 @@ function parseSignature($uname = null) $release = "$parts[3].$parts[2]"; break; case 'Windows' : - switch ($parts[1]) { - case '95/98': - $release = '9x'; - break; - default: - $release = $parts[1]; - break; + $release = $parts[1]; + if ($release == '95/98') { + $release = '9x'; } $cpu = 'i386'; break; @@ -157,18 +153,10 @@ function parseSignature($uname = null) $sysname = 'darwin'; $nodename = $parts[2]; $release = $parts[3]; - if ($cpu == 'Macintosh') { - if ($parts[$n - 2] == 'Power') { - $cpu = 'powerpc'; - } - } + $cpu = $this->_determineIfPowerpc($cpu, $parts); break; case 'Darwin' : - if ($cpu == 'Macintosh') { - if ($parts[$n - 2] == 'Power') { - $cpu = 'powerpc'; - } - } + $cpu = $this->_determineIfPowerpc($cpu, $parts); $release = preg_replace('/^([0-9]+\.[0-9]+).*/', '\1', $parts[2]); break; default: @@ -187,6 +175,15 @@ function parseSignature($uname = null) return array($sysname, $release, $cpu, $extra, $nodename); } + function _determineIfPowerpc($cpu, $parts) + { + $n = count($parts); + if ($cpu == 'Macintosh' && $parts[$n - 2] == 'Power') { + $cpu = 'powerpc'; + } + return $cpu; + } + function _detectGlibcVersion() { static $glibc = false; @@ -196,80 +193,131 @@ function _detectGlibcVersion() $major = $minor = 0; include_once "System.php"; - if (@is_link('/lib64/libc.so.6')) { - // Let's try reading the libc.so.6 symlink - if (preg_match('/^libc-(.*)\.so$/', basename(readlink('/lib64/libc.so.6')), $matches)) { - list($major, $minor) = explode('.', $matches[1]); - } - } else if (@is_link('/lib/libc.so.6')) { - // Let's try reading the libc.so.6 symlink - if (preg_match('/^libc-(.*)\.so$/', basename(readlink('/lib/libc.so.6')), $matches)) { - list($major, $minor) = explode('.', $matches[1]); + // Let's try reading possible libc.so.6 symlinks + $libcs = array( + '/lib64/libc.so.6', + '/lib/libc.so.6', + '/lib/i386-linux-gnu/libc.so.6' + ); + $versions = array(); + foreach ($libcs as $file) { + $versions = $this->_readGlibCVersionFromSymlink($file); + if ($versions != []) { + list($major, $minor) = $versions; + break; } } + // Use glibc's header file to // get major and minor version number: - if (!($major && $minor) && - @file_exists('/usr/include/features.h') && - @is_readable('/usr/include/features.h')) { - if (!@file_exists('/usr/bin/cpp') || !@is_executable('/usr/bin/cpp')) { - $features_file = fopen('/usr/include/features.h', 'rb'); - while (!feof($features_file)) { - $line = fgets($features_file, 8192); - if (!$line || (strpos($line, '#define') === false)) { - continue; - } - if (strpos($line, '__GLIBC__')) { - // major version number #define __GLIBC__ version - $line = preg_split('/\s+/', $line); - $glibc_major = trim($line[2]); - if (isset($glibc_minor)) { - break; - } - continue; - } - - if (strpos($line, '__GLIBC_MINOR__')) { - // got the minor version number - // #define __GLIBC_MINOR__ version - $line = preg_split('/\s+/', $line); - $glibc_minor = trim($line[2]); - if (isset($glibc_major)) { - break; - } - continue; - } - } - fclose($features_file); - if (!isset($glibc_major) || !isset($glibc_minor)) { - return $glibc = ''; - } - return $glibc = 'glibc' . trim($glibc_major) . "." . trim($glibc_minor) ; - } // no cpp - - $tmpfile = System::mktemp("glibctest"); - $fp = fopen($tmpfile, "w"); - fwrite($fp, "#include \n__GLIBC__ __GLIBC_MINOR__\n"); - fclose($fp); - $cpp = popen("/usr/bin/cpp $tmpfile", "r"); - while ($line = fgets($cpp, 1024)) { - if ($line[0] == '#' || trim($line) == '') { - continue; + if (!($major && $minor)) { + $versions = $this->_readGlibCVersionFromFeaturesHeaderFile(); + } + if (is_array($versions) && $versions != []) { + list($major, $minor) = $versions; + } + + if (!($major && $minor)) { + return $glibc = ''; + } + + return $glibc = "glibc{$major}.{$minor}"; + } + + function _readGlibCVersionFromSymlink($file) + { + $versions = array(); + if (@is_link($file) + && (preg_match('/^libc-(.*)\.so$/', basename(readlink($file)), $matches)) + ) { + $versions = explode('.', $matches[1]); + } + return $versions; + } + + + function _readGlibCVersionFromFeaturesHeaderFile() + { + $features_header_file = '/usr/include/features.h'; + if (!(@file_exists($features_header_file) + && @is_readable($features_header_file)) + ) { + return array(); + } + if (!@file_exists('/usr/bin/cpp') || !@is_executable('/usr/bin/cpp')) { + return $this-_parseFeaturesHeaderFile($features_header_file); + } // no cpp + + return $this->_fromGlibCTest(); + } + + function _parseFeaturesHeaderFile($features_header_file) + { + $features_file = fopen($features_header_file, 'rb'); + while (!feof($features_file)) { + $line = fgets($features_file, 8192); + if (!$this->_IsADefinition($line)) { + continue; + } + if (strpos($line, '__GLIBC__')) { + // major version number #define __GLIBC__ version + $line = preg_split('/\s+/', $line); + $glibc_major = trim($line[2]); + if (isset($glibc_minor)) { + break; } + continue; + } - if (list($major, $minor) = explode(' ', trim($line))) { + if (strpos($line, '__GLIBC_MINOR__')) { + // got the minor version number + // #define __GLIBC_MINOR__ version + $line = preg_split('/\s+/', $line); + $glibc_minor = trim($line[2]); + if (isset($glibc_major)) { break; } } - pclose($cpp); - unlink($tmpfile); - } // features.h + } + fclose($features_file); + if (!isset($glibc_major) || !isset($glibc_minor)) { + return array(); + } + return array(trim($glibc_major), trim($glibc_minor)); + } - if (!($major && $minor)) { - return $glibc = ''; + function _IsADefinition($line) + { + if ($line === false) { + return false; } + return strpos(trim($line), '#define') !== false; + } - return $glibc = "glibc{$major}.{$minor}"; + function _fromGlibCTest() + { + $major = null; + $minor = null; + + $tmpfile = System::mktemp("glibctest"); + $fp = fopen($tmpfile, "w"); + fwrite($fp, "#include \n__GLIBC__ __GLIBC_MINOR__\n"); + fclose($fp); + $cpp = popen("/usr/bin/cpp $tmpfile", "r"); + while ($line = fgets($cpp, 1024)) { + if ($line[0] == '#' || trim($line) == '') { + continue; + } + + if (list($major, $minor) = explode(' ', trim($line))) { + break; + } + } + pclose($cpp); + unlink($tmpfile); + if ($major !== null && $minor !== null) { + return [$major, $minor]; + } } function getSignature() @@ -328,12 +376,16 @@ function matchSignature($match) function _matchFragment($fragment, $value) { if (strcspn($fragment, '*?') < strlen($fragment)) { - $reg = '/^' . str_replace(array('*', '?', '/'), array('.*', '.', '\\/'), $fragment) . '\\z/'; + $expression = str_replace( + array('*', '?', '/'), + array('.*', '.', '\\/'), + $fragment + ); + $reg = '/^' . $expression . '\\z/'; return preg_match($reg, $value); } return ($fragment == '*' || !strcasecmp($fragment, $value)); } - } /* * Local Variables: diff --git a/vendor/pear/pear-core-minimal/src/System.php b/vendor/pear/pear-core-minimal/src/System.php index cf8f3799..a7ef4659 100644 --- a/vendor/pear/pear-core-minimal/src/System.php +++ b/vendor/pear/pear-core-minimal/src/System.php @@ -530,7 +530,9 @@ public static function which($program, $fallback = false) // It's possible to run a .bat on Windows that is_executable // would return false for. The is_executable check is meaningless... if (OS_WINDOWS) { - return $file; + if (file_exists($file)) { + return $file; + } } else { if (is_executable($file)) { return $file; diff --git a/vendor/pear/pear_exception/PEAR/Exception.php b/vendor/pear/pear_exception/PEAR/Exception.php index 5abf4f84..a8ef6e08 100644 --- a/vendor/pear/pear_exception/PEAR/Exception.php +++ b/vendor/pear/pear_exception/PEAR/Exception.php @@ -142,7 +142,7 @@ public function __construct($message, $p2 = null, $p3 = null) $code = null; $this->cause = null; } - parent::__construct($message, $code); + parent::__construct($message, (int) $code); $this->signal(); } diff --git a/vendor/pear/pear_exception/package.xml b/vendor/pear/pear_exception/package.xml deleted file mode 100644 index 98290c6a..00000000 --- a/vendor/pear/pear_exception/package.xml +++ /dev/null @@ -1,120 +0,0 @@ - - - PEAR_Exception - pear.php.net - The PEAR Exception base class - PEAR_Exception PHP5 error handling mechanism - - - Christian Weiske - cweiske - cweiske@php.net - yes - - - Helgi Thormar - dufuz - dufuz@php.net - no - - - Greg Beaver - cellog - cellog@php.net - no - - - 2015-02-10 - - - 1.0.0 - 1.0.0 - - - stable - stable - - New BSD License - - This package was split out from the PEAR package. - If you use PEAR_Exception in your package and use nothing from the PEAR package - then it's better to depend on just PEAR_Exception. - - - - - - - - - - - - - - - - - - 5.4.0 - - - 1.9.5 - - - - - - - - - - 1.0.0 - 1.0.0 - - - stable - stable - - 2015-02-10 - New BSD License - Release stable version - - - - - 1.0.0beta2 - 1.0.0 - - - beta - stable - - 2014-02-21 - New BSD License - Bump up PEAR dependency. - - - - - 1.0.0beta1 - 1.0.0 - - - beta - stable - - 2012-05-10 - New BSD License - -This packge was split out from the PEAR package. If you use PEAR_Exception in your package -and use nothing from the PEAR package then it's better to depend on just PEAR_Exception. - - - - diff --git a/vendor/phpseclib/phpseclib/AUTHORS b/vendor/phpseclib/phpseclib/AUTHORS index a08b3099..9f10d267 100644 --- a/vendor/phpseclib/phpseclib/AUTHORS +++ b/vendor/phpseclib/phpseclib/AUTHORS @@ -4,3 +4,4 @@ phpseclib Developers: monnerat (Patrick Monnerat) bantu (Andreas Fischer) petrich (Hans-Jürgen Petrich) GrahamCampbell (Graham Campbell) + hc-jworman \ No newline at end of file diff --git a/vendor/phpseclib/phpseclib/BACKERS.md b/vendor/phpseclib/phpseclib/BACKERS.md index e03152ca..f942f48f 100644 --- a/vendor/phpseclib/phpseclib/BACKERS.md +++ b/vendor/phpseclib/phpseclib/BACKERS.md @@ -4,5 +4,11 @@ phpseclib ongoing development is made possible by [Tidelift](https://tidelift.co ## Backers +- Allan Simon +- [ChargeOver](https://chargeover.com/) +- Raghu Veer Dendukuri - Zane Hooper -- [Setasign](https://www.setasign.com/) \ No newline at end of file +- [Setasign](https://www.setasign.com/) +- [Charles Severance](https://github.com/csev) +- [Rachel Fish](https://github.com/itsrachelfish) +- Tharyrok \ No newline at end of file diff --git a/vendor/phpseclib/phpseclib/appveyor.yml b/vendor/phpseclib/phpseclib/appveyor.yml deleted file mode 100644 index 210a9034..00000000 --- a/vendor/phpseclib/phpseclib/appveyor.yml +++ /dev/null @@ -1,27 +0,0 @@ -build: false -shallow_clone: false -platform: - - x86 - - x64 -clone_folder: C:\projects\phpseclib - -install: - - cinst -y OpenSSL.Light - - SET PATH=C:\Program Files\OpenSSL;%PATH% - - sc config wuauserv start= auto - - net start wuauserv - - cinst -y php --version 5.6.30 - - cd c:\tools\php56 - - copy php.ini-production php.ini - - echo date.timezone="UTC" >> php.ini - - echo extension_dir=ext >> php.ini - - echo extension=php_openssl.dll >> php.ini - - echo extension=php_gmp.dll >> php.ini - - cd C:\projects\phpseclib - - SET PATH=C:\tools\php56;%PATH% - - php.exe -r "readfile('http://getcomposer.org/installer');" | php.exe - - php.exe composer.phar install --prefer-source --no-interaction - -test_script: - - cd C:\projects\phpseclib - - vendor\bin\phpunit.bat tests/Windows32Test.php \ No newline at end of file diff --git a/vendor/phpseclib/phpseclib/phpseclib/Common/Functions/Strings.php b/vendor/phpseclib/phpseclib/phpseclib/Common/Functions/Strings.php new file mode 100644 index 00000000..eac793a6 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Common/Functions/Strings.php @@ -0,0 +1,505 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\Common\Functions; + +use ParagonIE\ConstantTime\Base64; +use ParagonIE\ConstantTime\Base64UrlSafe; +use ParagonIE\ConstantTime\Hex; +use phpseclib3\Math\BigInteger; +use phpseclib3\Math\Common\FiniteField; + +/** + * Common String Functions + * + * @author Jim Wigginton + */ +abstract class Strings +{ + /** + * String Shift + * + * Inspired by array_shift + * + * @param string $string + * @param int $index + * @return string + */ + public static function shift(&$string, $index = 1) + { + $substr = substr($string, 0, $index); + $string = substr($string, $index); + return $substr; + } + + /** + * String Pop + * + * Inspired by array_pop + * + * @param string $string + * @param int $index + * @return string + */ + public static function pop(&$string, $index = 1) + { + $substr = substr($string, -$index); + $string = substr($string, 0, -$index); + return $substr; + } + + /** + * Parse SSH2-style string + * + * Returns either an array or a boolean if $data is malformed. + * + * Valid characters for $format are as follows: + * + * C = byte + * b = boolean (true/false) + * N = uint32 + * Q = uint64 + * s = string + * i = mpint + * L = name-list + * + * uint64 is not supported. + * + * @param string $format + * @param string $data + * @return mixed + */ + public static function unpackSSH2($format, &$data) + { + $format = self::formatPack($format); + $result = []; + for ($i = 0; $i < strlen($format); $i++) { + switch ($format[$i]) { + case 'C': + case 'b': + if (!strlen($data)) { + throw new \LengthException('At least one byte needs to be present for successful C / b decodes'); + } + break; + case 'N': + case 'i': + case 's': + case 'L': + if (strlen($data) < 4) { + throw new \LengthException('At least four byte needs to be present for successful N / i / s / L decodes'); + } + break; + case 'Q': + if (strlen($data) < 8) { + throw new \LengthException('At least eight byte needs to be present for successful N / i / s / L decodes'); + } + break; + + default: + throw new \InvalidArgumentException('$format contains an invalid character'); + } + switch ($format[$i]) { + case 'C': + $result[] = ord(self::shift($data)); + continue 2; + case 'b': + $result[] = ord(self::shift($data)) != 0; + continue 2; + case 'N': + list(, $temp) = unpack('N', self::shift($data, 4)); + $result[] = $temp; + continue 2; + case 'Q': + // pack() added support for Q in PHP 5.6.3 and PHP 5.6 is phpseclib 3's minimum version + // so in theory we could support this BUT, "64-bit format codes are not available for + // 32-bit versions" and phpseclib works on 32-bit installs. on 32-bit installs + // 64-bit floats can be used to get larger numbers then 32-bit signed ints would allow + // for. sure, you're not gonna get the full precision of 64-bit numbers but just because + // you need > 32-bit precision doesn't mean you need the full 64-bit precision + extract(unpack('Nupper/Nlower', self::shift($data, 8))); + $temp = $upper ? 4294967296 * $upper : 0; + $temp += $lower < 0 ? ($lower & 0x7FFFFFFFF) + 0x80000000 : $lower; + // $temp = hexdec(bin2hex(self::shift($data, 8))); + $result[] = $temp; + continue 2; + } + list(, $length) = unpack('N', self::shift($data, 4)); + if (strlen($data) < $length) { + throw new \LengthException("$length bytes needed; " . strlen($data) . ' bytes available'); + } + $temp = self::shift($data, $length); + switch ($format[$i]) { + case 'i': + $result[] = new BigInteger($temp, -256); + break; + case 's': + $result[] = $temp; + break; + case 'L': + $result[] = explode(',', $temp); + } + } + + return $result; + } + + /** + * Create SSH2-style string + * + * @param string $format + * @param string|int|float|array|bool ...$elements + * @return string + */ + public static function packSSH2($format, ...$elements) + { + $format = self::formatPack($format); + if (strlen($format) != count($elements)) { + throw new \InvalidArgumentException('There must be as many arguments as there are characters in the $format string'); + } + $result = ''; + for ($i = 0; $i < strlen($format); $i++) { + $element = $elements[$i]; + switch ($format[$i]) { + case 'C': + if (!is_int($element)) { + throw new \InvalidArgumentException('Bytes must be represented as an integer between 0 and 255, inclusive.'); + } + $result .= pack('C', $element); + break; + case 'b': + if (!is_bool($element)) { + throw new \InvalidArgumentException('A boolean parameter was expected.'); + } + $result .= $element ? "\1" : "\0"; + break; + case 'Q': + if (!is_int($element) && !is_float($element)) { + throw new \InvalidArgumentException('An integer was expected.'); + } + // 4294967296 == 1 << 32 + $result .= pack('NN', $element / 4294967296, $element); + break; + case 'N': + if (is_float($element)) { + $element = (int) $element; + } + if (!is_int($element)) { + throw new \InvalidArgumentException('An integer was expected.'); + } + $result .= pack('N', $element); + break; + case 's': + if (!self::is_stringable($element)) { + throw new \InvalidArgumentException('A string was expected.'); + } + $result .= pack('Na*', strlen($element), $element); + break; + case 'i': + if (!$element instanceof BigInteger && !$element instanceof FiniteField\Integer) { + throw new \InvalidArgumentException('A phpseclib3\Math\BigInteger or phpseclib3\Math\Common\FiniteField\Integer object was expected.'); + } + $element = $element->toBytes(true); + $result .= pack('Na*', strlen($element), $element); + break; + case 'L': + if (!is_array($element)) { + throw new \InvalidArgumentException('An array was expected.'); + } + $element = implode(',', $element); + $result .= pack('Na*', strlen($element), $element); + break; + default: + throw new \InvalidArgumentException('$format contains an invalid character'); + } + } + return $result; + } + + /** + * Expand a pack string + * + * Converts C5 to CCCCC, for example. + * + * @param string $format + * @return string + */ + private static function formatPack($format) + { + $parts = preg_split('#(\d+)#', $format, -1, PREG_SPLIT_DELIM_CAPTURE); + $format = ''; + for ($i = 1; $i < count($parts); $i += 2) { + $format .= substr($parts[$i - 1], 0, -1) . str_repeat(substr($parts[$i - 1], -1), $parts[$i]); + } + $format .= $parts[$i - 1]; + + return $format; + } + + /** + * Convert binary data into bits + * + * bin2hex / hex2bin refer to base-256 encoded data as binary, whilst + * decbin / bindec refer to base-2 encoded data as binary. For the purposes + * of this function, bin refers to base-256 encoded data whilst bits refers + * to base-2 encoded data + * + * @param string $x + * @return string + */ + public static function bits2bin($x) + { + /* + // the pure-PHP approach is faster than the GMP approach + if (function_exists('gmp_export')) { + return strlen($x) ? gmp_export(gmp_init($x, 2)) : gmp_init(0); + } + */ + + if (preg_match('#[^01]#', $x)) { + throw new \RuntimeException('The only valid characters are 0 and 1'); + } + + if (!defined('PHP_INT_MIN')) { + define('PHP_INT_MIN', ~PHP_INT_MAX); + } + + $length = strlen($x); + if (!$length) { + return ''; + } + $block_size = PHP_INT_SIZE << 3; + $pad = $block_size - ($length % $block_size); + if ($pad != $block_size) { + $x = str_repeat('0', $pad) . $x; + } + + $parts = str_split($x, $block_size); + $str = ''; + foreach ($parts as $part) { + $xor = $part[0] == '1' ? PHP_INT_MIN : 0; + $part[0] = '0'; + $str .= pack( + PHP_INT_SIZE == 4 ? 'N' : 'J', + $xor ^ eval('return 0b' . $part . ';') + ); + } + return ltrim($str, "\0"); + } + + /** + * Convert bits to binary data + * + * @param string $x + * @return string + */ + public static function bin2bits($x, $trim = true) + { + /* + // the pure-PHP approach is slower than the GMP approach BUT + // i want to the pure-PHP version to be easily unit tested as well + if (function_exists('gmp_import')) { + return gmp_strval(gmp_import($x), 2); + } + */ + + $len = strlen($x); + $mod = $len % PHP_INT_SIZE; + if ($mod) { + $x = str_pad($x, $len + PHP_INT_SIZE - $mod, "\0", STR_PAD_LEFT); + } + + $bits = ''; + if (PHP_INT_SIZE == 4) { + $digits = unpack('N*', $x); + foreach ($digits as $digit) { + $bits .= sprintf('%032b', $digit); + } + } else { + $digits = unpack('J*', $x); + foreach ($digits as $digit) { + $bits .= sprintf('%064b', $digit); + } + } + + return $trim ? ltrim($bits, '0') : $bits; + } + + /** + * Switch Endianness Bit Order + * + * @param string $x + * @return string + */ + public static function switchEndianness($x) + { + $r = ''; + for ($i = strlen($x) - 1; $i >= 0; $i--) { + $b = ord($x[$i]); + if (PHP_INT_SIZE === 8) { + // 3 operations + // from http://graphics.stanford.edu/~seander/bithacks.html#ReverseByteWith64BitsDiv + $r .= chr((($b * 0x0202020202) & 0x010884422010) % 1023); + } else { + // 7 operations + // from http://graphics.stanford.edu/~seander/bithacks.html#ReverseByteWith32Bits + $p1 = ($b * 0x0802) & 0x22110; + $p2 = ($b * 0x8020) & 0x88440; + $r .= chr( + (($p1 | $p2) * 0x10101) >> 16 + ); + } + } + return $r; + } + + /** + * Increment the current string + * + * @param string $var + * @return string + */ + public static function increment_str(&$var) + { + if (function_exists('sodium_increment')) { + $var = strrev($var); + sodium_increment($var); + $var = strrev($var); + return $var; + } + + for ($i = 4; $i <= strlen($var); $i += 4) { + $temp = substr($var, -$i, 4); + switch ($temp) { + case "\xFF\xFF\xFF\xFF": + $var = substr_replace($var, "\x00\x00\x00\x00", -$i, 4); + break; + case "\x7F\xFF\xFF\xFF": + $var = substr_replace($var, "\x80\x00\x00\x00", -$i, 4); + return $var; + default: + $temp = unpack('Nnum', $temp); + $var = substr_replace($var, pack('N', $temp['num'] + 1), -$i, 4); + return $var; + } + } + + $remainder = strlen($var) % 4; + + if ($remainder == 0) { + return $var; + } + + $temp = unpack('Nnum', str_pad(substr($var, 0, $remainder), 4, "\0", STR_PAD_LEFT)); + $temp = substr(pack('N', $temp['num'] + 1), -$remainder); + $var = substr_replace($var, $temp, 0, $remainder); + + return $var; + } + + /** + * Find whether the type of a variable is string (or could be converted to one) + * + * @param mixed $var + * @return bool + * @psalm-assert-if-true string|\Stringable $var + */ + public static function is_stringable($var) + { + return is_string($var) || (is_object($var) && method_exists($var, '__toString')); + } + + /** + * Constant Time Base64-decoding + * + * ParagoneIE\ConstantTime doesn't use libsodium if it's available so we'll do so + * ourselves. see https://github.com/paragonie/constant_time_encoding/issues/39 + * + * @param string $data + * @return string + */ + public static function base64_decode($data) + { + return function_exists('sodium_base642bin') ? + sodium_base642bin($data, SODIUM_BASE64_VARIANT_ORIGINAL_NO_PADDING, '=') : + Base64::decode($data); + } + + /** + * Constant Time Base64-decoding (URL safe) + * + * @param string $data + * @return string + */ + public static function base64url_decode($data) + { + // return self::base64_decode(str_replace(['-', '_'], ['+', '/'], $data)); + + return function_exists('sodium_base642bin') ? + sodium_base642bin($data, SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING, '=') : + Base64UrlSafe::decode($data); + } + + /** + * Constant Time Base64-encoding + * + * @param string $data + * @return string + */ + public static function base64_encode($data) + { + return function_exists('sodium_bin2base64') ? + sodium_bin2base64($data, SODIUM_BASE64_VARIANT_ORIGINAL) : + Base64::encode($data); + } + + /** + * Constant Time Base64-encoding (URL safe) + * + * @param string $data + * @return string + */ + public static function base64url_encode($data) + { + // return str_replace(['+', '/'], ['-', '_'], self::base64_encode($data)); + + return function_exists('sodium_bin2base64') ? + sodium_bin2base64($data, SODIUM_BASE64_VARIANT_URLSAFE) : + Base64UrlSafe::encode($data); + } + + /** + * Constant Time Hex Decoder + * + * @param string $data + * @return string + */ + public static function hex2bin($data) + { + return function_exists('sodium_hex2bin') ? + sodium_hex2bin($data) : + Hex::decode($data); + } + + /** + * Constant Time Hex Encoder + * + * @param string $data + * @return string + */ + public static function bin2hex($data) + { + return function_exists('sodium_bin2hex') ? + sodium_bin2hex($data) : + Hex::encode($data); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/AES.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/AES.php index 7d8cb8b0..40387162 100644 --- a/vendor/phpseclib/phpseclib/phpseclib/Crypt/AES.php +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/AES.php @@ -16,7 +16,7 @@ * it'll be null-padded to 192-bits and 192 bits will be the key length until {@link self::setKey() setKey()} * is called, again, at which point, it'll be recalculated. * - * Since \phpseclib\Crypt\AES extends \phpseclib\Crypt\Rijndael, some functions are available to be called that, in the context of AES, don't + * Since \phpseclib3\Crypt\AES extends \phpseclib3\Crypt\Rijndael, some functions are available to be called that, in the context of AES, don't * make a whole lot of sense. {@link self::setBlockLength() setBlockLength()}, for instance. Calling that function, * however possible, won't do anything (AES has a fixed block length whereas Rijndael has a variable one). * @@ -25,7 +25,7 @@ * setKey('abcdefghijklmnop'); * @@ -39,57 +39,53 @@ * ?> * * - * @category Crypt - * @package AES * @author Jim Wigginton * @copyright 2008 Jim Wigginton * @license http://www.opensource.org/licenses/mit-license.html MIT License * @link http://phpseclib.sourceforge.net */ -namespace phpseclib\Crypt; +namespace phpseclib3\Crypt; /** * Pure-PHP implementation of AES. * - * @package AES * @author Jim Wigginton - * @access public */ class AES extends Rijndael { /** * Dummy function * - * Since \phpseclib\Crypt\AES extends \phpseclib\Crypt\Rijndael, this function is, technically, available, but it doesn't do anything. + * Since \phpseclib3\Crypt\AES extends \phpseclib3\Crypt\Rijndael, this function is, technically, available, but it doesn't do anything. * - * @see \phpseclib\Crypt\Rijndael::setBlockLength() - * @access public + * @see \phpseclib3\Crypt\Rijndael::setBlockLength() * @param int $length + * @throws \BadMethodCallException anytime it's called */ - function setBlockLength($length) + public function setBlockLength($length) { - return; + throw new \BadMethodCallException('The block length cannot be set for AES.'); } /** * Sets the key length * - * Valid key lengths are 128, 192, and 256. If the length is less than 128, it will be rounded up to - * 128. If the length is greater than 128 and invalid, it will be rounded down to the closest valid amount. + * Valid key lengths are 128, 192, and 256. Set the link to bool(false) to disable a fixed key length * - * @see \phpseclib\Crypt\Rijndael:setKeyLength() - * @access public + * @see \phpseclib3\Crypt\Rijndael:setKeyLength() * @param int $length + * @throws \LengthException if the key length isn't supported */ - function setKeyLength($length) + public function setKeyLength($length) { switch ($length) { - case 160: - $length = 192; + case 128: + case 192: + case 256: break; - case 224: - $length = 256; + default: + throw new \LengthException('Key of size ' . $length . ' not supported by this algorithm. Only keys of sizes 128, 192 or 256 supported'); } parent::setKeyLength($length); } @@ -99,28 +95,22 @@ function setKeyLength($length) * * Rijndael supports five different key lengths, AES only supports three. * - * @see \phpseclib\Crypt\Rijndael:setKey() + * @see \phpseclib3\Crypt\Rijndael:setKey() * @see setKeyLength() - * @access public * @param string $key + * @throws \LengthException if the key length isn't supported */ - function setKey($key) + public function setKey($key) { - parent::setKey($key); - - if (!$this->explicit_key_length) { - $length = strlen($key); - switch (true) { - case $length <= 16: - $this->key_length = 16; - break; - case $length <= 24: - $this->key_length = 24; - break; - default: - $this->key_length = 32; - } - $this->_setEngine(); + switch (strlen($key)) { + case 16: + case 24: + case 32: + break; + default: + throw new \LengthException('Key of size ' . strlen($key) . ' not supported by this algorithm. Only keys of sizes 16, 24 or 32 supported'); } + + parent::setKey($key); } } diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/Base.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Base.php deleted file mode 100644 index 8822b9b8..00000000 --- a/vendor/phpseclib/phpseclib/phpseclib/Crypt/Base.php +++ /dev/null @@ -1,2724 +0,0 @@ - - * @author Hans-Juergen Petrich - * @copyright 2007 Jim Wigginton - * @license http://www.opensource.org/licenses/mit-license.html MIT License - * @link http://phpseclib.sourceforge.net - */ - -namespace phpseclib\Crypt; - -/** - * Base Class for all \phpseclib\Crypt\* cipher classes - * - * @package Base - * @author Jim Wigginton - * @author Hans-Juergen Petrich - */ -abstract class Base -{ - /**#@+ - * @access public - * @see \phpseclib\Crypt\Base::encrypt() - * @see \phpseclib\Crypt\Base::decrypt() - */ - /** - * Encrypt / decrypt using the Counter mode. - * - * Set to -1 since that's what Crypt/Random.php uses to index the CTR mode. - * - * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Counter_.28CTR.29 - */ - const MODE_CTR = -1; - /** - * Encrypt / decrypt using the Electronic Code Book mode. - * - * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Electronic_codebook_.28ECB.29 - */ - const MODE_ECB = 1; - /** - * Encrypt / decrypt using the Code Book Chaining mode. - * - * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Cipher-block_chaining_.28CBC.29 - */ - const MODE_CBC = 2; - /** - * Encrypt / decrypt using the Cipher Feedback mode. - * - * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Cipher_feedback_.28CFB.29 - */ - const MODE_CFB = 3; - /** - * Encrypt / decrypt using the Cipher Feedback mode (8bit) - */ - const MODE_CFB8 = 38; - /** - * Encrypt / decrypt using the Output Feedback mode. - * - * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Output_feedback_.28OFB.29 - */ - const MODE_OFB = 4; - /** - * Encrypt / decrypt using streaming mode. - */ - const MODE_STREAM = 5; - /**#@-*/ - - /** - * Whirlpool available flag - * - * @see \phpseclib\Crypt\Base::_hashInlineCryptFunction() - * @var bool - * @access private - */ - static $WHIRLPOOL_AVAILABLE; - - /**#@+ - * @access private - * @see \phpseclib\Crypt\Base::__construct() - */ - /** - * Base value for the internal implementation $engine switch - */ - const ENGINE_INTERNAL = 1; - /** - * Base value for the mcrypt implementation $engine switch - */ - const ENGINE_MCRYPT = 2; - /** - * Base value for the mcrypt implementation $engine switch - */ - const ENGINE_OPENSSL = 3; - /**#@-*/ - - /** - * The Encryption Mode - * - * @see self::__construct() - * @var int - * @access private - */ - var $mode; - - /** - * The Block Length of the block cipher - * - * @var int - * @access private - */ - var $block_size = 16; - - /** - * The Key - * - * @see self::setKey() - * @var string - * @access private - */ - var $key = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; - - /** - * The Initialization Vector - * - * @see self::setIV() - * @var string - * @access private - */ - var $iv; - - /** - * A "sliding" Initialization Vector - * - * @see self::enableContinuousBuffer() - * @see self::_clearBuffers() - * @var string - * @access private - */ - var $encryptIV; - - /** - * A "sliding" Initialization Vector - * - * @see self::enableContinuousBuffer() - * @see self::_clearBuffers() - * @var string - * @access private - */ - var $decryptIV; - - /** - * Continuous Buffer status - * - * @see self::enableContinuousBuffer() - * @var bool - * @access private - */ - var $continuousBuffer = false; - - /** - * Encryption buffer for CTR, OFB and CFB modes - * - * @see self::encrypt() - * @see self::_clearBuffers() - * @var array - * @access private - */ - var $enbuffer; - - /** - * Decryption buffer for CTR, OFB and CFB modes - * - * @see self::decrypt() - * @see self::_clearBuffers() - * @var array - * @access private - */ - var $debuffer; - - /** - * mcrypt resource for encryption - * - * The mcrypt resource can be recreated every time something needs to be created or it can be created just once. - * Since mcrypt operates in continuous mode, by default, it'll need to be recreated when in non-continuous mode. - * - * @see self::encrypt() - * @var resource - * @access private - */ - var $enmcrypt; - - /** - * mcrypt resource for decryption - * - * The mcrypt resource can be recreated every time something needs to be created or it can be created just once. - * Since mcrypt operates in continuous mode, by default, it'll need to be recreated when in non-continuous mode. - * - * @see self::decrypt() - * @var resource - * @access private - */ - var $demcrypt; - - /** - * Does the enmcrypt resource need to be (re)initialized? - * - * @see \phpseclib\Crypt\Twofish::setKey() - * @see \phpseclib\Crypt\Twofish::setIV() - * @var bool - * @access private - */ - var $enchanged = true; - - /** - * Does the demcrypt resource need to be (re)initialized? - * - * @see \phpseclib\Crypt\Twofish::setKey() - * @see \phpseclib\Crypt\Twofish::setIV() - * @var bool - * @access private - */ - var $dechanged = true; - - /** - * mcrypt resource for CFB mode - * - * mcrypt's CFB mode, in (and only in) buffered context, - * is broken, so phpseclib implements the CFB mode by it self, - * even when the mcrypt php extension is available. - * - * In order to do the CFB-mode work (fast) phpseclib - * use a separate ECB-mode mcrypt resource. - * - * @link http://phpseclib.sourceforge.net/cfb-demo.phps - * @see self::encrypt() - * @see self::decrypt() - * @see self::_setupMcrypt() - * @var resource - * @access private - */ - var $ecb; - - /** - * Optimizing value while CFB-encrypting - * - * Only relevant if $continuousBuffer enabled - * and $engine == self::ENGINE_MCRYPT - * - * It's faster to re-init $enmcrypt if - * $buffer bytes > $cfb_init_len than - * using the $ecb resource furthermore. - * - * This value depends of the chosen cipher - * and the time it would be needed for it's - * initialization [by mcrypt_generic_init()] - * which, typically, depends on the complexity - * on its internaly Key-expanding algorithm. - * - * @see self::encrypt() - * @var int - * @access private - */ - var $cfb_init_len = 600; - - /** - * Does internal cipher state need to be (re)initialized? - * - * @see self::setKey() - * @see self::setIV() - * @see self::disableContinuousBuffer() - * @var bool - * @access private - */ - var $changed = true; - - /** - * Padding status - * - * @see self::enablePadding() - * @var bool - * @access private - */ - var $padding = true; - - /** - * Is the mode one that is paddable? - * - * @see self::__construct() - * @var bool - * @access private - */ - var $paddable = false; - - /** - * Holds which crypt engine internaly should be use, - * which will be determined automatically on __construct() - * - * Currently available $engines are: - * - self::ENGINE_OPENSSL (very fast, php-extension: openssl, extension_loaded('openssl') required) - * - self::ENGINE_MCRYPT (fast, php-extension: mcrypt, extension_loaded('mcrypt') required) - * - self::ENGINE_INTERNAL (slower, pure php-engine, no php-extension required) - * - * @see self::_setEngine() - * @see self::encrypt() - * @see self::decrypt() - * @var int - * @access private - */ - var $engine; - - /** - * Holds the preferred crypt engine - * - * @see self::_setEngine() - * @see self::setPreferredEngine() - * @var int - * @access private - */ - var $preferredEngine; - - /** - * The mcrypt specific name of the cipher - * - * Only used if $engine == self::ENGINE_MCRYPT - * - * @link http://www.php.net/mcrypt_module_open - * @link http://www.php.net/mcrypt_list_algorithms - * @see self::_setupMcrypt() - * @var string - * @access private - */ - var $cipher_name_mcrypt; - - /** - * The openssl specific name of the cipher - * - * Only used if $engine == self::ENGINE_OPENSSL - * - * @link http://www.php.net/openssl-get-cipher-methods - * @var string - * @access private - */ - var $cipher_name_openssl; - - /** - * The openssl specific name of the cipher in ECB mode - * - * If OpenSSL does not support the mode we're trying to use (CTR) - * it can still be emulated with ECB mode. - * - * @link http://www.php.net/openssl-get-cipher-methods - * @var string - * @access private - */ - var $cipher_name_openssl_ecb; - - /** - * The default salt used by setPassword() - * - * @see self::setPassword() - * @var string - * @access private - */ - var $password_default_salt = 'phpseclib/salt'; - - /** - * The name of the performance-optimized callback function - * - * Used by encrypt() / decrypt() - * only if $engine == self::ENGINE_INTERNAL - * - * @see self::encrypt() - * @see self::decrypt() - * @see self::_setupInlineCrypt() - * @see self::$use_inline_crypt - * @var Callback - * @access private - */ - var $inline_crypt; - - /** - * Holds whether performance-optimized $inline_crypt() can/should be used. - * - * @see self::encrypt() - * @see self::decrypt() - * @see self::inline_crypt - * @var mixed - * @access private - */ - var $use_inline_crypt = true; - - /** - * If OpenSSL can be used in ECB but not in CTR we can emulate CTR - * - * @see self::_openssl_ctr_process() - * @var bool - * @access private - */ - var $openssl_emulate_ctr = false; - - /** - * Determines what options are passed to openssl_encrypt/decrypt - * - * @see self::isValidEngine() - * @var mixed - * @access private - */ - var $openssl_options; - - /** - * Has the key length explicitly been set or should it be derived from the key, itself? - * - * @see self::setKeyLength() - * @var bool - * @access private - */ - var $explicit_key_length = false; - - /** - * Don't truncate / null pad key - * - * @see self::_clearBuffers() - * @var bool - * @access private - */ - var $skip_key_adjustment = false; - - /** - * Default Constructor. - * - * Determines whether or not the mcrypt extension should be used. - * - * $mode could be: - * - * - self::MODE_ECB - * - * - self::MODE_CBC - * - * - self::MODE_CTR - * - * - self::MODE_CFB - * - * - self::MODE_OFB - * - * If not explicitly set, self::MODE_CBC will be used. - * - * @param int $mode - * @access public - */ - function __construct($mode = self::MODE_CBC) - { - // $mode dependent settings - switch ($mode) { - case self::MODE_ECB: - $this->paddable = true; - $this->mode = self::MODE_ECB; - break; - case self::MODE_CTR: - case self::MODE_CFB: - case self::MODE_CFB8: - case self::MODE_OFB: - case self::MODE_STREAM: - $this->mode = $mode; - break; - case self::MODE_CBC: - default: - $this->paddable = true; - $this->mode = self::MODE_CBC; - } - - $this->_setEngine(); - } - - /** - * Sets the initialization vector. (optional) - * - * SetIV is not required when self::MODE_ECB (or ie for AES: \phpseclib\Crypt\AES::MODE_ECB) is being used. If not explicitly set, it'll be assumed - * to be all zero's. - * - * @access public - * @param string $iv - * @internal Can be overwritten by a sub class, but does not have to be - */ - function setIV($iv) - { - if ($this->mode == self::MODE_ECB) { - return; - } - - $this->iv = $iv; - $this->changed = true; - } - - /** - * Sets the key length. - * - * Keys with explicitly set lengths need to be treated accordingly - * - * @access public - * @param int $length - */ - function setKeyLength($length) - { - $this->explicit_key_length = true; - $this->changed = true; - $this->_setEngine(); - } - - /** - * Returns the current key length in bits - * - * @access public - * @return int - */ - function getKeyLength() - { - return $this->key_length << 3; - } - - /** - * Returns the current block length in bits - * - * @access public - * @return int - */ - function getBlockLength() - { - return $this->block_size << 3; - } - - /** - * Sets the key. - * - * The min/max length(s) of the key depends on the cipher which is used. - * If the key not fits the length(s) of the cipher it will paded with null bytes - * up to the closest valid key length. If the key is more than max length, - * we trim the excess bits. - * - * If the key is not explicitly set, it'll be assumed to be all null bytes. - * - * @access public - * @param string $key - * @internal Could, but not must, extend by the child Crypt_* class - */ - function setKey($key) - { - if (!$this->explicit_key_length) { - $this->setKeyLength(strlen($key) << 3); - $this->explicit_key_length = false; - } - - $this->key = $key; - $this->changed = true; - $this->_setEngine(); - } - - /** - * Sets the password. - * - * Depending on what $method is set to, setPassword()'s (optional) parameters are as follows: - * {@link http://en.wikipedia.org/wiki/PBKDF2 pbkdf2} or pbkdf1: - * $hash, $salt, $count, $dkLen - * - * Where $hash (default = sha1) currently supports the following hashes: see: Crypt/Hash.php - * - * @see Crypt/Hash.php - * @param string $password - * @param string $method - * @return bool - * @access public - * @internal Could, but not must, extend by the child Crypt_* class - */ - function setPassword($password, $method = 'pbkdf2') - { - $key = ''; - - switch ($method) { - default: // 'pbkdf2' or 'pbkdf1' - $func_args = func_get_args(); - - // Hash function - $hash = isset($func_args[2]) ? $func_args[2] : 'sha1'; - - // WPA and WPA2 use the SSID as the salt - $salt = isset($func_args[3]) ? $func_args[3] : $this->password_default_salt; - - // RFC2898#section-4.2 uses 1,000 iterations by default - // WPA and WPA2 use 4,096. - $count = isset($func_args[4]) ? $func_args[4] : 1000; - - // Keylength - if (isset($func_args[5])) { - $dkLen = $func_args[5]; - } else { - $dkLen = $method == 'pbkdf1' ? 2 * $this->key_length : $this->key_length; - } - - switch (true) { - case $method == 'pbkdf1': - $hashObj = new Hash(); - $hashObj->setHash($hash); - if ($dkLen > $hashObj->getLength()) { - user_error('Derived key too long'); - return false; - } - $t = $password . $salt; - for ($i = 0; $i < $count; ++$i) { - $t = $hashObj->hash($t); - } - $key = substr($t, 0, $dkLen); - - $this->setKey(substr($key, 0, $dkLen >> 1)); - $this->setIV(substr($key, $dkLen >> 1)); - - return true; - // Determining if php[>=5.5.0]'s hash_pbkdf2() function avail- and useable - case !function_exists('hash_pbkdf2'): - case !function_exists('hash_algos'): - case !in_array($hash, hash_algos()): - $i = 1; - $hmac = new Hash(); - $hmac->setHash($hash); - $hmac->setKey($password); - while (strlen($key) < $dkLen) { - $f = $u = $hmac->hash($salt . pack('N', $i++)); - for ($j = 2; $j <= $count; ++$j) { - $u = $hmac->hash($u); - $f^= $u; - } - $key.= $f; - } - $key = substr($key, 0, $dkLen); - break; - default: - $key = hash_pbkdf2($hash, $password, $salt, $count, $dkLen, true); - } - } - - $this->setKey($key); - - return true; - } - - /** - * Encrypts a message. - * - * $plaintext will be padded with additional bytes such that it's length is a multiple of the block size. Other cipher - * implementations may or may not pad in the same manner. Other common approaches to padding and the reasons why it's - * necessary are discussed in the following - * URL: - * - * {@link http://www.di-mgt.com.au/cryptopad.html http://www.di-mgt.com.au/cryptopad.html} - * - * An alternative to padding is to, separately, send the length of the file. This is what SSH, in fact, does. - * strlen($plaintext) will still need to be a multiple of the block size, however, arbitrary values can be added to make it that - * length. - * - * @see self::decrypt() - * @access public - * @param string $plaintext - * @return string $ciphertext - * @internal Could, but not must, extend by the child Crypt_* class - */ - function encrypt($plaintext) - { - if ($this->paddable) { - $plaintext = $this->_pad($plaintext); - } - - if ($this->engine === self::ENGINE_OPENSSL) { - if ($this->changed) { - $this->_clearBuffers(); - $this->changed = false; - } - switch ($this->mode) { - case self::MODE_STREAM: - return openssl_encrypt($plaintext, $this->cipher_name_openssl, $this->key, $this->openssl_options); - case self::MODE_ECB: - $result = @openssl_encrypt($plaintext, $this->cipher_name_openssl, $this->key, $this->openssl_options); - return !defined('OPENSSL_RAW_DATA') ? substr($result, 0, -$this->block_size) : $result; - case self::MODE_CBC: - $result = openssl_encrypt($plaintext, $this->cipher_name_openssl, $this->key, $this->openssl_options, $this->encryptIV); - if (!defined('OPENSSL_RAW_DATA')) { - $result = substr($result, 0, -$this->block_size); - } - if ($this->continuousBuffer) { - $this->encryptIV = substr($result, -$this->block_size); - } - return $result; - case self::MODE_CTR: - return $this->_openssl_ctr_process($plaintext, $this->encryptIV, $this->enbuffer); - case self::MODE_CFB: - // cfb loosely routines inspired by openssl's: - // {@link http://cvs.openssl.org/fileview?f=openssl/crypto/modes/cfb128.c&v=1.3.2.2.2.1} - $ciphertext = ''; - if ($this->continuousBuffer) { - $iv = &$this->encryptIV; - $pos = &$this->enbuffer['pos']; - } else { - $iv = $this->encryptIV; - $pos = 0; - } - $len = strlen($plaintext); - $i = 0; - if ($pos) { - $orig_pos = $pos; - $max = $this->block_size - $pos; - if ($len >= $max) { - $i = $max; - $len-= $max; - $pos = 0; - } else { - $i = $len; - $pos+= $len; - $len = 0; - } - // ie. $i = min($max, $len), $len-= $i, $pos+= $i, $pos%= $blocksize - $ciphertext = substr($iv, $orig_pos) ^ $plaintext; - $iv = substr_replace($iv, $ciphertext, $orig_pos, $i); - $plaintext = substr($plaintext, $i); - } - - $overflow = $len % $this->block_size; - - if ($overflow) { - $ciphertext.= openssl_encrypt(substr($plaintext, 0, -$overflow) . str_repeat("\0", $this->block_size), $this->cipher_name_openssl, $this->key, $this->openssl_options, $iv); - $iv = $this->_string_pop($ciphertext, $this->block_size); - - $size = $len - $overflow; - $block = $iv ^ substr($plaintext, -$overflow); - $iv = substr_replace($iv, $block, 0, $overflow); - $ciphertext.= $block; - $pos = $overflow; - } elseif ($len) { - $ciphertext = openssl_encrypt($plaintext, $this->cipher_name_openssl, $this->key, $this->openssl_options, $iv); - $iv = substr($ciphertext, -$this->block_size); - } - - return $ciphertext; - case self::MODE_CFB8: - $ciphertext = openssl_encrypt($plaintext, $this->cipher_name_openssl, $this->key, $this->openssl_options, $this->encryptIV); - if ($this->continuousBuffer) { - if (($len = strlen($ciphertext)) >= $this->block_size) { - $this->encryptIV = substr($ciphertext, -$this->block_size); - } else { - $this->encryptIV = substr($this->encryptIV, $len - $this->block_size) . substr($ciphertext, -$len); - } - } - return $ciphertext; - case self::MODE_OFB: - return $this->_openssl_ofb_process($plaintext, $this->encryptIV, $this->enbuffer); - } - } - - if ($this->engine === self::ENGINE_MCRYPT) { - set_error_handler(array($this, 'do_nothing')); - - if ($this->changed) { - $this->_setupMcrypt(); - $this->changed = false; - } - if ($this->enchanged) { - mcrypt_generic_init($this->enmcrypt, $this->key, $this->encryptIV); - $this->enchanged = false; - } - - // re: {@link http://phpseclib.sourceforge.net/cfb-demo.phps} - // using mcrypt's default handing of CFB the above would output two different things. using phpseclib's - // rewritten CFB implementation the above outputs the same thing twice. - if ($this->mode == self::MODE_CFB && $this->continuousBuffer) { - $block_size = $this->block_size; - $iv = &$this->encryptIV; - $pos = &$this->enbuffer['pos']; - $len = strlen($plaintext); - $ciphertext = ''; - $i = 0; - if ($pos) { - $orig_pos = $pos; - $max = $block_size - $pos; - if ($len >= $max) { - $i = $max; - $len-= $max; - $pos = 0; - } else { - $i = $len; - $pos+= $len; - $len = 0; - } - $ciphertext = substr($iv, $orig_pos) ^ $plaintext; - $iv = substr_replace($iv, $ciphertext, $orig_pos, $i); - $this->enbuffer['enmcrypt_init'] = true; - } - if ($len >= $block_size) { - if ($this->enbuffer['enmcrypt_init'] === false || $len > $this->cfb_init_len) { - if ($this->enbuffer['enmcrypt_init'] === true) { - mcrypt_generic_init($this->enmcrypt, $this->key, $iv); - $this->enbuffer['enmcrypt_init'] = false; - } - $ciphertext.= mcrypt_generic($this->enmcrypt, substr($plaintext, $i, $len - $len % $block_size)); - $iv = substr($ciphertext, -$block_size); - $len%= $block_size; - } else { - while ($len >= $block_size) { - $iv = mcrypt_generic($this->ecb, $iv) ^ substr($plaintext, $i, $block_size); - $ciphertext.= $iv; - $len-= $block_size; - $i+= $block_size; - } - } - } - - if ($len) { - $iv = mcrypt_generic($this->ecb, $iv); - $block = $iv ^ substr($plaintext, -$len); - $iv = substr_replace($iv, $block, 0, $len); - $ciphertext.= $block; - $pos = $len; - } - - restore_error_handler(); - - return $ciphertext; - } - - $ciphertext = mcrypt_generic($this->enmcrypt, $plaintext); - - if (!$this->continuousBuffer) { - mcrypt_generic_init($this->enmcrypt, $this->key, $this->encryptIV); - } - - restore_error_handler(); - - return $ciphertext; - } - - if ($this->changed) { - $this->_setup(); - $this->changed = false; - } - if ($this->use_inline_crypt) { - $inline = $this->inline_crypt; - return $inline('encrypt', $this, $plaintext); - } - - $buffer = &$this->enbuffer; - $block_size = $this->block_size; - $ciphertext = ''; - switch ($this->mode) { - case self::MODE_ECB: - for ($i = 0; $i < strlen($plaintext); $i+=$block_size) { - $ciphertext.= $this->_encryptBlock(substr($plaintext, $i, $block_size)); - } - break; - case self::MODE_CBC: - $xor = $this->encryptIV; - for ($i = 0; $i < strlen($plaintext); $i+=$block_size) { - $block = substr($plaintext, $i, $block_size); - $block = $this->_encryptBlock($block ^ $xor); - $xor = $block; - $ciphertext.= $block; - } - if ($this->continuousBuffer) { - $this->encryptIV = $xor; - } - break; - case self::MODE_CTR: - $xor = $this->encryptIV; - if (strlen($buffer['ciphertext'])) { - for ($i = 0; $i < strlen($plaintext); $i+=$block_size) { - $block = substr($plaintext, $i, $block_size); - if (strlen($block) > strlen($buffer['ciphertext'])) { - $buffer['ciphertext'].= $this->_encryptBlock($xor); - } - $this->_increment_str($xor); - $key = $this->_string_shift($buffer['ciphertext'], $block_size); - $ciphertext.= $block ^ $key; - } - } else { - for ($i = 0; $i < strlen($plaintext); $i+=$block_size) { - $block = substr($plaintext, $i, $block_size); - $key = $this->_encryptBlock($xor); - $this->_increment_str($xor); - $ciphertext.= $block ^ $key; - } - } - if ($this->continuousBuffer) { - $this->encryptIV = $xor; - if ($start = strlen($plaintext) % $block_size) { - $buffer['ciphertext'] = substr($key, $start) . $buffer['ciphertext']; - } - } - break; - case self::MODE_CFB: - // cfb loosely routines inspired by openssl's: - // {@link http://cvs.openssl.org/fileview?f=openssl/crypto/modes/cfb128.c&v=1.3.2.2.2.1} - if ($this->continuousBuffer) { - $iv = &$this->encryptIV; - $pos = &$buffer['pos']; - } else { - $iv = $this->encryptIV; - $pos = 0; - } - $len = strlen($plaintext); - $i = 0; - if ($pos) { - $orig_pos = $pos; - $max = $block_size - $pos; - if ($len >= $max) { - $i = $max; - $len-= $max; - $pos = 0; - } else { - $i = $len; - $pos+= $len; - $len = 0; - } - // ie. $i = min($max, $len), $len-= $i, $pos+= $i, $pos%= $blocksize - $ciphertext = substr($iv, $orig_pos) ^ $plaintext; - $iv = substr_replace($iv, $ciphertext, $orig_pos, $i); - } - while ($len >= $block_size) { - $iv = $this->_encryptBlock($iv) ^ substr($plaintext, $i, $block_size); - $ciphertext.= $iv; - $len-= $block_size; - $i+= $block_size; - } - if ($len) { - $iv = $this->_encryptBlock($iv); - $block = $iv ^ substr($plaintext, $i); - $iv = substr_replace($iv, $block, 0, $len); - $ciphertext.= $block; - $pos = $len; - } - break; - case self::MODE_CFB8: - $ciphertext = ''; - $len = strlen($plaintext); - $iv = $this->encryptIV; - - for ($i = 0; $i < $len; ++$i) { - $ciphertext .= ($c = $plaintext[$i] ^ $this->_encryptBlock($iv)); - $iv = substr($iv, 1) . $c; - } - - if ($this->continuousBuffer) { - if ($len >= $block_size) { - $this->encryptIV = substr($ciphertext, -$block_size); - } else { - $this->encryptIV = substr($this->encryptIV, $len - $block_size) . substr($ciphertext, -$len); - } - } - break; - case self::MODE_OFB: - $xor = $this->encryptIV; - if (strlen($buffer['xor'])) { - for ($i = 0; $i < strlen($plaintext); $i+=$block_size) { - $block = substr($plaintext, $i, $block_size); - if (strlen($block) > strlen($buffer['xor'])) { - $xor = $this->_encryptBlock($xor); - $buffer['xor'].= $xor; - } - $key = $this->_string_shift($buffer['xor'], $block_size); - $ciphertext.= $block ^ $key; - } - } else { - for ($i = 0; $i < strlen($plaintext); $i+=$block_size) { - $xor = $this->_encryptBlock($xor); - $ciphertext.= substr($plaintext, $i, $block_size) ^ $xor; - } - $key = $xor; - } - if ($this->continuousBuffer) { - $this->encryptIV = $xor; - if ($start = strlen($plaintext) % $block_size) { - $buffer['xor'] = substr($key, $start) . $buffer['xor']; - } - } - break; - case self::MODE_STREAM: - $ciphertext = $this->_encryptBlock($plaintext); - break; - } - - return $ciphertext; - } - - /** - * Decrypts a message. - * - * If strlen($ciphertext) is not a multiple of the block size, null bytes will be added to the end of the string until - * it is. - * - * @see self::encrypt() - * @access public - * @param string $ciphertext - * @return string $plaintext - * @internal Could, but not must, extend by the child Crypt_* class - */ - function decrypt($ciphertext) - { - if ($this->paddable) { - // we pad with chr(0) since that's what mcrypt_generic does. to quote from {@link http://www.php.net/function.mcrypt-generic}: - // "The data is padded with "\0" to make sure the length of the data is n * blocksize." - $ciphertext = str_pad($ciphertext, strlen($ciphertext) + ($this->block_size - strlen($ciphertext) % $this->block_size) % $this->block_size, chr(0)); - } - - if ($this->engine === self::ENGINE_OPENSSL) { - if ($this->changed) { - $this->_clearBuffers(); - $this->changed = false; - } - switch ($this->mode) { - case self::MODE_STREAM: - $plaintext = openssl_decrypt($ciphertext, $this->cipher_name_openssl, $this->key, $this->openssl_options); - break; - case self::MODE_ECB: - if (!defined('OPENSSL_RAW_DATA')) { - $ciphertext.= @openssl_encrypt('', $this->cipher_name_openssl_ecb, $this->key, true); - } - $plaintext = openssl_decrypt($ciphertext, $this->cipher_name_openssl, $this->key, $this->openssl_options); - break; - case self::MODE_CBC: - if (!defined('OPENSSL_RAW_DATA')) { - $padding = str_repeat(chr($this->block_size), $this->block_size) ^ substr($ciphertext, -$this->block_size); - $ciphertext.= substr(@openssl_encrypt($padding, $this->cipher_name_openssl_ecb, $this->key, true), 0, $this->block_size); - $offset = 2 * $this->block_size; - } else { - $offset = $this->block_size; - } - $plaintext = openssl_decrypt($ciphertext, $this->cipher_name_openssl, $this->key, $this->openssl_options, $this->decryptIV); - if ($this->continuousBuffer) { - $this->decryptIV = substr($ciphertext, -$offset, $this->block_size); - } - break; - case self::MODE_CTR: - $plaintext = $this->_openssl_ctr_process($ciphertext, $this->decryptIV, $this->debuffer); - break; - case self::MODE_CFB: - // cfb loosely routines inspired by openssl's: - // {@link http://cvs.openssl.org/fileview?f=openssl/crypto/modes/cfb128.c&v=1.3.2.2.2.1} - $plaintext = ''; - if ($this->continuousBuffer) { - $iv = &$this->decryptIV; - $pos = &$this->buffer['pos']; - } else { - $iv = $this->decryptIV; - $pos = 0; - } - $len = strlen($ciphertext); - $i = 0; - if ($pos) { - $orig_pos = $pos; - $max = $this->block_size - $pos; - if ($len >= $max) { - $i = $max; - $len-= $max; - $pos = 0; - } else { - $i = $len; - $pos+= $len; - $len = 0; - } - // ie. $i = min($max, $len), $len-= $i, $pos+= $i, $pos%= $this->blocksize - $plaintext = substr($iv, $orig_pos) ^ $ciphertext; - $iv = substr_replace($iv, substr($ciphertext, 0, $i), $orig_pos, $i); - $ciphertext = substr($ciphertext, $i); - } - $overflow = $len % $this->block_size; - if ($overflow) { - $plaintext.= openssl_decrypt(substr($ciphertext, 0, -$overflow), $this->cipher_name_openssl, $this->key, $this->openssl_options, $iv); - if ($len - $overflow) { - $iv = substr($ciphertext, -$overflow - $this->block_size, -$overflow); - } - $iv = openssl_encrypt(str_repeat("\0", $this->block_size), $this->cipher_name_openssl, $this->key, $this->openssl_options, $iv); - $plaintext.= $iv ^ substr($ciphertext, -$overflow); - $iv = substr_replace($iv, substr($ciphertext, -$overflow), 0, $overflow); - $pos = $overflow; - } elseif ($len) { - $plaintext.= openssl_decrypt($ciphertext, $this->cipher_name_openssl, $this->key, $this->openssl_options, $iv); - $iv = substr($ciphertext, -$this->block_size); - } - break; - case self::MODE_CFB8: - $plaintext = openssl_decrypt($ciphertext, $this->cipher_name_openssl, $this->key, $this->openssl_options, $this->decryptIV); - if ($this->continuousBuffer) { - if (($len = strlen($ciphertext)) >= $this->block_size) { - $this->decryptIV = substr($ciphertext, -$this->block_size); - } else { - $this->decryptIV = substr($this->decryptIV, $len - $this->block_size) . substr($ciphertext, -$len); - } - } - break; - case self::MODE_OFB: - $plaintext = $this->_openssl_ofb_process($ciphertext, $this->decryptIV, $this->debuffer); - } - - return $this->paddable ? $this->_unpad($plaintext) : $plaintext; - } - - if ($this->engine === self::ENGINE_MCRYPT) { - set_error_handler(array($this, 'do_nothing')); - $block_size = $this->block_size; - if ($this->changed) { - $this->_setupMcrypt(); - $this->changed = false; - } - if ($this->dechanged) { - mcrypt_generic_init($this->demcrypt, $this->key, $this->decryptIV); - $this->dechanged = false; - } - - if ($this->mode == self::MODE_CFB && $this->continuousBuffer) { - $iv = &$this->decryptIV; - $pos = &$this->debuffer['pos']; - $len = strlen($ciphertext); - $plaintext = ''; - $i = 0; - if ($pos) { - $orig_pos = $pos; - $max = $block_size - $pos; - if ($len >= $max) { - $i = $max; - $len-= $max; - $pos = 0; - } else { - $i = $len; - $pos+= $len; - $len = 0; - } - // ie. $i = min($max, $len), $len-= $i, $pos+= $i, $pos%= $blocksize - $plaintext = substr($iv, $orig_pos) ^ $ciphertext; - $iv = substr_replace($iv, substr($ciphertext, 0, $i), $orig_pos, $i); - } - if ($len >= $block_size) { - $cb = substr($ciphertext, $i, $len - $len % $block_size); - $plaintext.= mcrypt_generic($this->ecb, $iv . $cb) ^ $cb; - $iv = substr($cb, -$block_size); - $len%= $block_size; - } - if ($len) { - $iv = mcrypt_generic($this->ecb, $iv); - $plaintext.= $iv ^ substr($ciphertext, -$len); - $iv = substr_replace($iv, substr($ciphertext, -$len), 0, $len); - $pos = $len; - } - - restore_error_handler(); - - return $plaintext; - } - - $plaintext = mdecrypt_generic($this->demcrypt, $ciphertext); - - if (!$this->continuousBuffer) { - mcrypt_generic_init($this->demcrypt, $this->key, $this->decryptIV); - } - - restore_error_handler(); - - return $this->paddable ? $this->_unpad($plaintext) : $plaintext; - } - - if ($this->changed) { - $this->_setup(); - $this->changed = false; - } - if ($this->use_inline_crypt) { - $inline = $this->inline_crypt; - return $inline('decrypt', $this, $ciphertext); - } - - $block_size = $this->block_size; - - $buffer = &$this->debuffer; - $plaintext = ''; - switch ($this->mode) { - case self::MODE_ECB: - for ($i = 0; $i < strlen($ciphertext); $i+=$block_size) { - $plaintext.= $this->_decryptBlock(substr($ciphertext, $i, $block_size)); - } - break; - case self::MODE_CBC: - $xor = $this->decryptIV; - for ($i = 0; $i < strlen($ciphertext); $i+=$block_size) { - $block = substr($ciphertext, $i, $block_size); - $plaintext.= $this->_decryptBlock($block) ^ $xor; - $xor = $block; - } - if ($this->continuousBuffer) { - $this->decryptIV = $xor; - } - break; - case self::MODE_CTR: - $xor = $this->decryptIV; - if (strlen($buffer['ciphertext'])) { - for ($i = 0; $i < strlen($ciphertext); $i+=$block_size) { - $block = substr($ciphertext, $i, $block_size); - if (strlen($block) > strlen($buffer['ciphertext'])) { - $buffer['ciphertext'].= $this->_encryptBlock($xor); - $this->_increment_str($xor); - } - $key = $this->_string_shift($buffer['ciphertext'], $block_size); - $plaintext.= $block ^ $key; - } - } else { - for ($i = 0; $i < strlen($ciphertext); $i+=$block_size) { - $block = substr($ciphertext, $i, $block_size); - $key = $this->_encryptBlock($xor); - $this->_increment_str($xor); - $plaintext.= $block ^ $key; - } - } - if ($this->continuousBuffer) { - $this->decryptIV = $xor; - if ($start = strlen($ciphertext) % $block_size) { - $buffer['ciphertext'] = substr($key, $start) . $buffer['ciphertext']; - } - } - break; - case self::MODE_CFB: - if ($this->continuousBuffer) { - $iv = &$this->decryptIV; - $pos = &$buffer['pos']; - } else { - $iv = $this->decryptIV; - $pos = 0; - } - $len = strlen($ciphertext); - $i = 0; - if ($pos) { - $orig_pos = $pos; - $max = $block_size - $pos; - if ($len >= $max) { - $i = $max; - $len-= $max; - $pos = 0; - } else { - $i = $len; - $pos+= $len; - $len = 0; - } - // ie. $i = min($max, $len), $len-= $i, $pos+= $i, $pos%= $blocksize - $plaintext = substr($iv, $orig_pos) ^ $ciphertext; - $iv = substr_replace($iv, substr($ciphertext, 0, $i), $orig_pos, $i); - } - while ($len >= $block_size) { - $iv = $this->_encryptBlock($iv); - $cb = substr($ciphertext, $i, $block_size); - $plaintext.= $iv ^ $cb; - $iv = $cb; - $len-= $block_size; - $i+= $block_size; - } - if ($len) { - $iv = $this->_encryptBlock($iv); - $plaintext.= $iv ^ substr($ciphertext, $i); - $iv = substr_replace($iv, substr($ciphertext, $i), 0, $len); - $pos = $len; - } - break; - case self::MODE_CFB8: - $plaintext = ''; - $len = strlen($ciphertext); - $iv = $this->decryptIV; - - for ($i = 0; $i < $len; ++$i) { - $plaintext .= $ciphertext[$i] ^ $this->_encryptBlock($iv); - $iv = substr($iv, 1) . $ciphertext[$i]; - } - - if ($this->continuousBuffer) { - if ($len >= $block_size) { - $this->decryptIV = substr($ciphertext, -$block_size); - } else { - $this->decryptIV = substr($this->decryptIV, $len - $block_size) . substr($ciphertext, -$len); - } - } - break; - case self::MODE_OFB: - $xor = $this->decryptIV; - if (strlen($buffer['xor'])) { - for ($i = 0; $i < strlen($ciphertext); $i+=$block_size) { - $block = substr($ciphertext, $i, $block_size); - if (strlen($block) > strlen($buffer['xor'])) { - $xor = $this->_encryptBlock($xor); - $buffer['xor'].= $xor; - } - $key = $this->_string_shift($buffer['xor'], $block_size); - $plaintext.= $block ^ $key; - } - } else { - for ($i = 0; $i < strlen($ciphertext); $i+=$block_size) { - $xor = $this->_encryptBlock($xor); - $plaintext.= substr($ciphertext, $i, $block_size) ^ $xor; - } - $key = $xor; - } - if ($this->continuousBuffer) { - $this->decryptIV = $xor; - if ($start = strlen($ciphertext) % $block_size) { - $buffer['xor'] = substr($key, $start) . $buffer['xor']; - } - } - break; - case self::MODE_STREAM: - $plaintext = $this->_decryptBlock($ciphertext); - break; - } - return $this->paddable ? $this->_unpad($plaintext) : $plaintext; - } - - /** - * OpenSSL CTR Processor - * - * PHP's OpenSSL bindings do not operate in continuous mode so we'll wrap around it. Since the keystream - * for CTR is the same for both encrypting and decrypting this function is re-used by both Base::encrypt() - * and Base::decrypt(). Also, OpenSSL doesn't implement CTR for all of it's symmetric ciphers so this - * function will emulate CTR with ECB when necessary. - * - * @see self::encrypt() - * @see self::decrypt() - * @param string $plaintext - * @param string $encryptIV - * @param array $buffer - * @return string - * @access private - */ - function _openssl_ctr_process($plaintext, &$encryptIV, &$buffer) - { - $ciphertext = ''; - - $block_size = $this->block_size; - $key = $this->key; - - if ($this->openssl_emulate_ctr) { - $xor = $encryptIV; - if (strlen($buffer['ciphertext'])) { - for ($i = 0; $i < strlen($plaintext); $i+=$block_size) { - $block = substr($plaintext, $i, $block_size); - if (strlen($block) > strlen($buffer['ciphertext'])) { - $result = @openssl_encrypt($xor, $this->cipher_name_openssl_ecb, $key, $this->openssl_options); - $result = !defined('OPENSSL_RAW_DATA') ? substr($result, 0, -$this->block_size) : $result; - $buffer['ciphertext'].= $result; - } - $this->_increment_str($xor); - $otp = $this->_string_shift($buffer['ciphertext'], $block_size); - $ciphertext.= $block ^ $otp; - } - } else { - for ($i = 0; $i < strlen($plaintext); $i+=$block_size) { - $block = substr($plaintext, $i, $block_size); - $otp = @openssl_encrypt($xor, $this->cipher_name_openssl_ecb, $key, $this->openssl_options); - $otp = !defined('OPENSSL_RAW_DATA') ? substr($otp, 0, -$this->block_size) : $otp; - $this->_increment_str($xor); - $ciphertext.= $block ^ $otp; - } - } - if ($this->continuousBuffer) { - $encryptIV = $xor; - if ($start = strlen($plaintext) % $block_size) { - $buffer['ciphertext'] = substr($key, $start) . $buffer['ciphertext']; - } - } - - return $ciphertext; - } - - if (strlen($buffer['ciphertext'])) { - $ciphertext = $plaintext ^ $this->_string_shift($buffer['ciphertext'], strlen($plaintext)); - $plaintext = substr($plaintext, strlen($ciphertext)); - - if (!strlen($plaintext)) { - return $ciphertext; - } - } - - $overflow = strlen($plaintext) % $block_size; - if ($overflow) { - $plaintext2 = $this->_string_pop($plaintext, $overflow); // ie. trim $plaintext to a multiple of $block_size and put rest of $plaintext in $plaintext2 - $encrypted = openssl_encrypt($plaintext . str_repeat("\0", $block_size), $this->cipher_name_openssl, $key, $this->openssl_options, $encryptIV); - $temp = $this->_string_pop($encrypted, $block_size); - $ciphertext.= $encrypted . ($plaintext2 ^ $temp); - if ($this->continuousBuffer) { - $buffer['ciphertext'] = substr($temp, $overflow); - $encryptIV = $temp; - } - } elseif (!strlen($buffer['ciphertext'])) { - $ciphertext.= openssl_encrypt($plaintext . str_repeat("\0", $block_size), $this->cipher_name_openssl, $key, $this->openssl_options, $encryptIV); - $temp = $this->_string_pop($ciphertext, $block_size); - if ($this->continuousBuffer) { - $encryptIV = $temp; - } - } - if ($this->continuousBuffer) { - if (!defined('OPENSSL_RAW_DATA')) { - $encryptIV.= @openssl_encrypt('', $this->cipher_name_openssl_ecb, $key, $this->openssl_options); - } - $encryptIV = openssl_decrypt($encryptIV, $this->cipher_name_openssl_ecb, $key, $this->openssl_options); - if ($overflow) { - $this->_increment_str($encryptIV); - } - } - - return $ciphertext; - } - - /** - * OpenSSL OFB Processor - * - * PHP's OpenSSL bindings do not operate in continuous mode so we'll wrap around it. Since the keystream - * for OFB is the same for both encrypting and decrypting this function is re-used by both Base::encrypt() - * and Base::decrypt(). - * - * @see self::encrypt() - * @see self::decrypt() - * @param string $plaintext - * @param string $encryptIV - * @param array $buffer - * @return string - * @access private - */ - function _openssl_ofb_process($plaintext, &$encryptIV, &$buffer) - { - if (strlen($buffer['xor'])) { - $ciphertext = $plaintext ^ $buffer['xor']; - $buffer['xor'] = substr($buffer['xor'], strlen($ciphertext)); - $plaintext = substr($plaintext, strlen($ciphertext)); - } else { - $ciphertext = ''; - } - - $block_size = $this->block_size; - - $len = strlen($plaintext); - $key = $this->key; - $overflow = $len % $block_size; - - if (strlen($plaintext)) { - if ($overflow) { - $ciphertext.= openssl_encrypt(substr($plaintext, 0, -$overflow) . str_repeat("\0", $block_size), $this->cipher_name_openssl, $key, $this->openssl_options, $encryptIV); - $xor = $this->_string_pop($ciphertext, $block_size); - if ($this->continuousBuffer) { - $encryptIV = $xor; - } - $ciphertext.= $this->_string_shift($xor, $overflow) ^ substr($plaintext, -$overflow); - if ($this->continuousBuffer) { - $buffer['xor'] = $xor; - } - } else { - $ciphertext = openssl_encrypt($plaintext, $this->cipher_name_openssl, $key, $this->openssl_options, $encryptIV); - if ($this->continuousBuffer) { - $encryptIV = substr($ciphertext, -$block_size) ^ substr($plaintext, -$block_size); - } - } - } - - return $ciphertext; - } - - /** - * phpseclib <-> OpenSSL Mode Mapper - * - * May need to be overwritten by classes extending this one in some cases - * - * @return int - * @access private - */ - function _openssl_translate_mode() - { - switch ($this->mode) { - case self::MODE_ECB: - return 'ecb'; - case self::MODE_CBC: - return 'cbc'; - case self::MODE_CTR: - return 'ctr'; - case self::MODE_CFB: - return 'cfb'; - case self::MODE_CFB8: - return 'cfb8'; - case self::MODE_OFB: - return 'ofb'; - } - } - - /** - * Pad "packets". - * - * Block ciphers working by encrypting between their specified [$this->]block_size at a time - * If you ever need to encrypt or decrypt something that isn't of the proper length, it becomes necessary to - * pad the input so that it is of the proper length. - * - * Padding is enabled by default. Sometimes, however, it is undesirable to pad strings. Such is the case in SSH, - * where "packets" are padded with random bytes before being encrypted. Unpad these packets and you risk stripping - * away characters that shouldn't be stripped away. (SSH knows how many bytes are added because the length is - * transmitted separately) - * - * @see self::disablePadding() - * @access public - */ - function enablePadding() - { - $this->padding = true; - } - - /** - * Do not pad packets. - * - * @see self::enablePadding() - * @access public - */ - function disablePadding() - { - $this->padding = false; - } - - /** - * Treat consecutive "packets" as if they are a continuous buffer. - * - * Say you have a 32-byte plaintext $plaintext. Using the default behavior, the two following code snippets - * will yield different outputs: - * - * - * echo $rijndael->encrypt(substr($plaintext, 0, 16)); - * echo $rijndael->encrypt(substr($plaintext, 16, 16)); - * - * - * echo $rijndael->encrypt($plaintext); - * - * - * The solution is to enable the continuous buffer. Although this will resolve the above discrepancy, it creates - * another, as demonstrated with the following: - * - * - * $rijndael->encrypt(substr($plaintext, 0, 16)); - * echo $rijndael->decrypt($rijndael->encrypt(substr($plaintext, 16, 16))); - * - * - * echo $rijndael->decrypt($rijndael->encrypt(substr($plaintext, 16, 16))); - * - * - * With the continuous buffer disabled, these would yield the same output. With it enabled, they yield different - * outputs. The reason is due to the fact that the initialization vector's change after every encryption / - * decryption round when the continuous buffer is enabled. When it's disabled, they remain constant. - * - * Put another way, when the continuous buffer is enabled, the state of the \phpseclib\Crypt\*() object changes after each - * encryption / decryption round, whereas otherwise, it'd remain constant. For this reason, it's recommended that - * continuous buffers not be used. They do offer better security and are, in fact, sometimes required (SSH uses them), - * however, they are also less intuitive and more likely to cause you problems. - * - * @see self::disableContinuousBuffer() - * @access public - * @internal Could, but not must, extend by the child Crypt_* class - */ - function enableContinuousBuffer() - { - if ($this->mode == self::MODE_ECB) { - return; - } - - $this->continuousBuffer = true; - - $this->_setEngine(); - } - - /** - * Treat consecutive packets as if they are a discontinuous buffer. - * - * The default behavior. - * - * @see self::enableContinuousBuffer() - * @access public - * @internal Could, but not must, extend by the child Crypt_* class - */ - function disableContinuousBuffer() - { - if ($this->mode == self::MODE_ECB) { - return; - } - if (!$this->continuousBuffer) { - return; - } - - $this->continuousBuffer = false; - $this->changed = true; - - $this->_setEngine(); - } - - /** - * Test for engine validity - * - * @see self::__construct() - * @param int $engine - * @access public - * @return bool - */ - function isValidEngine($engine) - { - switch ($engine) { - case self::ENGINE_OPENSSL: - if ($this->mode == self::MODE_STREAM && $this->continuousBuffer) { - return false; - } - $this->openssl_emulate_ctr = false; - $result = $this->cipher_name_openssl && - extension_loaded('openssl') && - // PHP 5.3.0 - 5.3.2 did not let you set IV's - version_compare(PHP_VERSION, '5.3.3', '>='); - if (!$result) { - return false; - } - - // prior to PHP 5.4.0 OPENSSL_RAW_DATA and OPENSSL_ZERO_PADDING were not defined. instead of expecting an integer - // $options openssl_encrypt expected a boolean $raw_data. - if (!defined('OPENSSL_RAW_DATA')) { - $this->openssl_options = true; - } else { - $this->openssl_options = OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING; - } - - $methods = openssl_get_cipher_methods(); - if (in_array($this->cipher_name_openssl, $methods)) { - return true; - } - // not all of openssl's symmetric cipher's support ctr. for those - // that don't we'll emulate it - switch ($this->mode) { - case self::MODE_CTR: - if (in_array($this->cipher_name_openssl_ecb, $methods)) { - $this->openssl_emulate_ctr = true; - return true; - } - } - return false; - case self::ENGINE_MCRYPT: - set_error_handler(array($this, 'do_nothing')); - $result = $this->cipher_name_mcrypt && - extension_loaded('mcrypt') && - in_array($this->cipher_name_mcrypt, mcrypt_list_algorithms()); - restore_error_handler(); - return $result; - case self::ENGINE_INTERNAL: - return true; - } - - return false; - } - - /** - * Sets the preferred crypt engine - * - * Currently, $engine could be: - * - * - \phpseclib\Crypt\Base::ENGINE_OPENSSL [very fast] - * - * - \phpseclib\Crypt\Base::ENGINE_MCRYPT [fast] - * - * - \phpseclib\Crypt\Base::ENGINE_INTERNAL [slow] - * - * If the preferred crypt engine is not available the fastest available one will be used - * - * @see self::__construct() - * @param int $engine - * @access public - */ - function setPreferredEngine($engine) - { - switch ($engine) { - //case self::ENGINE_OPENSSL; - case self::ENGINE_MCRYPT: - case self::ENGINE_INTERNAL: - $this->preferredEngine = $engine; - break; - default: - $this->preferredEngine = self::ENGINE_OPENSSL; - } - - $this->_setEngine(); - } - - /** - * Returns the engine currently being utilized - * - * @see self::_setEngine() - * @access public - */ - function getEngine() - { - return $this->engine; - } - - /** - * Sets the engine as appropriate - * - * @see self::__construct() - * @access private - */ - function _setEngine() - { - $this->engine = null; - - $candidateEngines = array( - $this->preferredEngine, - self::ENGINE_OPENSSL, - self::ENGINE_MCRYPT - ); - foreach ($candidateEngines as $engine) { - if ($this->isValidEngine($engine)) { - $this->engine = $engine; - break; - } - } - if (!$this->engine) { - $this->engine = self::ENGINE_INTERNAL; - } - - if ($this->engine != self::ENGINE_MCRYPT && $this->enmcrypt) { - set_error_handler(array($this, 'do_nothing')); - // Closing the current mcrypt resource(s). _mcryptSetup() will, if needed, - // (re)open them with the module named in $this->cipher_name_mcrypt - mcrypt_module_close($this->enmcrypt); - mcrypt_module_close($this->demcrypt); - $this->enmcrypt = null; - $this->demcrypt = null; - - if ($this->ecb) { - mcrypt_module_close($this->ecb); - $this->ecb = null; - } - restore_error_handler(); - } - - $this->changed = true; - } - - /** - * Encrypts a block - * - * Note: Must be extended by the child \phpseclib\Crypt\* class - * - * @access private - * @param string $in - * @return string - */ - abstract function _encryptBlock($in); - - /** - * Decrypts a block - * - * Note: Must be extended by the child \phpseclib\Crypt\* class - * - * @access private - * @param string $in - * @return string - */ - abstract function _decryptBlock($in); - - /** - * Setup the key (expansion) - * - * Only used if $engine == self::ENGINE_INTERNAL - * - * Note: Must extend by the child \phpseclib\Crypt\* class - * - * @see self::_setup() - * @access private - */ - abstract function _setupKey(); - - /** - * Setup the self::ENGINE_INTERNAL $engine - * - * (re)init, if necessary, the internal cipher $engine and flush all $buffers - * Used (only) if $engine == self::ENGINE_INTERNAL - * - * _setup() will be called each time if $changed === true - * typically this happens when using one or more of following public methods: - * - * - setKey() - * - * - setIV() - * - * - disableContinuousBuffer() - * - * - First run of encrypt() / decrypt() with no init-settings - * - * @see self::setKey() - * @see self::setIV() - * @see self::disableContinuousBuffer() - * @access private - * @internal _setup() is always called before en/decryption. - * @internal Could, but not must, extend by the child Crypt_* class - */ - function _setup() - { - $this->_clearBuffers(); - $this->_setupKey(); - - if ($this->use_inline_crypt) { - $this->_setupInlineCrypt(); - } - } - - /** - * Setup the self::ENGINE_MCRYPT $engine - * - * (re)init, if necessary, the (ext)mcrypt resources and flush all $buffers - * Used (only) if $engine = self::ENGINE_MCRYPT - * - * _setupMcrypt() will be called each time if $changed === true - * typically this happens when using one or more of following public methods: - * - * - setKey() - * - * - setIV() - * - * - disableContinuousBuffer() - * - * - First run of encrypt() / decrypt() - * - * @see self::setKey() - * @see self::setIV() - * @see self::disableContinuousBuffer() - * @access private - * @internal Could, but not must, extend by the child Crypt_* class - */ - function _setupMcrypt() - { - $this->_clearBuffers(); - $this->enchanged = $this->dechanged = true; - - if (!isset($this->enmcrypt)) { - static $mcrypt_modes = array( - self::MODE_CTR => 'ctr', - self::MODE_ECB => MCRYPT_MODE_ECB, - self::MODE_CBC => MCRYPT_MODE_CBC, - self::MODE_CFB => 'ncfb', - self::MODE_CFB8 => MCRYPT_MODE_CFB, - self::MODE_OFB => MCRYPT_MODE_NOFB, - self::MODE_STREAM => MCRYPT_MODE_STREAM, - ); - - $this->demcrypt = mcrypt_module_open($this->cipher_name_mcrypt, '', $mcrypt_modes[$this->mode], ''); - $this->enmcrypt = mcrypt_module_open($this->cipher_name_mcrypt, '', $mcrypt_modes[$this->mode], ''); - - // we need the $ecb mcrypt resource (only) in MODE_CFB with enableContinuousBuffer() - // to workaround mcrypt's broken ncfb implementation in buffered mode - // see: {@link http://phpseclib.sourceforge.net/cfb-demo.phps} - if ($this->mode == self::MODE_CFB) { - $this->ecb = mcrypt_module_open($this->cipher_name_mcrypt, '', MCRYPT_MODE_ECB, ''); - } - } // else should mcrypt_generic_deinit be called? - - if ($this->mode == self::MODE_CFB) { - mcrypt_generic_init($this->ecb, $this->key, str_repeat("\0", $this->block_size)); - } - } - - /** - * Pads a string - * - * Pads a string using the RSA PKCS padding standards so that its length is a multiple of the blocksize. - * $this->block_size - (strlen($text) % $this->block_size) bytes are added, each of which is equal to - * chr($this->block_size - (strlen($text) % $this->block_size) - * - * If padding is disabled and $text is not a multiple of the blocksize, the string will be padded regardless - * and padding will, hence forth, be enabled. - * - * @see self::_unpad() - * @param string $text - * @access private - * @return string - */ - function _pad($text) - { - $length = strlen($text); - - if (!$this->padding) { - if ($length % $this->block_size == 0) { - return $text; - } else { - user_error("The plaintext's length ($length) is not a multiple of the block size ({$this->block_size})"); - $this->padding = true; - } - } - - $pad = $this->block_size - ($length % $this->block_size); - - return str_pad($text, $length + $pad, chr($pad)); - } - - /** - * Unpads a string. - * - * If padding is enabled and the reported padding length is invalid the encryption key will be assumed to be wrong - * and false will be returned. - * - * @see self::_pad() - * @param string $text - * @access private - * @return string - */ - function _unpad($text) - { - if (!$this->padding) { - return $text; - } - - $length = ord($text[strlen($text) - 1]); - - if (!$length || $length > $this->block_size) { - return false; - } - - return substr($text, 0, -$length); - } - - /** - * Clears internal buffers - * - * Clearing/resetting the internal buffers is done everytime - * after disableContinuousBuffer() or on cipher $engine (re)init - * ie after setKey() or setIV() - * - * @access public - * @internal Could, but not must, extend by the child Crypt_* class - */ - function _clearBuffers() - { - $this->enbuffer = $this->debuffer = array('ciphertext' => '', 'xor' => '', 'pos' => 0, 'enmcrypt_init' => true); - - // mcrypt's handling of invalid's $iv: - // $this->encryptIV = $this->decryptIV = strlen($this->iv) == $this->block_size ? $this->iv : str_repeat("\0", $this->block_size); - $this->encryptIV = $this->decryptIV = str_pad(substr($this->iv, 0, $this->block_size), $this->block_size, "\0"); - - if (!$this->skip_key_adjustment) { - $this->key = str_pad(substr($this->key, 0, $this->key_length), $this->key_length, "\0"); - } - } - - /** - * String Shift - * - * Inspired by array_shift - * - * @param string $string - * @param int $index - * @access private - * @return string - */ - function _string_shift(&$string, $index = 1) - { - $substr = substr($string, 0, $index); - $string = substr($string, $index); - return $substr; - } - - /** - * String Pop - * - * Inspired by array_pop - * - * @param string $string - * @param int $index - * @access private - * @return string - */ - function _string_pop(&$string, $index = 1) - { - $substr = substr($string, -$index); - $string = substr($string, 0, -$index); - return $substr; - } - - /** - * Increment the current string - * - * @see self::decrypt() - * @see self::encrypt() - * @param string $var - * @access private - */ - function _increment_str(&$var) - { - for ($i = 4; $i <= strlen($var); $i+= 4) { - $temp = substr($var, -$i, 4); - switch ($temp) { - case "\xFF\xFF\xFF\xFF": - $var = substr_replace($var, "\x00\x00\x00\x00", -$i, 4); - break; - case "\x7F\xFF\xFF\xFF": - $var = substr_replace($var, "\x80\x00\x00\x00", -$i, 4); - return; - default: - $temp = unpack('Nnum', $temp); - $var = substr_replace($var, pack('N', $temp['num'] + 1), -$i, 4); - return; - } - } - - $remainder = strlen($var) % 4; - - if ($remainder == 0) { - return; - } - - $temp = unpack('Nnum', str_pad(substr($var, 0, $remainder), 4, "\0", STR_PAD_LEFT)); - $temp = substr(pack('N', $temp['num'] + 1), -$remainder); - $var = substr_replace($var, $temp, 0, $remainder); - } - - /** - * Setup the performance-optimized function for de/encrypt() - * - * Stores the created (or existing) callback function-name - * in $this->inline_crypt - * - * Internally for phpseclib developers: - * - * _setupInlineCrypt() would be called only if: - * - * - $engine == self::ENGINE_INTERNAL and - * - * - $use_inline_crypt === true - * - * - each time on _setup(), after(!) _setupKey() - * - * - * This ensures that _setupInlineCrypt() has always a - * full ready2go initializated internal cipher $engine state - * where, for example, the keys allready expanded, - * keys/block_size calculated and such. - * - * It is, each time if called, the responsibility of _setupInlineCrypt(): - * - * - to set $this->inline_crypt to a valid and fully working callback function - * as a (faster) replacement for encrypt() / decrypt() - * - * - NOT to create unlimited callback functions (for memory reasons!) - * no matter how often _setupInlineCrypt() would be called. At some - * point of amount they must be generic re-useable. - * - * - the code of _setupInlineCrypt() it self, - * and the generated callback code, - * must be, in following order: - * - 100% safe - * - 100% compatible to encrypt()/decrypt() - * - using only php5+ features/lang-constructs/php-extensions if - * compatibility (down to php4) or fallback is provided - * - readable/maintainable/understandable/commented and... not-cryptic-styled-code :-) - * - >= 10% faster than encrypt()/decrypt() [which is, by the way, - * the reason for the existence of _setupInlineCrypt() :-)] - * - memory-nice - * - short (as good as possible) - * - * Note: - _setupInlineCrypt() is using _createInlineCryptFunction() to create the full callback function code. - * - In case of using inline crypting, _setupInlineCrypt() must extend by the child \phpseclib\Crypt\* class. - * - The following variable names are reserved: - * - $_* (all variable names prefixed with an underscore) - * - $self (object reference to it self. Do not use $this, but $self instead) - * - $in (the content of $in has to en/decrypt by the generated code) - * - The callback function should not use the 'return' statement, but en/decrypt'ing the content of $in only - * - * - * @see self::_setup() - * @see self::_createInlineCryptFunction() - * @see self::encrypt() - * @see self::decrypt() - * @access private - * @internal If a Crypt_* class providing inline crypting it must extend _setupInlineCrypt() - */ - function _setupInlineCrypt() - { - // If, for any reason, an extending \phpseclib\Crypt\Base() \phpseclib\Crypt\* class - // not using inline crypting then it must be ensured that: $this->use_inline_crypt = false - // ie in the class var declaration of $use_inline_crypt in general for the \phpseclib\Crypt\* class, - // in the constructor at object instance-time - // or, if it's runtime-specific, at runtime - - $this->use_inline_crypt = false; - } - - /** - * Creates the performance-optimized function for en/decrypt() - * - * Internally for phpseclib developers: - * - * _createInlineCryptFunction(): - * - * - merge the $cipher_code [setup'ed by _setupInlineCrypt()] - * with the current [$this->]mode of operation code - * - * - create the $inline function, which called by encrypt() / decrypt() - * as its replacement to speed up the en/decryption operations. - * - * - return the name of the created $inline callback function - * - * - used to speed up en/decryption - * - * - * - * The main reason why can speed up things [up to 50%] this way are: - * - * - using variables more effective then regular. - * (ie no use of expensive arrays but integers $k_0, $k_1 ... - * or even, for example, the pure $key[] values hardcoded) - * - * - avoiding 1000's of function calls of ie _encryptBlock() - * but inlining the crypt operations. - * in the mode of operation for() loop. - * - * - full loop unroll the (sometimes key-dependent) rounds - * avoiding this way ++$i counters and runtime-if's etc... - * - * The basic code architectur of the generated $inline en/decrypt() - * lambda function, in pseudo php, is: - * - * - * +----------------------------------------------------------------------------------------------+ - * | callback $inline = create_function: | - * | lambda_function_0001_crypt_ECB($action, $text) | - * | { | - * | INSERT PHP CODE OF: | - * | $cipher_code['init_crypt']; // general init code. | - * | // ie: $sbox'es declarations used for | - * | // encrypt and decrypt'ing. | - * | | - * | switch ($action) { | - * | case 'encrypt': | - * | INSERT PHP CODE OF: | - * | $cipher_code['init_encrypt']; // encrypt sepcific init code. | - * | ie: specified $key or $box | - * | declarations for encrypt'ing. | - * | | - * | foreach ($ciphertext) { | - * | $in = $block_size of $ciphertext; | - * | | - * | INSERT PHP CODE OF: | - * | $cipher_code['encrypt_block']; // encrypt's (string) $in, which is always: | - * | // strlen($in) == $this->block_size | - * | // here comes the cipher algorithm in action | - * | // for encryption. | - * | // $cipher_code['encrypt_block'] has to | - * | // encrypt the content of the $in variable | - * | | - * | $plaintext .= $in; | - * | } | - * | return $plaintext; | - * | | - * | case 'decrypt': | - * | INSERT PHP CODE OF: | - * | $cipher_code['init_decrypt']; // decrypt sepcific init code | - * | ie: specified $key or $box | - * | declarations for decrypt'ing. | - * | foreach ($plaintext) { | - * | $in = $block_size of $plaintext; | - * | | - * | INSERT PHP CODE OF: | - * | $cipher_code['decrypt_block']; // decrypt's (string) $in, which is always | - * | // strlen($in) == $this->block_size | - * | // here comes the cipher algorithm in action | - * | // for decryption. | - * | // $cipher_code['decrypt_block'] has to | - * | // decrypt the content of the $in variable | - * | $ciphertext .= $in; | - * | } | - * | return $ciphertext; | - * | } | - * | } | - * +----------------------------------------------------------------------------------------------+ - * - * - * See also the \phpseclib\Crypt\*::_setupInlineCrypt()'s for - * productive inline $cipher_code's how they works. - * - * Structure of: - * - * $cipher_code = array( - * 'init_crypt' => (string) '', // optional - * 'init_encrypt' => (string) '', // optional - * 'init_decrypt' => (string) '', // optional - * 'encrypt_block' => (string) '', // required - * 'decrypt_block' => (string) '' // required - * ); - * - * - * @see self::_setupInlineCrypt() - * @see self::encrypt() - * @see self::decrypt() - * @param array $cipher_code - * @access private - * @return string (the name of the created callback function) - */ - function _createInlineCryptFunction($cipher_code) - { - $block_size = $this->block_size; - - // optional - $init_crypt = isset($cipher_code['init_crypt']) ? $cipher_code['init_crypt'] : ''; - $init_encrypt = isset($cipher_code['init_encrypt']) ? $cipher_code['init_encrypt'] : ''; - $init_decrypt = isset($cipher_code['init_decrypt']) ? $cipher_code['init_decrypt'] : ''; - // required - $encrypt_block = $cipher_code['encrypt_block']; - $decrypt_block = $cipher_code['decrypt_block']; - - // Generating mode of operation inline code, - // merged with the $cipher_code algorithm - // for encrypt- and decryption. - switch ($this->mode) { - case self::MODE_ECB: - $encrypt = $init_encrypt . ' - $_ciphertext = ""; - $_plaintext_len = strlen($_text); - - for ($_i = 0; $_i < $_plaintext_len; $_i+= '.$block_size.') { - $in = substr($_text, $_i, '.$block_size.'); - '.$encrypt_block.' - $_ciphertext.= $in; - } - - return $_ciphertext; - '; - - $decrypt = $init_decrypt . ' - $_plaintext = ""; - $_text = str_pad($_text, strlen($_text) + ('.$block_size.' - strlen($_text) % '.$block_size.') % '.$block_size.', chr(0)); - $_ciphertext_len = strlen($_text); - - for ($_i = 0; $_i < $_ciphertext_len; $_i+= '.$block_size.') { - $in = substr($_text, $_i, '.$block_size.'); - '.$decrypt_block.' - $_plaintext.= $in; - } - - return $self->_unpad($_plaintext); - '; - break; - case self::MODE_CTR: - $encrypt = $init_encrypt . ' - $_ciphertext = ""; - $_plaintext_len = strlen($_text); - $_xor = $self->encryptIV; - $_buffer = &$self->enbuffer; - if (strlen($_buffer["ciphertext"])) { - for ($_i = 0; $_i < $_plaintext_len; $_i+= '.$block_size.') { - $_block = substr($_text, $_i, '.$block_size.'); - if (strlen($_block) > strlen($_buffer["ciphertext"])) { - $in = $_xor; - '.$encrypt_block.' - $self->_increment_str($_xor); - $_buffer["ciphertext"].= $in; - } - $_key = $self->_string_shift($_buffer["ciphertext"], '.$block_size.'); - $_ciphertext.= $_block ^ $_key; - } - } else { - for ($_i = 0; $_i < $_plaintext_len; $_i+= '.$block_size.') { - $_block = substr($_text, $_i, '.$block_size.'); - $in = $_xor; - '.$encrypt_block.' - $self->_increment_str($_xor); - $_key = $in; - $_ciphertext.= $_block ^ $_key; - } - } - if ($self->continuousBuffer) { - $self->encryptIV = $_xor; - if ($_start = $_plaintext_len % '.$block_size.') { - $_buffer["ciphertext"] = substr($_key, $_start) . $_buffer["ciphertext"]; - } - } - - return $_ciphertext; - '; - - $decrypt = $init_encrypt . ' - $_plaintext = ""; - $_ciphertext_len = strlen($_text); - $_xor = $self->decryptIV; - $_buffer = &$self->debuffer; - - if (strlen($_buffer["ciphertext"])) { - for ($_i = 0; $_i < $_ciphertext_len; $_i+= '.$block_size.') { - $_block = substr($_text, $_i, '.$block_size.'); - if (strlen($_block) > strlen($_buffer["ciphertext"])) { - $in = $_xor; - '.$encrypt_block.' - $self->_increment_str($_xor); - $_buffer["ciphertext"].= $in; - } - $_key = $self->_string_shift($_buffer["ciphertext"], '.$block_size.'); - $_plaintext.= $_block ^ $_key; - } - } else { - for ($_i = 0; $_i < $_ciphertext_len; $_i+= '.$block_size.') { - $_block = substr($_text, $_i, '.$block_size.'); - $in = $_xor; - '.$encrypt_block.' - $self->_increment_str($_xor); - $_key = $in; - $_plaintext.= $_block ^ $_key; - } - } - if ($self->continuousBuffer) { - $self->decryptIV = $_xor; - if ($_start = $_ciphertext_len % '.$block_size.') { - $_buffer["ciphertext"] = substr($_key, $_start) . $_buffer["ciphertext"]; - } - } - - return $_plaintext; - '; - break; - case self::MODE_CFB: - $encrypt = $init_encrypt . ' - $_ciphertext = ""; - $_buffer = &$self->enbuffer; - - if ($self->continuousBuffer) { - $_iv = &$self->encryptIV; - $_pos = &$_buffer["pos"]; - } else { - $_iv = $self->encryptIV; - $_pos = 0; - } - $_len = strlen($_text); - $_i = 0; - if ($_pos) { - $_orig_pos = $_pos; - $_max = '.$block_size.' - $_pos; - if ($_len >= $_max) { - $_i = $_max; - $_len-= $_max; - $_pos = 0; - } else { - $_i = $_len; - $_pos+= $_len; - $_len = 0; - } - $_ciphertext = substr($_iv, $_orig_pos) ^ $_text; - $_iv = substr_replace($_iv, $_ciphertext, $_orig_pos, $_i); - } - while ($_len >= '.$block_size.') { - $in = $_iv; - '.$encrypt_block.'; - $_iv = $in ^ substr($_text, $_i, '.$block_size.'); - $_ciphertext.= $_iv; - $_len-= '.$block_size.'; - $_i+= '.$block_size.'; - } - if ($_len) { - $in = $_iv; - '.$encrypt_block.' - $_iv = $in; - $_block = $_iv ^ substr($_text, $_i); - $_iv = substr_replace($_iv, $_block, 0, $_len); - $_ciphertext.= $_block; - $_pos = $_len; - } - return $_ciphertext; - '; - - $decrypt = $init_encrypt . ' - $_plaintext = ""; - $_buffer = &$self->debuffer; - - if ($self->continuousBuffer) { - $_iv = &$self->decryptIV; - $_pos = &$_buffer["pos"]; - } else { - $_iv = $self->decryptIV; - $_pos = 0; - } - $_len = strlen($_text); - $_i = 0; - if ($_pos) { - $_orig_pos = $_pos; - $_max = '.$block_size.' - $_pos; - if ($_len >= $_max) { - $_i = $_max; - $_len-= $_max; - $_pos = 0; - } else { - $_i = $_len; - $_pos+= $_len; - $_len = 0; - } - $_plaintext = substr($_iv, $_orig_pos) ^ $_text; - $_iv = substr_replace($_iv, substr($_text, 0, $_i), $_orig_pos, $_i); - } - while ($_len >= '.$block_size.') { - $in = $_iv; - '.$encrypt_block.' - $_iv = $in; - $cb = substr($_text, $_i, '.$block_size.'); - $_plaintext.= $_iv ^ $cb; - $_iv = $cb; - $_len-= '.$block_size.'; - $_i+= '.$block_size.'; - } - if ($_len) { - $in = $_iv; - '.$encrypt_block.' - $_iv = $in; - $_plaintext.= $_iv ^ substr($_text, $_i); - $_iv = substr_replace($_iv, substr($_text, $_i), 0, $_len); - $_pos = $_len; - } - - return $_plaintext; - '; - break; - case self::MODE_CFB8: - $encrypt = $init_encrypt . ' - $_ciphertext = ""; - $_len = strlen($_text); - $_iv = $self->encryptIV; - - for ($_i = 0; $_i < $_len; ++$_i) { - $in = $_iv; - '.$encrypt_block.' - $_ciphertext .= ($_c = $_text[$_i] ^ $in); - $_iv = substr($_iv, 1) . $_c; - } - - if ($self->continuousBuffer) { - if ($_len >= '.$block_size.') { - $self->encryptIV = substr($_ciphertext, -'.$block_size.'); - } else { - $self->encryptIV = substr($self->encryptIV, $_len - '.$block_size.') . substr($_ciphertext, -$_len); - } - } - - return $_ciphertext; - '; - $decrypt = $init_encrypt . ' - $_plaintext = ""; - $_len = strlen($_text); - $_iv = $self->decryptIV; - - for ($_i = 0; $_i < $_len; ++$_i) { - $in = $_iv; - '.$encrypt_block.' - $_plaintext .= $_text[$_i] ^ $in; - $_iv = substr($_iv, 1) . $_text[$_i]; - } - - if ($self->continuousBuffer) { - if ($_len >= '.$block_size.') { - $self->decryptIV = substr($_text, -'.$block_size.'); - } else { - $self->decryptIV = substr($self->decryptIV, $_len - '.$block_size.') . substr($_text, -$_len); - } - } - - return $_plaintext; - '; - break; - case self::MODE_OFB: - $encrypt = $init_encrypt . ' - $_ciphertext = ""; - $_plaintext_len = strlen($_text); - $_xor = $self->encryptIV; - $_buffer = &$self->enbuffer; - - if (strlen($_buffer["xor"])) { - for ($_i = 0; $_i < $_plaintext_len; $_i+= '.$block_size.') { - $_block = substr($_text, $_i, '.$block_size.'); - if (strlen($_block) > strlen($_buffer["xor"])) { - $in = $_xor; - '.$encrypt_block.' - $_xor = $in; - $_buffer["xor"].= $_xor; - } - $_key = $self->_string_shift($_buffer["xor"], '.$block_size.'); - $_ciphertext.= $_block ^ $_key; - } - } else { - for ($_i = 0; $_i < $_plaintext_len; $_i+= '.$block_size.') { - $in = $_xor; - '.$encrypt_block.' - $_xor = $in; - $_ciphertext.= substr($_text, $_i, '.$block_size.') ^ $_xor; - } - $_key = $_xor; - } - if ($self->continuousBuffer) { - $self->encryptIV = $_xor; - if ($_start = $_plaintext_len % '.$block_size.') { - $_buffer["xor"] = substr($_key, $_start) . $_buffer["xor"]; - } - } - return $_ciphertext; - '; - - $decrypt = $init_encrypt . ' - $_plaintext = ""; - $_ciphertext_len = strlen($_text); - $_xor = $self->decryptIV; - $_buffer = &$self->debuffer; - - if (strlen($_buffer["xor"])) { - for ($_i = 0; $_i < $_ciphertext_len; $_i+= '.$block_size.') { - $_block = substr($_text, $_i, '.$block_size.'); - if (strlen($_block) > strlen($_buffer["xor"])) { - $in = $_xor; - '.$encrypt_block.' - $_xor = $in; - $_buffer["xor"].= $_xor; - } - $_key = $self->_string_shift($_buffer["xor"], '.$block_size.'); - $_plaintext.= $_block ^ $_key; - } - } else { - for ($_i = 0; $_i < $_ciphertext_len; $_i+= '.$block_size.') { - $in = $_xor; - '.$encrypt_block.' - $_xor = $in; - $_plaintext.= substr($_text, $_i, '.$block_size.') ^ $_xor; - } - $_key = $_xor; - } - if ($self->continuousBuffer) { - $self->decryptIV = $_xor; - if ($_start = $_ciphertext_len % '.$block_size.') { - $_buffer["xor"] = substr($_key, $_start) . $_buffer["xor"]; - } - } - return $_plaintext; - '; - break; - case self::MODE_STREAM: - $encrypt = $init_encrypt . ' - $_ciphertext = ""; - '.$encrypt_block.' - return $_ciphertext; - '; - $decrypt = $init_decrypt . ' - $_plaintext = ""; - '.$decrypt_block.' - return $_plaintext; - '; - break; - // case self::MODE_CBC: - default: - $encrypt = $init_encrypt . ' - $_ciphertext = ""; - $_plaintext_len = strlen($_text); - - $in = $self->encryptIV; - - for ($_i = 0; $_i < $_plaintext_len; $_i+= '.$block_size.') { - $in = substr($_text, $_i, '.$block_size.') ^ $in; - '.$encrypt_block.' - $_ciphertext.= $in; - } - - if ($self->continuousBuffer) { - $self->encryptIV = $in; - } - - return $_ciphertext; - '; - - $decrypt = $init_decrypt . ' - $_plaintext = ""; - $_text = str_pad($_text, strlen($_text) + ('.$block_size.' - strlen($_text) % '.$block_size.') % '.$block_size.', chr(0)); - $_ciphertext_len = strlen($_text); - - $_iv = $self->decryptIV; - - for ($_i = 0; $_i < $_ciphertext_len; $_i+= '.$block_size.') { - $in = $_block = substr($_text, $_i, '.$block_size.'); - '.$decrypt_block.' - $_plaintext.= $in ^ $_iv; - $_iv = $_block; - } - - if ($self->continuousBuffer) { - $self->decryptIV = $_iv; - } - - return $self->_unpad($_plaintext); - '; - break; - } - - // Create the $inline function and return its name as string. Ready to run! - eval('$func = function ($_action, &$self, $_text) { ' . $init_crypt . 'if ($_action == "encrypt") { ' . $encrypt . ' } else { ' . $decrypt . ' } };'); - return $func; - } - - /** - * Holds the lambda_functions table (classwide) - * - * Each name of the lambda function, created from - * _setupInlineCrypt() && _createInlineCryptFunction() - * is stored, classwide (!), here for reusing. - * - * The string-based index of $function is a classwide - * unique value representing, at least, the $mode of - * operation (or more... depends of the optimizing level) - * for which $mode the lambda function was created. - * - * @access private - * @return array &$functions - */ - function &_getLambdaFunctions() - { - static $functions = array(); - return $functions; - } - - /** - * Generates a digest from $bytes - * - * @see self::_setupInlineCrypt() - * @access private - * @param string $bytes - * @return string - */ - function _hashInlineCryptFunction($bytes) - { - if (!isset(self::$WHIRLPOOL_AVAILABLE)) { - self::$WHIRLPOOL_AVAILABLE = extension_loaded('hash') && in_array('whirlpool', hash_algos()); - } - - $result = ''; - $hash = $bytes; - - switch (true) { - case self::$WHIRLPOOL_AVAILABLE: - foreach (str_split($bytes, 64) as $t) { - $hash = hash('whirlpool', $hash, true); - $result .= $t ^ $hash; - } - return $result . hash('whirlpool', $hash, true); - default: - $len = strlen($bytes); - for ($i = 0; $i < $len; $i+=20) { - $t = substr($bytes, $i, 20); - $hash = pack('H*', sha1($hash)); - $result .= $t ^ $hash; - } - return $result . pack('H*', sha1($hash)); - } - } - - /** - * Convert float to int - * - * On ARM CPUs converting floats to ints doesn't always work - * - * @access private - * @param string $x - * @return int - */ - function safe_intval($x) - { - switch (true) { - case is_int($x): - // PHP 5.3, per http://php.net/releases/5_3_0.php, introduced "more consistent float rounding" - case (php_uname('m') & "\xDF\xDF\xDF") != 'ARM': - return $x; - } - return (fmod($x, 0x80000000) & 0x7FFFFFFF) | - ((fmod(floor($x / 0x80000000), 2) & 1) << 31); - } - - /** - * eval()'able string for in-line float to int - * - * @access private - * @return string - */ - function safe_intval_inline() - { - switch (true) { - case defined('PHP_INT_SIZE') && PHP_INT_SIZE == 8: - case (php_uname('m') & "\xDF\xDF\xDF") != 'ARM': - return '%s'; - break; - default: - $safeint = '(is_int($temp = %s) ? $temp : (fmod($temp, 0x80000000) & 0x7FFFFFFF) | '; - return $safeint . '((fmod(floor($temp / 0x80000000), 2) & 1) << 31))'; - } - } - - /** - * Dummy error handler to suppress mcrypt errors - * - * @access private - */ - function do_nothing() - { - } -} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/Blowfish.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Blowfish.php index 74cc49de..9a33dd0f 100644 --- a/vendor/phpseclib/phpseclib/phpseclib/Crypt/Blowfish.php +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Blowfish.php @@ -11,12 +11,93 @@ * * - {@link http://en.wikipedia.org/wiki/Blowfish_(cipher) Wikipedia description of Blowfish} * + * # An overview of bcrypt vs Blowfish + * + * OpenSSH private keys use a customized version of bcrypt. Specifically, instead of + * encrypting OrpheanBeholderScryDoubt 64 times OpenSSH's bcrypt variant encrypts + * OxychromaticBlowfishSwatDynamite 64 times. so we can't use crypt(). + * + * bcrypt is basically Blowfish but instead of performing the key expansion once it performs + * the expansion 129 times for each round, with the first key expansion interleaving the salt + * and password. This renders OpenSSL unusable and forces us to use a pure-PHP implementation + * of blowfish. + * + * # phpseclib's four different _encryptBlock() implementations + * + * When using Blowfish as an encryption algorithm, _encryptBlock() is called 9 + 512 + + * (the number of blocks in the plaintext) times. + * + * Each of the first 9 calls to _encryptBlock() modify the P-array. Each of the next 512 + * calls modify the S-boxes. The remaining _encryptBlock() calls operate on the plaintext to + * produce the ciphertext. In the pure-PHP implementation of Blowfish these remaining + * _encryptBlock() calls are highly optimized through the use of eval(). Among other things, + * P-array lookups are eliminated by hard-coding the key-dependent P-array values, and thus we + * have explained 2 of the 4 different _encryptBlock() implementations. + * + * With bcrypt things are a bit different. _encryptBlock() is called 1,079,296 times, + * assuming 16 rounds (which is what OpenSSH's bcrypt defaults to). The eval()-optimized + * _encryptBlock() isn't as beneficial because the P-array values are not constant. Well, they + * are constant, but only for, at most, 777 _encryptBlock() calls, which is equivalent to ~6KB + * of data. The average length of back to back _encryptBlock() calls with a fixed P-array is + * 514.12, which is ~4KB of data. Creating an eval()-optimized _encryptBlock() has an upfront + * cost, which is CPU dependent and is probably not going to be worth it for just ~4KB of + * data. Conseqeuently, bcrypt does not benefit from the eval()-optimized _encryptBlock(). + * + * The regular _encryptBlock() does unpack() and pack() on every call, as well, and that can + * begin to add up after one million function calls. + * + * In theory, one might think that it might be beneficial to rewrite all block ciphers so + * that, instead of passing strings to _encryptBlock(), you convert the string to an array of + * integers and then pass successive subarrays of that array to _encryptBlock. This, however, + * kills PHP's memory use. Like let's say you have a 1MB long string. After doing + * $in = str_repeat('a', 1024 * 1024); PHP's memory utilization jumps up by ~1MB. After doing + * $blocks = str_split($in, 4); it jumps up by an additional ~16MB. After + * $blocks = array_map(fn($x) => unpack('N*', $x), $blocks); it jumps up by an additional + * ~90MB, yielding a 106x increase in memory usage. Consequently, it bcrypt calls a different + * _encryptBlock() then the regular Blowfish does. That said, the Blowfish _encryptBlock() is + * basically just a thin wrapper around the bcrypt _encryptBlock(), so there's that. + * + * This explains 3 of the 4 _encryptBlock() implementations. the last _encryptBlock() + * implementation can best be understood by doing Ctrl + F and searching for where + * self::$use_reg_intval is defined. + * + * # phpseclib's three different _setupKey() implementations + * + * Every bcrypt round is the equivalent of encrypting 512KB of data. Since OpenSSH uses 16 + * rounds by default that's ~8MB of data that's essentially being encrypted whenever + * you use bcrypt. That's a lot of data, however, bcrypt operates within tighter constraints + * than regular Blowfish, so we can use that to our advantage. In particular, whereas Blowfish + * supports variable length keys, in bcrypt, the initial "key" is the sha512 hash of the + * password. sha512 hashes are 512 bits or 64 bytes long and thus the bcrypt keys are of a + * fixed length whereas Blowfish keys are not of a fixed length. + * + * bcrypt actually has two different key expansion steps. The first one (expandstate) is + * constantly XOR'ing every _encryptBlock() parameter against the salt prior _encryptBlock()'s + * being called. The second one (expand0state) is more similar to Blowfish's _setupKey() + * but it can still use the fixed length key optimization discussed above and can do away with + * the pack() / unpack() calls. + * + * I suppose _setupKey() could be made to be a thin wrapper around expandstate() but idk it's + * just a lot of work for very marginal benefits as _setupKey() is only called once for + * regular Blowfish vs the 128 times it's called --per round-- with bcrypt. + * + * # blowfish + bcrypt in the same class + * + * Altho there's a lot of Blowfish code that bcrypt doesn't re-use, bcrypt does re-use the + * initial S-boxes, the initial P-array and the int-only _encryptBlock() implementation. + * + * # Credit + * + * phpseclib's bcrypt implementation is based losely off of OpenSSH's implementation: + * + * https://github.com/openssh/openssh-portable/blob/master/openbsd-compat/bcrypt_pbkdf.c + * * Here's a short example of how to use this library: * * setKey('12345678901234567890123456789012'); * @@ -26,8 +107,6 @@ * ?> * * - * @category Crypt - * @package Blowfish * @author Jim Wigginton * @author Hans-Juergen Petrich * @copyright 2007 Jim Wigginton @@ -35,54 +114,50 @@ * @link http://phpseclib.sourceforge.net */ -namespace phpseclib\Crypt; +namespace phpseclib3\Crypt; + +use phpseclib3\Crypt\Common\BlockCipher; /** * Pure-PHP implementation of Blowfish. * - * @package Blowfish * @author Jim Wigginton * @author Hans-Juergen Petrich - * @access public */ -class Blowfish extends Base +class Blowfish extends BlockCipher { /** * Block Length of the cipher * - * @see \phpseclib\Crypt\Base::block_size + * @see \phpseclib3\Crypt\Common\SymmetricKey::block_size * @var int - * @access private */ - var $block_size = 8; + protected $block_size = 8; /** * The mcrypt specific name of the cipher * - * @see \phpseclib\Crypt\Base::cipher_name_mcrypt + * @see \phpseclib3\Crypt\Common\SymmetricKey::cipher_name_mcrypt * @var string - * @access private */ - var $cipher_name_mcrypt = 'blowfish'; + protected $cipher_name_mcrypt = 'blowfish'; /** * Optimizing value while CFB-encrypting * - * @see \phpseclib\Crypt\Base::cfb_init_len + * @see \phpseclib3\Crypt\Common\SymmetricKey::cfb_init_len * @var int - * @access private */ - var $cfb_init_len = 500; + protected $cfb_init_len = 500; /** * The fixed subkeys boxes ($sbox0 - $sbox3) with 256 entries each * * S-Box 0 * - * @access private * @var array */ - var $sbox0 = array( + private static $sbox0 = [ 0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7, 0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99, 0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16, 0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e, 0x0d95748f, 0x728eb658, 0x718bcd58, 0x82154aee, 0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013, @@ -115,15 +190,14 @@ class Blowfish extends Base 0x83260376, 0x6295cfa9, 0x11c81968, 0x4e734a41, 0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915, 0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400, 0x08ba6fb5, 0x571be91f, 0xf296ec6b, 0x2a0dd915, 0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664, 0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a - ); + ]; /** * S-Box 1 * - * @access private * @var array */ - var $sbox1 = array( + private static $sbox1 = [ 0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623, 0xad6ea6b0, 0x49a7df7d, 0x9cee60b8, 0x8fedb266, 0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1, 0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e, 0x3f54989a, 0x5b429d65, 0x6b8fe4d6, 0x99f73fd6, 0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1, @@ -156,15 +230,14 @@ class Blowfish extends Base 0xa6078084, 0x19f8509e, 0xe8efd855, 0x61d99735, 0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc, 0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9, 0xdb73dbd3, 0x105588cd, 0x675fda79, 0xe3674340, 0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20, 0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7 - ); + ]; /** * S-Box 2 * - * @access private * @var array */ - var $sbox2 = array( + private static $sbox2 = [ 0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934, 0x411520f7, 0x7602d4f7, 0xbcf46b2e, 0xd4a20068, 0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af, 0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840, 0x4d95fc1d, 0x96b591af, 0x70f4ddd3, 0x66a02f45, 0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504, @@ -197,15 +270,14 @@ class Blowfish extends Base 0x6f05e409, 0x4b7c0188, 0x39720a3d, 0x7c927c24, 0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc, 0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4, 0x1e50ef5e, 0xb161e6f8, 0xa28514d9, 0x6c51133c, 0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837, 0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0 - ); + ]; /** * S-Box 3 * - * @access private * @var array */ - var $sbox3 = array( + private static $sbox3 = [ 0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b, 0x5cb0679e, 0x4fa33742, 0xd3822740, 0x99bc9bbe, 0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b, 0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4, 0x5748ab2f, 0xbc946e79, 0xc6a376d2, 0x6549c2c8, 0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6, @@ -238,19 +310,18 @@ class Blowfish extends Base 0x53113ec0, 0x1640e3d3, 0x38abbd60, 0x2547adf0, 0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060, 0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e, 0x1948c25c, 0x02fb8a8c, 0x01c36ae4, 0xd6ebe1f9, 0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f, 0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6 - ); + ]; /** * P-Array consists of 18 32-bit subkeys * * @var array - * @access private */ - var $parray = array( + private static $parray = [ 0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344, 0xa4093822, 0x299f31d0, 0x082efa98, 0xec4e6c89, 0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c, 0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917, 0x9216d5d9, 0x8979fb1b - ); + ]; /** * The BCTX-working Array @@ -258,63 +329,71 @@ class Blowfish extends Base * Holds the expanded key [p] and the key-depended s-boxes [sb] * * @var array - * @access private */ - var $bctx; + private $bctx; /** * Holds the last used key * * @var array - * @access private */ - var $kl; + private $kl; /** * The Key Length (in bytes) - * - * @see \phpseclib\Crypt\Base::setKeyLength() - * @var int - * @access private - * @internal The max value is 256 / 8 = 32, the min value is 128 / 8 = 16. Exists in conjunction with $Nk + * {@internal The max value is 256 / 8 = 32, the min value is 128 / 8 = 16. Exists in conjunction with $Nk * because the encryption / decryption / key schedule creation requires this number and not $key_length. We could * derive this from $key_length or vice versa, but that'd mean we'd have to do multiple shift operations, so in lieu - * of that, we'll just precompute it once. + * of that, we'll just precompute it once.} + * + * @see \phpseclib3\Crypt\Common\SymmetricKey::setKeyLength() + * @var int */ - var $key_length = 16; + protected $key_length = 16; + + /** + * Default Constructor. + * + * @param string $mode + * @throws \InvalidArgumentException if an invalid / unsupported mode is provided + */ + public function __construct($mode) + { + parent::__construct($mode); + + if ($this->mode == self::MODE_STREAM) { + throw new \InvalidArgumentException('Block ciphers cannot be ran in stream mode'); + } + } /** * Sets the key length. * * Key lengths can be between 32 and 448 bits. * - * @access public * @param int $length */ - function setKeyLength($length) + public function setKeyLength($length) { - if ($length < 32) { - $this->key_length = 4; - } elseif ($length > 448) { - $this->key_length = 56; - } else { - $this->key_length = $length >> 3; + if ($length < 32 || $length > 448) { + throw new \LengthException('Key size of ' . $length . ' bits is not supported by this algorithm. Only keys of sizes between 32 and 448 bits are supported'); } + $this->key_length = $length >> 3; + parent::setKeyLength($length); } /** * Test for engine validity * - * This is mainly just a wrapper to set things up for \phpseclib\Crypt\Base::isValidEngine() + * This is mainly just a wrapper to set things up for \phpseclib3\Crypt\Common\SymmetricKey::isValidEngine() * - * @see \phpseclib\Crypt\Base::isValidEngine() + * @see \phpseclib3\Crypt\Common\SymmetricKey::isValidEngine() * @param int $engine - * @access public * @return bool */ - function isValidEngine($engine) + protected function isValidEngineHelper($engine) { if ($engine == self::ENGINE_OPENSSL) { if (version_compare(PHP_VERSION, '5.3.7') < 0 && $this->key_length != 16) { @@ -324,40 +403,40 @@ function isValidEngine($engine) return false; } $this->cipher_name_openssl_ecb = 'bf-ecb'; - $this->cipher_name_openssl = 'bf-' . $this->_openssl_translate_mode(); + $this->cipher_name_openssl = 'bf-' . $this->openssl_translate_mode(); } - return parent::isValidEngine($engine); + return parent::isValidEngineHelper($engine); } /** * Setup the key (expansion) * - * @see \phpseclib\Crypt\Base::_setupKey() - * @access private + * @see \phpseclib3\Crypt\Common\SymmetricKey::_setupKey() */ - function _setupKey() + protected function setupKey() { if (isset($this->kl['key']) && $this->key === $this->kl['key']) { // already expanded return; } - $this->kl = array('key' => $this->key); + $this->kl = ['key' => $this->key]; /* key-expanding p[] and S-Box building sb[] */ - $this->bctx = array( - 'p' => array(), - 'sb' => array( - $this->sbox0, - $this->sbox1, - $this->sbox2, - $this->sbox3 - ) - ); + $this->bctx = [ + 'p' => [], + 'sb' => [ + self::$sbox0, + self::$sbox1, + self::$sbox2, + self::$sbox3 + ] + ]; // unpack binary string in unsigned chars $key = array_values(unpack('C*', $this->key)); $keyl = count($key); + // with bcrypt $keyl will always be 16 (because the key is the sha512 of the key you provide) for ($j = 0, $i = 0; $i < 18; ++$i) { // xor P1 with the first 32-bits of the key, xor P2 with the second 32-bits ... for ($data = 0, $k = 0; $k < 4; ++$k) { @@ -366,20 +445,20 @@ function _setupKey() $j = 0; } } - $this->bctx['p'][] = $this->parray[$i] ^ $data; + $this->bctx['p'][] = self::$parray[$i] ^ intval($data); } // encrypt the zero-string, replace P1 and P2 with the encrypted data, // encrypt P3 and P4 with the new P1 and P2, do it with all P-array and subkeys $data = "\0\0\0\0\0\0\0\0"; for ($i = 0; $i < 18; $i += 2) { - list($l, $r) = array_values(unpack('N*', $data = $this->_encryptBlock($data))); + list($l, $r) = array_values(unpack('N*', $data = $this->encryptBlock($data))); $this->bctx['p'][$i ] = $l; $this->bctx['p'][$i + 1] = $r; } for ($i = 0; $i < 4; ++$i) { for ($j = 0; $j < 256; $j += 2) { - list($l, $r) = array_values(unpack('N*', $data = $this->_encryptBlock($data))); + list($l, $r) = array_values(unpack('N*', $data = $this->encryptBlock($data))); $this->bctx['sb'][$i][$j ] = $l; $this->bctx['sb'][$i][$j + 1] = $r; } @@ -387,185 +466,450 @@ function _setupKey() } /** - * Encrypts a block + * Initialize Static Variables + */ + protected static function initialize_static_variables() + { + if (is_float(self::$sbox2[0])) { + self::$sbox0 = array_map('intval', self::$sbox0); + self::$sbox1 = array_map('intval', self::$sbox1); + self::$sbox2 = array_map('intval', self::$sbox2); + self::$sbox3 = array_map('intval', self::$sbox3); + self::$parray = array_map('intval', self::$parray); + } + + parent::initialize_static_variables(); + } + + /** + * bcrypt + * + * @param string $sha2pass + * @param string $sha2salt + * @access private + * @return string + */ + private static function bcrypt_hash($sha2pass, $sha2salt) + { + $p = self::$parray; + $sbox0 = self::$sbox0; + $sbox1 = self::$sbox1; + $sbox2 = self::$sbox2; + $sbox3 = self::$sbox3; + + $cdata = array_values(unpack('N*', 'OxychromaticBlowfishSwatDynamite')); + $sha2pass = array_values(unpack('N*', $sha2pass)); + $sha2salt = array_values(unpack('N*', $sha2salt)); + + self::expandstate($sha2salt, $sha2pass, $sbox0, $sbox1, $sbox2, $sbox3, $p); + for ($i = 0; $i < 64; $i++) { + self::expand0state($sha2salt, $sbox0, $sbox1, $sbox2, $sbox3, $p); + self::expand0state($sha2pass, $sbox0, $sbox1, $sbox2, $sbox3, $p); + } + + for ($i = 0; $i < 64; $i++) { + for ($j = 0; $j < 8; $j += 2) { // count($cdata) == 8 + list($cdata[$j], $cdata[$j + 1]) = self::encryptBlockHelperFast($cdata[$j], $cdata[$j + 1], $sbox0, $sbox1, $sbox2, $sbox3, $p); + } + } + + return pack('L*', ...$cdata); + } + + /** + * Performs OpenSSH-style bcrypt + * + * @param string $pass + * @param string $salt + * @param int $keylen + * @param int $rounds + * @access public + * @return string + */ + public static function bcrypt_pbkdf($pass, $salt, $keylen, $rounds) + { + self::initialize_static_variables(); + + if (PHP_INT_SIZE == 4) { + throw new \RuntimeException('bcrypt is far too slow to be practical on 32-bit versions of PHP'); + } + + $sha2pass = hash('sha512', $pass, true); + $results = []; + $count = 1; + while (32 * count($results) < $keylen) { + $countsalt = $salt . pack('N', $count++); + $sha2salt = hash('sha512', $countsalt, true); + $out = $tmpout = self::bcrypt_hash($sha2pass, $sha2salt); + for ($i = 1; $i < $rounds; $i++) { + $sha2salt = hash('sha512', $tmpout, true); + $tmpout = self::bcrypt_hash($sha2pass, $sha2salt); + $out ^= $tmpout; + } + $results[] = $out; + } + $output = ''; + for ($i = 0; $i < 32; $i++) { + foreach ($results as $result) { + $output .= $result[$i]; + } + } + return substr($output, 0, $keylen); + } + + /** + * Key expansion without salt + * + * @access private + * @param int[] $key + * @param int[] $sbox0 + * @param int[] $sbox1 + * @param int[] $sbox2 + * @param int[] $sbox3 + * @param int[] $p + * @see self::_bcrypt_hash() + */ + private static function expand0state(array $key, array &$sbox0, array &$sbox1, array &$sbox2, array &$sbox3, array &$p) + { + // expand0state is basically the same thing as this: + //return self::expandstate(array_fill(0, 16, 0), $key); + // but this separate function eliminates a bunch of XORs and array lookups + + $p = [ + $p[0] ^ $key[0], + $p[1] ^ $key[1], + $p[2] ^ $key[2], + $p[3] ^ $key[3], + $p[4] ^ $key[4], + $p[5] ^ $key[5], + $p[6] ^ $key[6], + $p[7] ^ $key[7], + $p[8] ^ $key[8], + $p[9] ^ $key[9], + $p[10] ^ $key[10], + $p[11] ^ $key[11], + $p[12] ^ $key[12], + $p[13] ^ $key[13], + $p[14] ^ $key[14], + $p[15] ^ $key[15], + $p[16] ^ $key[0], + $p[17] ^ $key[1] + ]; + + // @codingStandardsIgnoreStart + list( $p[0], $p[1]) = self::encryptBlockHelperFast( 0, 0, $sbox0, $sbox1, $sbox2, $sbox3, $p); + list( $p[2], $p[3]) = self::encryptBlockHelperFast($p[ 0], $p[ 1], $sbox0, $sbox1, $sbox2, $sbox3, $p); + list( $p[4], $p[5]) = self::encryptBlockHelperFast($p[ 2], $p[ 3], $sbox0, $sbox1, $sbox2, $sbox3, $p); + list( $p[6], $p[7]) = self::encryptBlockHelperFast($p[ 4], $p[ 5], $sbox0, $sbox1, $sbox2, $sbox3, $p); + list( $p[8], $p[9]) = self::encryptBlockHelperFast($p[ 6], $p[ 7], $sbox0, $sbox1, $sbox2, $sbox3, $p); + list($p[10], $p[11]) = self::encryptBlockHelperFast($p[ 8], $p[ 9], $sbox0, $sbox1, $sbox2, $sbox3, $p); + list($p[12], $p[13]) = self::encryptBlockHelperFast($p[10], $p[11], $sbox0, $sbox1, $sbox2, $sbox3, $p); + list($p[14], $p[15]) = self::encryptBlockHelperFast($p[12], $p[13], $sbox0, $sbox1, $sbox2, $sbox3, $p); + list($p[16], $p[17]) = self::encryptBlockHelperFast($p[14], $p[15], $sbox0, $sbox1, $sbox2, $sbox3, $p); + // @codingStandardsIgnoreEnd + + list($sbox0[0], $sbox0[1]) = self::encryptBlockHelperFast($p[16], $p[17], $sbox0, $sbox1, $sbox2, $sbox3, $p); + for ($i = 2; $i < 256; $i += 2) { + list($sbox0[$i], $sbox0[$i + 1]) = self::encryptBlockHelperFast($sbox0[$i - 2], $sbox0[$i - 1], $sbox0, $sbox1, $sbox2, $sbox3, $p); + } + + list($sbox1[0], $sbox1[1]) = self::encryptBlockHelperFast($sbox0[254], $sbox0[255], $sbox0, $sbox1, $sbox2, $sbox3, $p); + for ($i = 2; $i < 256; $i += 2) { + list($sbox1[$i], $sbox1[$i + 1]) = self::encryptBlockHelperFast($sbox1[$i - 2], $sbox1[$i - 1], $sbox0, $sbox1, $sbox2, $sbox3, $p); + } + + list($sbox2[0], $sbox2[1]) = self::encryptBlockHelperFast($sbox1[254], $sbox1[255], $sbox0, $sbox1, $sbox2, $sbox3, $p); + for ($i = 2; $i < 256; $i += 2) { + list($sbox2[$i], $sbox2[$i + 1]) = self::encryptBlockHelperFast($sbox2[$i - 2], $sbox2[$i - 1], $sbox0, $sbox1, $sbox2, $sbox3, $p); + } + + list($sbox3[0], $sbox3[1]) = self::encryptBlockHelperFast($sbox2[254], $sbox2[255], $sbox0, $sbox1, $sbox2, $sbox3, $p); + for ($i = 2; $i < 256; $i += 2) { + list($sbox3[$i], $sbox3[$i + 1]) = self::encryptBlockHelperFast($sbox3[$i - 2], $sbox3[$i - 1], $sbox0, $sbox1, $sbox2, $sbox3, $p); + } + } + + /** + * Key expansion with salt * * @access private + * @param int[] $data + * @param int[] $key + * @param int[] $sbox0 + * @param int[] $sbox1 + * @param int[] $sbox2 + * @param int[] $sbox3 + * @param int[] $p + * @see self::_bcrypt_hash() + */ + private static function expandstate(array $data, array $key, array &$sbox0, array &$sbox1, array &$sbox2, array &$sbox3, array &$p) + { + $p = [ + $p[0] ^ $key[0], + $p[1] ^ $key[1], + $p[2] ^ $key[2], + $p[3] ^ $key[3], + $p[4] ^ $key[4], + $p[5] ^ $key[5], + $p[6] ^ $key[6], + $p[7] ^ $key[7], + $p[8] ^ $key[8], + $p[9] ^ $key[9], + $p[10] ^ $key[10], + $p[11] ^ $key[11], + $p[12] ^ $key[12], + $p[13] ^ $key[13], + $p[14] ^ $key[14], + $p[15] ^ $key[15], + $p[16] ^ $key[0], + $p[17] ^ $key[1] + ]; + + // @codingStandardsIgnoreStart + list( $p[0], $p[1]) = self::encryptBlockHelperFast($data[ 0] , $data[ 1] , $sbox0, $sbox1, $sbox2, $sbox3, $p); + list( $p[2], $p[3]) = self::encryptBlockHelperFast($data[ 2] ^ $p[ 0], $data[ 3] ^ $p[ 1], $sbox0, $sbox1, $sbox2, $sbox3, $p); + list( $p[4], $p[5]) = self::encryptBlockHelperFast($data[ 4] ^ $p[ 2], $data[ 5] ^ $p[ 3], $sbox0, $sbox1, $sbox2, $sbox3, $p); + list( $p[6], $p[7]) = self::encryptBlockHelperFast($data[ 6] ^ $p[ 4], $data[ 7] ^ $p[ 5], $sbox0, $sbox1, $sbox2, $sbox3, $p); + list( $p[8], $p[9]) = self::encryptBlockHelperFast($data[ 8] ^ $p[ 6], $data[ 9] ^ $p[ 7], $sbox0, $sbox1, $sbox2, $sbox3, $p); + list($p[10], $p[11]) = self::encryptBlockHelperFast($data[10] ^ $p[ 8], $data[11] ^ $p[ 9], $sbox0, $sbox1, $sbox2, $sbox3, $p); + list($p[12], $p[13]) = self::encryptBlockHelperFast($data[12] ^ $p[10], $data[13] ^ $p[11], $sbox0, $sbox1, $sbox2, $sbox3, $p); + list($p[14], $p[15]) = self::encryptBlockHelperFast($data[14] ^ $p[12], $data[15] ^ $p[13], $sbox0, $sbox1, $sbox2, $sbox3, $p); + list($p[16], $p[17]) = self::encryptBlockHelperFast($data[ 0] ^ $p[14], $data[ 1] ^ $p[15], $sbox0, $sbox1, $sbox2, $sbox3, $p); + // @codingStandardsIgnoreEnd + + list($sbox0[0], $sbox0[1]) = self::encryptBlockHelperFast($data[2] ^ $p[16], $data[3] ^ $p[17], $sbox0, $sbox1, $sbox2, $sbox3, $p); + for ($i = 2, $j = 4; $i < 256; $i += 2, $j = ($j + 2) % 16) { // instead of 16 maybe count($data) would be better? + list($sbox0[$i], $sbox0[$i + 1]) = self::encryptBlockHelperFast($data[$j] ^ $sbox0[$i - 2], $data[$j + 1] ^ $sbox0[$i - 1], $sbox0, $sbox1, $sbox2, $sbox3, $p); + } + + list($sbox1[0], $sbox1[1]) = self::encryptBlockHelperFast($data[2] ^ $sbox0[254], $data[3] ^ $sbox0[255], $sbox0, $sbox1, $sbox2, $sbox3, $p); + for ($i = 2, $j = 4; $i < 256; $i += 2, $j = ($j + 2) % 16) { + list($sbox1[$i], $sbox1[$i + 1]) = self::encryptBlockHelperFast($data[$j] ^ $sbox1[$i - 2], $data[$j + 1] ^ $sbox1[$i - 1], $sbox0, $sbox1, $sbox2, $sbox3, $p); + } + + list($sbox2[0], $sbox2[1]) = self::encryptBlockHelperFast($data[2] ^ $sbox1[254], $data[3] ^ $sbox1[255], $sbox0, $sbox1, $sbox2, $sbox3, $p); + for ($i = 2, $j = 4; $i < 256; $i += 2, $j = ($j + 2) % 16) { + list($sbox2[$i], $sbox2[$i + 1]) = self::encryptBlockHelperFast($data[$j] ^ $sbox2[$i - 2], $data[$j + 1] ^ $sbox2[$i - 1], $sbox0, $sbox1, $sbox2, $sbox3, $p); + } + + list($sbox3[0], $sbox3[1]) = self::encryptBlockHelperFast($data[2] ^ $sbox2[254], $data[3] ^ $sbox2[255], $sbox0, $sbox1, $sbox2, $sbox3, $p); + for ($i = 2, $j = 4; $i < 256; $i += 2, $j = ($j + 2) % 16) { + list($sbox3[$i], $sbox3[$i + 1]) = self::encryptBlockHelperFast($data[$j] ^ $sbox3[$i - 2], $data[$j + 1] ^ $sbox3[$i - 1], $sbox0, $sbox1, $sbox2, $sbox3, $p); + } + } + + /** + * Encrypts a block + * * @param string $in * @return string */ - function _encryptBlock($in) + protected function encryptBlock($in) { - $p = $this->bctx["p"]; - // extract($this->bctx["sb"], EXTR_PREFIX_ALL, "sb"); // slower - $sb_0 = $this->bctx["sb"][0]; - $sb_1 = $this->bctx["sb"][1]; - $sb_2 = $this->bctx["sb"][2]; - $sb_3 = $this->bctx["sb"][3]; - - $in = unpack("N*", $in); + $p = $this->bctx['p']; + // extract($this->bctx['sb'], EXTR_PREFIX_ALL, 'sb'); // slower + $sb_0 = $this->bctx['sb'][0]; + $sb_1 = $this->bctx['sb'][1]; + $sb_2 = $this->bctx['sb'][2]; + $sb_3 = $this->bctx['sb'][3]; + + $in = unpack('N*', $in); $l = $in[1]; $r = $in[2]; - for ($i = 0; $i < 16; $i+= 2) { - $l^= $p[$i]; - $r^= $this->safe_intval(($this->safe_intval($sb_0[$l >> 24 & 0xff] + $sb_1[$l >> 16 & 0xff]) ^ - $sb_2[$l >> 8 & 0xff]) + - $sb_3[$l & 0xff]); + list($r, $l) = PHP_INT_SIZE == 4 ? + self::encryptBlockHelperSlow($l, $r, $sb_0, $sb_1, $sb_2, $sb_3, $p) : + self::encryptBlockHelperFast($l, $r, $sb_0, $sb_1, $sb_2, $sb_3, $p); - $r^= $p[$i + 1]; - $l^= $this->safe_intval(($this->safe_intval($sb_0[$r >> 24 & 0xff] + $sb_1[$r >> 16 & 0xff]) ^ - $sb_2[$r >> 8 & 0xff]) + - $sb_3[$r & 0xff]); - } - return pack("N*", $r ^ $p[17], $l ^ $p[16]); + return pack("N*", $r, $l); } /** - * Decrypts a block + * Fast helper function for block encryption * * @access private + * @param int $x0 + * @param int $x1 + * @param int[] $sbox0 + * @param int[] $sbox1 + * @param int[] $sbox2 + * @param int[] $sbox3 + * @param int[] $p + * @return int[] + */ + private static function encryptBlockHelperFast($x0, $x1, array $sbox0, array $sbox1, array $sbox2, array $sbox3, array $p) + { + $x0 ^= $p[0]; + $x1 ^= ((($sbox0[($x0 & 0xFF000000) >> 24] + $sbox1[($x0 & 0xFF0000) >> 16]) ^ $sbox2[($x0 & 0xFF00) >> 8]) + $sbox3[$x0 & 0xFF]) ^ $p[1]; + $x0 ^= ((($sbox0[($x1 & 0xFF000000) >> 24] + $sbox1[($x1 & 0xFF0000) >> 16]) ^ $sbox2[($x1 & 0xFF00) >> 8]) + $sbox3[$x1 & 0xFF]) ^ $p[2]; + $x1 ^= ((($sbox0[($x0 & 0xFF000000) >> 24] + $sbox1[($x0 & 0xFF0000) >> 16]) ^ $sbox2[($x0 & 0xFF00) >> 8]) + $sbox3[$x0 & 0xFF]) ^ $p[3]; + $x0 ^= ((($sbox0[($x1 & 0xFF000000) >> 24] + $sbox1[($x1 & 0xFF0000) >> 16]) ^ $sbox2[($x1 & 0xFF00) >> 8]) + $sbox3[$x1 & 0xFF]) ^ $p[4]; + $x1 ^= ((($sbox0[($x0 & 0xFF000000) >> 24] + $sbox1[($x0 & 0xFF0000) >> 16]) ^ $sbox2[($x0 & 0xFF00) >> 8]) + $sbox3[$x0 & 0xFF]) ^ $p[5]; + $x0 ^= ((($sbox0[($x1 & 0xFF000000) >> 24] + $sbox1[($x1 & 0xFF0000) >> 16]) ^ $sbox2[($x1 & 0xFF00) >> 8]) + $sbox3[$x1 & 0xFF]) ^ $p[6]; + $x1 ^= ((($sbox0[($x0 & 0xFF000000) >> 24] + $sbox1[($x0 & 0xFF0000) >> 16]) ^ $sbox2[($x0 & 0xFF00) >> 8]) + $sbox3[$x0 & 0xFF]) ^ $p[7]; + $x0 ^= ((($sbox0[($x1 & 0xFF000000) >> 24] + $sbox1[($x1 & 0xFF0000) >> 16]) ^ $sbox2[($x1 & 0xFF00) >> 8]) + $sbox3[$x1 & 0xFF]) ^ $p[8]; + $x1 ^= ((($sbox0[($x0 & 0xFF000000) >> 24] + $sbox1[($x0 & 0xFF0000) >> 16]) ^ $sbox2[($x0 & 0xFF00) >> 8]) + $sbox3[$x0 & 0xFF]) ^ $p[9]; + $x0 ^= ((($sbox0[($x1 & 0xFF000000) >> 24] + $sbox1[($x1 & 0xFF0000) >> 16]) ^ $sbox2[($x1 & 0xFF00) >> 8]) + $sbox3[$x1 & 0xFF]) ^ $p[10]; + $x1 ^= ((($sbox0[($x0 & 0xFF000000) >> 24] + $sbox1[($x0 & 0xFF0000) >> 16]) ^ $sbox2[($x0 & 0xFF00) >> 8]) + $sbox3[$x0 & 0xFF]) ^ $p[11]; + $x0 ^= ((($sbox0[($x1 & 0xFF000000) >> 24] + $sbox1[($x1 & 0xFF0000) >> 16]) ^ $sbox2[($x1 & 0xFF00) >> 8]) + $sbox3[$x1 & 0xFF]) ^ $p[12]; + $x1 ^= ((($sbox0[($x0 & 0xFF000000) >> 24] + $sbox1[($x0 & 0xFF0000) >> 16]) ^ $sbox2[($x0 & 0xFF00) >> 8]) + $sbox3[$x0 & 0xFF]) ^ $p[13]; + $x0 ^= ((($sbox0[($x1 & 0xFF000000) >> 24] + $sbox1[($x1 & 0xFF0000) >> 16]) ^ $sbox2[($x1 & 0xFF00) >> 8]) + $sbox3[$x1 & 0xFF]) ^ $p[14]; + $x1 ^= ((($sbox0[($x0 & 0xFF000000) >> 24] + $sbox1[($x0 & 0xFF0000) >> 16]) ^ $sbox2[($x0 & 0xFF00) >> 8]) + $sbox3[$x0 & 0xFF]) ^ $p[15]; + $x0 ^= ((($sbox0[($x1 & 0xFF000000) >> 24] + $sbox1[($x1 & 0xFF0000) >> 16]) ^ $sbox2[($x1 & 0xFF00) >> 8]) + $sbox3[$x1 & 0xFF]) ^ $p[16]; + + return [$x1 & 0xFFFFFFFF ^ $p[17], $x0 & 0xFFFFFFFF]; + } + + /** + * Slow helper function for block encryption + * + * @access private + * @param int $x0 + * @param int $x1 + * @param int[] $sbox0 + * @param int[] $sbox1 + * @param int[] $sbox2 + * @param int[] $sbox3 + * @param int[] $p + * @return int[] + */ + private static function encryptBlockHelperSlow($x0, $x1, array $sbox0, array $sbox1, array $sbox2, array $sbox3, array $p) + { + // -16777216 == intval(0xFF000000) on 32-bit PHP installs + $x0 ^= $p[0]; + $x1 ^= self::safe_intval((self::safe_intval($sbox0[(($x0 & -16777216) >> 24) & 0xFF] + $sbox1[($x0 & 0xFF0000) >> 16]) ^ $sbox2[($x0 & 0xFF00) >> 8]) + $sbox3[$x0 & 0xFF]) ^ $p[1]; + $x0 ^= self::safe_intval((self::safe_intval($sbox0[(($x1 & -16777216) >> 24) & 0xFF] + $sbox1[($x1 & 0xFF0000) >> 16]) ^ $sbox2[($x1 & 0xFF00) >> 8]) + $sbox3[$x1 & 0xFF]) ^ $p[2]; + $x1 ^= self::safe_intval((self::safe_intval($sbox0[(($x0 & -16777216) >> 24) & 0xFF] + $sbox1[($x0 & 0xFF0000) >> 16]) ^ $sbox2[($x0 & 0xFF00) >> 8]) + $sbox3[$x0 & 0xFF]) ^ $p[3]; + $x0 ^= self::safe_intval((self::safe_intval($sbox0[(($x1 & -16777216) >> 24) & 0xFF] + $sbox1[($x1 & 0xFF0000) >> 16]) ^ $sbox2[($x1 & 0xFF00) >> 8]) + $sbox3[$x1 & 0xFF]) ^ $p[4]; + $x1 ^= self::safe_intval((self::safe_intval($sbox0[(($x0 & -16777216) >> 24) & 0xFF] + $sbox1[($x0 & 0xFF0000) >> 16]) ^ $sbox2[($x0 & 0xFF00) >> 8]) + $sbox3[$x0 & 0xFF]) ^ $p[5]; + $x0 ^= self::safe_intval((self::safe_intval($sbox0[(($x1 & -16777216) >> 24) & 0xFF] + $sbox1[($x1 & 0xFF0000) >> 16]) ^ $sbox2[($x1 & 0xFF00) >> 8]) + $sbox3[$x1 & 0xFF]) ^ $p[6]; + $x1 ^= self::safe_intval((self::safe_intval($sbox0[(($x0 & -16777216) >> 24) & 0xFF] + $sbox1[($x0 & 0xFF0000) >> 16]) ^ $sbox2[($x0 & 0xFF00) >> 8]) + $sbox3[$x0 & 0xFF]) ^ $p[7]; + $x0 ^= self::safe_intval((self::safe_intval($sbox0[(($x1 & -16777216) >> 24) & 0xFF] + $sbox1[($x1 & 0xFF0000) >> 16]) ^ $sbox2[($x1 & 0xFF00) >> 8]) + $sbox3[$x1 & 0xFF]) ^ $p[8]; + $x1 ^= self::safe_intval((self::safe_intval($sbox0[(($x0 & -16777216) >> 24) & 0xFF] + $sbox1[($x0 & 0xFF0000) >> 16]) ^ $sbox2[($x0 & 0xFF00) >> 8]) + $sbox3[$x0 & 0xFF]) ^ $p[9]; + $x0 ^= self::safe_intval((self::safe_intval($sbox0[(($x1 & -16777216) >> 24) & 0xFF] + $sbox1[($x1 & 0xFF0000) >> 16]) ^ $sbox2[($x1 & 0xFF00) >> 8]) + $sbox3[$x1 & 0xFF]) ^ $p[10]; + $x1 ^= self::safe_intval((self::safe_intval($sbox0[(($x0 & -16777216) >> 24) & 0xFF] + $sbox1[($x0 & 0xFF0000) >> 16]) ^ $sbox2[($x0 & 0xFF00) >> 8]) + $sbox3[$x0 & 0xFF]) ^ $p[11]; + $x0 ^= self::safe_intval((self::safe_intval($sbox0[(($x1 & -16777216) >> 24) & 0xFF] + $sbox1[($x1 & 0xFF0000) >> 16]) ^ $sbox2[($x1 & 0xFF00) >> 8]) + $sbox3[$x1 & 0xFF]) ^ $p[12]; + $x1 ^= self::safe_intval((self::safe_intval($sbox0[(($x0 & -16777216) >> 24) & 0xFF] + $sbox1[($x0 & 0xFF0000) >> 16]) ^ $sbox2[($x0 & 0xFF00) >> 8]) + $sbox3[$x0 & 0xFF]) ^ $p[13]; + $x0 ^= self::safe_intval((self::safe_intval($sbox0[(($x1 & -16777216) >> 24) & 0xFF] + $sbox1[($x1 & 0xFF0000) >> 16]) ^ $sbox2[($x1 & 0xFF00) >> 8]) + $sbox3[$x1 & 0xFF]) ^ $p[14]; + $x1 ^= self::safe_intval((self::safe_intval($sbox0[(($x0 & -16777216) >> 24) & 0xFF] + $sbox1[($x0 & 0xFF0000) >> 16]) ^ $sbox2[($x0 & 0xFF00) >> 8]) + $sbox3[$x0 & 0xFF]) ^ $p[15]; + $x0 ^= self::safe_intval((self::safe_intval($sbox0[(($x1 & -16777216) >> 24) & 0xFF] + $sbox1[($x1 & 0xFF0000) >> 16]) ^ $sbox2[($x1 & 0xFF00) >> 8]) + $sbox3[$x1 & 0xFF]) ^ $p[16]; + + return [$x1 ^ $p[17], $x0]; + } + + /** + * Decrypts a block + * * @param string $in * @return string */ - function _decryptBlock($in) + protected function decryptBlock($in) { - $p = $this->bctx["p"]; - $sb_0 = $this->bctx["sb"][0]; - $sb_1 = $this->bctx["sb"][1]; - $sb_2 = $this->bctx["sb"][2]; - $sb_3 = $this->bctx["sb"][3]; + $p = $this->bctx['p']; + $sb_0 = $this->bctx['sb'][0]; + $sb_1 = $this->bctx['sb'][1]; + $sb_2 = $this->bctx['sb'][2]; + $sb_3 = $this->bctx['sb'][3]; - $in = unpack("N*", $in); + $in = unpack('N*', $in); $l = $in[1]; $r = $in[2]; - for ($i = 17; $i > 2; $i-= 2) { - $l^= $p[$i]; - $r^= $this->safe_intval(($this->safe_intval($sb_0[$l >> 24 & 0xff] + $sb_1[$l >> 16 & 0xff]) ^ + for ($i = 17; $i > 2; $i -= 2) { + $l ^= $p[$i]; + $r ^= self::safe_intval((self::safe_intval($sb_0[$l >> 24 & 0xff] + $sb_1[$l >> 16 & 0xff]) ^ $sb_2[$l >> 8 & 0xff]) + $sb_3[$l & 0xff]); - $r^= $p[$i - 1]; - $l^= $this->safe_intval(($this->safe_intval($sb_0[$r >> 24 & 0xff] + $sb_1[$r >> 16 & 0xff]) ^ + $r ^= $p[$i - 1]; + $l ^= self::safe_intval((self::safe_intval($sb_0[$r >> 24 & 0xff] + $sb_1[$r >> 16 & 0xff]) ^ $sb_2[$r >> 8 & 0xff]) + $sb_3[$r & 0xff]); } - return pack("N*", $r ^ $p[0], $l ^ $p[1]); + return pack('N*', $r ^ $p[0], $l ^ $p[1]); } /** * Setup the performance-optimized function for de/encrypt() * - * @see \phpseclib\Crypt\Base::_setupInlineCrypt() - * @access private + * @see \phpseclib3\Crypt\Common\SymmetricKey::_setupInlineCrypt() */ - function _setupInlineCrypt() + protected function setupInlineCrypt() { - $lambda_functions =& self::_getLambdaFunctions(); - - // We create max. 10 hi-optimized code for memory reason. Means: For each $key one ultra fast inline-crypt function. - // (Currently, for Blowfish, one generated $lambda_function cost on php5.5@32bit ~100kb unfreeable mem and ~180kb on php5.5@64bit) - // After that, we'll still create very fast optimized code but not the hi-ultimative code, for each $mode one. - $gen_hi_opt_code = (bool)(count($lambda_functions) < 10); - - // Generation of a unique hash for our generated code - $code_hash = "Crypt_Blowfish, {$this->mode}"; - if ($gen_hi_opt_code) { - $code_hash = str_pad($code_hash, 32) . $this->_hashInlineCryptFunction($this->key); - } - - $safeint = $this->safe_intval_inline(); - - if (!isset($lambda_functions[$code_hash])) { - switch (true) { - case $gen_hi_opt_code: - $p = $this->bctx['p']; - $init_crypt = ' - static $sb_0, $sb_1, $sb_2, $sb_3; - if (!$sb_0) { - $sb_0 = $self->bctx["sb"][0]; - $sb_1 = $self->bctx["sb"][1]; - $sb_2 = $self->bctx["sb"][2]; - $sb_3 = $self->bctx["sb"][3]; - } - '; - break; - default: - $p = array(); - for ($i = 0; $i < 18; ++$i) { - $p[] = '$p_' . $i; - } - $init_crypt = ' - list($sb_0, $sb_1, $sb_2, $sb_3) = $self->bctx["sb"]; - list(' . implode(',', $p) . ') = $self->bctx["p"]; - - '; + $p = $this->bctx['p']; + $init_crypt = ' + static $sb_0, $sb_1, $sb_2, $sb_3; + if (!$sb_0) { + $sb_0 = $this->bctx["sb"][0]; + $sb_1 = $this->bctx["sb"][1]; + $sb_2 = $this->bctx["sb"][2]; + $sb_3 = $this->bctx["sb"][3]; } - - // Generating encrypt code: - $encrypt_block = ' - $in = unpack("N*", $in); - $l = $in[1]; - $r = $in[2]; - '; - for ($i = 0; $i < 16; $i+= 2) { - $encrypt_block.= ' - $l^= ' . $p[$i] . '; - $r^= ' . sprintf($safeint, '(' . sprintf($safeint, '$sb_0[$l >> 24 & 0xff] + $sb_1[$l >> 16 & 0xff]') . ' ^ - $sb_2[$l >> 8 & 0xff]) + - $sb_3[$l & 0xff]') . '; - - $r^= ' . $p[$i + 1] . '; - $l^= ' . sprintf($safeint, '(' . sprintf($safeint, '$sb_0[$r >> 24 & 0xff] + $sb_1[$r >> 16 & 0xff]') . ' ^ - $sb_2[$r >> 8 & 0xff]) + - $sb_3[$r & 0xff]') . '; - '; - } - $encrypt_block.= ' - $in = pack("N*", - $r ^ ' . $p[17] . ', - $l ^ ' . $p[16] . ' - ); - '; - - // Generating decrypt code: - $decrypt_block = ' - $in = unpack("N*", $in); - $l = $in[1]; - $r = $in[2]; + '; + + $safeint = self::safe_intval_inline(); + + // Generating encrypt code: + $encrypt_block = ' + $in = unpack("N*", $in); + $l = $in[1]; + $r = $in[2]; + '; + for ($i = 0; $i < 16; $i += 2) { + $encrypt_block .= ' + $l^= ' . $p[$i] . '; + $r^= ' . sprintf($safeint, '(' . sprintf($safeint, '$sb_0[$l >> 24 & 0xff] + $sb_1[$l >> 16 & 0xff]') . ' ^ + $sb_2[$l >> 8 & 0xff]) + + $sb_3[$l & 0xff]') . '; + + $r^= ' . $p[$i + 1] . '; + $l^= ' . sprintf($safeint, '(' . sprintf($safeint, '$sb_0[$r >> 24 & 0xff] + $sb_1[$r >> 16 & 0xff]') . ' ^ + $sb_2[$r >> 8 & 0xff]) + + $sb_3[$r & 0xff]') . '; '; - - for ($i = 17; $i > 2; $i-= 2) { - $decrypt_block.= ' - $l^= ' . $p[$i] . '; - $r^= ' . sprintf($safeint, '(' . sprintf($safeint, '$sb_0[$l >> 24 & 0xff] + $sb_1[$l >> 16 & 0xff]') . ' ^ - $sb_2[$l >> 8 & 0xff]) + - $sb_3[$l & 0xff]') . '; - - $r^= ' . $p[$i - 1] . '; - $l^= ' . sprintf($safeint, '(' . sprintf($safeint, '$sb_0[$r >> 24 & 0xff] + $sb_1[$r >> 16 & 0xff]') . ' ^ - $sb_2[$r >> 8 & 0xff]) + - $sb_3[$r & 0xff]') . '; - '; - } - - $decrypt_block.= ' - $in = pack("N*", - $r ^ ' . $p[0] . ', - $l ^ ' . $p[1] . ' - ); + } + $encrypt_block .= ' + $in = pack("N*", + $r ^ ' . $p[17] . ', + $l ^ ' . $p[16] . ' + ); + '; + // Generating decrypt code: + $decrypt_block = ' + $in = unpack("N*", $in); + $l = $in[1]; + $r = $in[2]; + '; + + for ($i = 17; $i > 2; $i -= 2) { + $decrypt_block .= ' + $l^= ' . $p[$i] . '; + $r^= ' . sprintf($safeint, '(' . sprintf($safeint, '$sb_0[$l >> 24 & 0xff] + $sb_1[$l >> 16 & 0xff]') . ' ^ + $sb_2[$l >> 8 & 0xff]) + + $sb_3[$l & 0xff]') . '; + + $r^= ' . $p[$i - 1] . '; + $l^= ' . sprintf($safeint, '(' . sprintf($safeint, '$sb_0[$r >> 24 & 0xff] + $sb_1[$r >> 16 & 0xff]') . ' ^ + $sb_2[$r >> 8 & 0xff]) + + $sb_3[$r & 0xff]') . '; '; + } - $lambda_functions[$code_hash] = $this->_createInlineCryptFunction( - array( - 'init_crypt' => $init_crypt, - 'init_encrypt' => '', - 'init_decrypt' => '', - 'encrypt_block' => $encrypt_block, - 'decrypt_block' => $decrypt_block - ) + $decrypt_block .= ' + $in = pack("N*", + $r ^ ' . $p[0] . ', + $l ^ ' . $p[1] . ' ); - } - $this->inline_crypt = $lambda_functions[$code_hash]; + '; + + $this->inline_crypt = $this->createInlineCryptFunction( + [ + 'init_crypt' => $init_crypt, + 'init_encrypt' => '', + 'init_decrypt' => '', + 'encrypt_block' => $encrypt_block, + 'decrypt_block' => $decrypt_block + ] + ); } } diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/ChaCha20.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/ChaCha20.php new file mode 100644 index 00000000..b2691b5d --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/ChaCha20.php @@ -0,0 +1,799 @@ + + * @copyright 2019 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\Crypt; + +use phpseclib3\Exception\BadDecryptionException; +use phpseclib3\Exception\InsufficientSetupException; + +/** + * Pure-PHP implementation of ChaCha20. + * + * @author Jim Wigginton + */ +class ChaCha20 extends Salsa20 +{ + /** + * The OpenSSL specific name of the cipher + * + * @var string + */ + protected $cipher_name_openssl = 'chacha20'; + + /** + * Test for engine validity + * + * This is mainly just a wrapper to set things up for \phpseclib3\Crypt\Common\SymmetricKey::isValidEngine() + * + * @see \phpseclib3\Crypt\Common\SymmetricKey::__construct() + * @param int $engine + * @return bool + */ + protected function isValidEngineHelper($engine) + { + switch ($engine) { + case self::ENGINE_LIBSODIUM: + // PHP 7.2.0 (30 Nov 2017) added support for libsodium + + // we could probably make it so that if $this->counter == 0 then the first block would be done with either OpenSSL + // or PHP and then subsequent blocks would then be done with libsodium but idk - it's not a high priority atm + + // we could also make it so that if $this->counter == 0 and $this->continuousBuffer then do the first string + // with libsodium and subsequent strings with openssl or pure-PHP but again not a high priority + return function_exists('sodium_crypto_aead_chacha20poly1305_ietf_encrypt') && + $this->key_length == 32 && + (($this->usePoly1305 && !isset($this->poly1305Key) && $this->counter == 0) || $this->counter == 1) && + !$this->continuousBuffer; + case self::ENGINE_OPENSSL: + // OpenSSL 1.1.0 (released 25 Aug 2016) added support for chacha20. + // PHP didn't support OpenSSL 1.1.0 until 7.0.19 (11 May 2017) + + // if you attempt to provide openssl with a 128 bit key (as opposed to a 256 bit key) openssl will null + // pad the key to 256 bits and still use the expansion constant for 256-bit keys. the fact that + // openssl treats the IV as both the counter and nonce, however, let's us use openssl in continuous mode + // whereas libsodium does not + if ($this->key_length != 32) { + return false; + } + } + + return parent::isValidEngineHelper($engine); + } + + /** + * Encrypts a message. + * + * @see \phpseclib3\Crypt\Common\SymmetricKey::decrypt() + * @see self::crypt() + * @param string $plaintext + * @return string $ciphertext + */ + public function encrypt($plaintext) + { + $this->setup(); + + if ($this->engine == self::ENGINE_LIBSODIUM) { + return $this->encrypt_with_libsodium($plaintext); + } + + return parent::encrypt($plaintext); + } + + /** + * Decrypts a message. + * + * $this->decrypt($this->encrypt($plaintext)) == $this->encrypt($this->encrypt($plaintext)). + * At least if the continuous buffer is disabled. + * + * @see \phpseclib3\Crypt\Common\SymmetricKey::encrypt() + * @see self::crypt() + * @param string $ciphertext + * @return string $plaintext + */ + public function decrypt($ciphertext) + { + $this->setup(); + + if ($this->engine == self::ENGINE_LIBSODIUM) { + return $this->decrypt_with_libsodium($ciphertext); + } + + return parent::decrypt($ciphertext); + } + + /** + * Encrypts a message with libsodium + * + * @see self::encrypt() + * @param string $plaintext + * @return string $text + */ + private function encrypt_with_libsodium($plaintext) + { + $params = [$plaintext, $this->aad, $this->nonce, $this->key]; + $ciphertext = strlen($this->nonce) == 8 ? + sodium_crypto_aead_chacha20poly1305_encrypt(...$params) : + sodium_crypto_aead_chacha20poly1305_ietf_encrypt(...$params); + if (!$this->usePoly1305) { + return substr($ciphertext, 0, strlen($plaintext)); + } + + $newciphertext = substr($ciphertext, 0, strlen($plaintext)); + + $this->newtag = $this->usingGeneratedPoly1305Key && strlen($this->nonce) == 12 ? + substr($ciphertext, strlen($plaintext)) : + $this->poly1305($newciphertext); + + return $newciphertext; + } + + /** + * Decrypts a message with libsodium + * + * @see self::decrypt() + * @param string $ciphertext + * @return string $text + */ + private function decrypt_with_libsodium($ciphertext) + { + $params = [$ciphertext, $this->aad, $this->nonce, $this->key]; + + if (isset($this->poly1305Key)) { + if ($this->oldtag === false) { + throw new InsufficientSetupException('Authentication Tag has not been set'); + } + if ($this->usingGeneratedPoly1305Key && strlen($this->nonce) == 12) { + $plaintext = sodium_crypto_aead_chacha20poly1305_ietf_decrypt(...$params); + $this->oldtag = false; + if ($plaintext === false) { + throw new BadDecryptionException('Derived authentication tag and supplied authentication tag do not match'); + } + return $plaintext; + } + $newtag = $this->poly1305($ciphertext); + if ($this->oldtag != substr($newtag, 0, strlen($this->oldtag))) { + $this->oldtag = false; + throw new BadDecryptionException('Derived authentication tag and supplied authentication tag do not match'); + } + $this->oldtag = false; + } + + $plaintext = strlen($this->nonce) == 8 ? + sodium_crypto_aead_chacha20poly1305_encrypt(...$params) : + sodium_crypto_aead_chacha20poly1305_ietf_encrypt(...$params); + + return substr($plaintext, 0, strlen($ciphertext)); + } + + /** + * Sets the nonce. + * + * @param string $nonce + */ + public function setNonce($nonce) + { + if (!is_string($nonce)) { + throw new \UnexpectedValueException('The nonce should be a string'); + } + + /* + from https://tools.ietf.org/html/rfc7539#page-7 + + "Note also that the original ChaCha had a 64-bit nonce and 64-bit + block count. We have modified this here to be more consistent with + recommendations in Section 3.2 of [RFC5116]." + */ + switch (strlen($nonce)) { + case 8: // 64 bits + case 12: // 96 bits + break; + default: + throw new \LengthException('Nonce of size ' . strlen($nonce) . ' not supported by this algorithm. Only 64-bit nonces or 96-bit nonces are supported'); + } + + $this->nonce = $nonce; + $this->changed = true; + $this->setEngine(); + } + + /** + * Setup the self::ENGINE_INTERNAL $engine + * + * (re)init, if necessary, the internal cipher $engine + * + * _setup() will be called each time if $changed === true + * typically this happens when using one or more of following public methods: + * + * - setKey() + * + * - setNonce() + * + * - First run of encrypt() / decrypt() with no init-settings + * + * @see self::setKey() + * @see self::setNonce() + * @see self::disableContinuousBuffer() + */ + protected function setup() + { + if (!$this->changed) { + return; + } + + $this->enbuffer = $this->debuffer = ['ciphertext' => '', 'counter' => $this->counter]; + + $this->changed = $this->nonIVChanged = false; + + if ($this->nonce === false) { + throw new InsufficientSetupException('No nonce has been defined'); + } + + if ($this->key === false) { + throw new InsufficientSetupException('No key has been defined'); + } + + if ($this->usePoly1305 && !isset($this->poly1305Key)) { + $this->usingGeneratedPoly1305Key = true; + if ($this->engine == self::ENGINE_LIBSODIUM) { + return; + } + $this->createPoly1305Key(); + } + + $key = $this->key; + if (strlen($key) == 16) { + $constant = 'expand 16-byte k'; + $key .= $key; + } else { + $constant = 'expand 32-byte k'; + } + + $this->p1 = $constant . $key; + $this->p2 = $this->nonce; + if (strlen($this->nonce) == 8) { + $this->p2 = "\0\0\0\0" . $this->p2; + } + } + + /** + * The quarterround function + * + * @param int $a + * @param int $b + * @param int $c + * @param int $d + */ + protected static function quarterRound(&$a, &$b, &$c, &$d) + { + // in https://datatracker.ietf.org/doc/html/rfc7539#section-2.1 the addition, + // xor'ing and rotation are all on the same line so i'm keeping it on the same + // line here as well + // @codingStandardsIgnoreStart + $a+= $b; $d = self::leftRotate(intval($d) ^ intval($a), 16); + $c+= $d; $b = self::leftRotate(intval($b) ^ intval($c), 12); + $a+= $b; $d = self::leftRotate(intval($d) ^ intval($a), 8); + $c+= $d; $b = self::leftRotate(intval($b) ^ intval($c), 7); + // @codingStandardsIgnoreEnd + } + + /** + * The doubleround function + * + * @param int $x0 (by reference) + * @param int $x1 (by reference) + * @param int $x2 (by reference) + * @param int $x3 (by reference) + * @param int $x4 (by reference) + * @param int $x5 (by reference) + * @param int $x6 (by reference) + * @param int $x7 (by reference) + * @param int $x8 (by reference) + * @param int $x9 (by reference) + * @param int $x10 (by reference) + * @param int $x11 (by reference) + * @param int $x12 (by reference) + * @param int $x13 (by reference) + * @param int $x14 (by reference) + * @param int $x15 (by reference) + */ + protected static function doubleRound(&$x0, &$x1, &$x2, &$x3, &$x4, &$x5, &$x6, &$x7, &$x8, &$x9, &$x10, &$x11, &$x12, &$x13, &$x14, &$x15) + { + // columnRound + static::quarterRound($x0, $x4, $x8, $x12); + static::quarterRound($x1, $x5, $x9, $x13); + static::quarterRound($x2, $x6, $x10, $x14); + static::quarterRound($x3, $x7, $x11, $x15); + // rowRound + static::quarterRound($x0, $x5, $x10, $x15); + static::quarterRound($x1, $x6, $x11, $x12); + static::quarterRound($x2, $x7, $x8, $x13); + static::quarterRound($x3, $x4, $x9, $x14); + } + + /** + * The Salsa20 hash function function + * + * On my laptop this loop unrolled / function dereferenced version of parent::salsa20 encrypts 1mb of text in + * 0.65s vs the 0.85s that it takes with the parent method. + * + * If we were free to assume that the host OS would always be 64-bits then the if condition in leftRotate could + * be eliminated and we could knock this done to 0.60s. + * + * For comparison purposes, RC4 takes 0.16s and AES in CTR mode with the Eval engine takes 0.48s. + * AES in CTR mode with the PHP engine takes 1.19s. Salsa20 / ChaCha20 do not benefit as much from the Eval + * approach due to the fact that there are a lot less variables to de-reference, fewer loops to unroll, etc + * + * @param string $x + */ + protected static function salsa20($x) + { + list(, $x0, $x1, $x2, $x3, $x4, $x5, $x6, $x7, $x8, $x9, $x10, $x11, $x12, $x13, $x14, $x15) = unpack('V*', $x); + $z0 = $x0; + $z1 = $x1; + $z2 = $x2; + $z3 = $x3; + $z4 = $x4; + $z5 = $x5; + $z6 = $x6; + $z7 = $x7; + $z8 = $x8; + $z9 = $x9; + $z10 = $x10; + $z11 = $x11; + $z12 = $x12; + $z13 = $x13; + $z14 = $x14; + $z15 = $x15; + + // @codingStandardsIgnoreStart + // columnRound + $x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 16); + $x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 12); + $x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 8); + $x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 7); + + $x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 16); + $x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 12); + $x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 8); + $x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 7); + + $x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 16); + $x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 12); + $x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 8); + $x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 7); + + $x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 16); + $x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 12); + $x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 8); + $x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 7); + + // rowRound + $x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 16); + $x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 12); + $x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 8); + $x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 7); + + $x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 16); + $x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 12); + $x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 8); + $x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 7); + + $x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 16); + $x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 12); + $x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 8); + $x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 7); + + $x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 16); + $x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 12); + $x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 8); + $x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 7); + + // columnRound + $x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 16); + $x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 12); + $x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 8); + $x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 7); + + $x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 16); + $x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 12); + $x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 8); + $x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 7); + + $x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 16); + $x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 12); + $x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 8); + $x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 7); + + $x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 16); + $x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 12); + $x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 8); + $x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 7); + + // rowRound + $x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 16); + $x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 12); + $x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 8); + $x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 7); + + $x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 16); + $x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 12); + $x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 8); + $x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 7); + + $x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 16); + $x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 12); + $x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 8); + $x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 7); + + $x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 16); + $x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 12); + $x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 8); + $x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 7); + + // columnRound + $x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 16); + $x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 12); + $x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 8); + $x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 7); + + $x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 16); + $x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 12); + $x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 8); + $x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 7); + + $x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 16); + $x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 12); + $x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 8); + $x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 7); + + $x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 16); + $x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 12); + $x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 8); + $x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 7); + + // rowRound + $x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 16); + $x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 12); + $x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 8); + $x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 7); + + $x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 16); + $x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 12); + $x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 8); + $x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 7); + + $x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 16); + $x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 12); + $x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 8); + $x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 7); + + $x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 16); + $x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 12); + $x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 8); + $x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 7); + + // columnRound + $x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 16); + $x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 12); + $x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 8); + $x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 7); + + $x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 16); + $x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 12); + $x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 8); + $x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 7); + + $x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 16); + $x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 12); + $x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 8); + $x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 7); + + $x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 16); + $x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 12); + $x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 8); + $x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 7); + + // rowRound + $x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 16); + $x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 12); + $x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 8); + $x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 7); + + $x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 16); + $x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 12); + $x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 8); + $x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 7); + + $x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 16); + $x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 12); + $x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 8); + $x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 7); + + $x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 16); + $x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 12); + $x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 8); + $x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 7); + + // columnRound + $x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 16); + $x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 12); + $x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 8); + $x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 7); + + $x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 16); + $x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 12); + $x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 8); + $x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 7); + + $x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 16); + $x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 12); + $x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 8); + $x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 7); + + $x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 16); + $x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 12); + $x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 8); + $x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 7); + + // rowRound + $x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 16); + $x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 12); + $x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 8); + $x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 7); + + $x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 16); + $x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 12); + $x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 8); + $x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 7); + + $x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 16); + $x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 12); + $x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 8); + $x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 7); + + $x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 16); + $x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 12); + $x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 8); + $x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 7); + + // columnRound + $x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 16); + $x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 12); + $x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 8); + $x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 7); + + $x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 16); + $x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 12); + $x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 8); + $x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 7); + + $x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 16); + $x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 12); + $x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 8); + $x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 7); + + $x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 16); + $x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 12); + $x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 8); + $x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 7); + + // rowRound + $x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 16); + $x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 12); + $x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 8); + $x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 7); + + $x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 16); + $x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 12); + $x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 8); + $x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 7); + + $x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 16); + $x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 12); + $x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 8); + $x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 7); + + $x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 16); + $x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 12); + $x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 8); + $x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 7); + + // columnRound + $x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 16); + $x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 12); + $x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 8); + $x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 7); + + $x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 16); + $x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 12); + $x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 8); + $x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 7); + + $x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 16); + $x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 12); + $x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 8); + $x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 7); + + $x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 16); + $x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 12); + $x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 8); + $x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 7); + + // rowRound + $x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 16); + $x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 12); + $x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 8); + $x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 7); + + $x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 16); + $x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 12); + $x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 8); + $x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 7); + + $x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 16); + $x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 12); + $x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 8); + $x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 7); + + $x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 16); + $x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 12); + $x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 8); + $x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 7); + + // columnRound + $x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 16); + $x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 12); + $x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 8); + $x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 7); + + $x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 16); + $x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 12); + $x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 8); + $x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 7); + + $x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 16); + $x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 12); + $x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 8); + $x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 7); + + $x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 16); + $x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 12); + $x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 8); + $x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 7); + + // rowRound + $x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 16); + $x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 12); + $x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 8); + $x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 7); + + $x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 16); + $x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 12); + $x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 8); + $x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 7); + + $x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 16); + $x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 12); + $x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 8); + $x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 7); + + $x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 16); + $x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 12); + $x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 8); + $x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 7); + + // columnRound + $x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 16); + $x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 12); + $x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 8); + $x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 7); + + $x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 16); + $x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 12); + $x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 8); + $x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 7); + + $x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 16); + $x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 12); + $x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 8); + $x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 7); + + $x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 16); + $x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 12); + $x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 8); + $x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 7); + + // rowRound + $x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 16); + $x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 12); + $x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 8); + $x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 7); + + $x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 16); + $x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 12); + $x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 8); + $x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 7); + + $x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 16); + $x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 12); + $x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 8); + $x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 7); + + $x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 16); + $x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 12); + $x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 8); + $x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 7); + + // columnRound + $x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 16); + $x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 12); + $x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 8); + $x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 7); + + $x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 16); + $x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 12); + $x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 8); + $x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 7); + + $x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 16); + $x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 12); + $x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 8); + $x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 7); + + $x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 16); + $x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 12); + $x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 8); + $x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 7); + + // rowRound + $x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 16); + $x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 12); + $x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 8); + $x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 7); + + $x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 16); + $x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 12); + $x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 8); + $x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 7); + + $x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 16); + $x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 12); + $x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 8); + $x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 7); + + $x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 16); + $x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 12); + $x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 8); + $x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 7); + // @codingStandardsIgnoreEnd + + $x0 += $z0; + $x1 += $z1; + $x2 += $z2; + $x3 += $z3; + $x4 += $z4; + $x5 += $z5; + $x6 += $z6; + $x7 += $z7; + $x8 += $z8; + $x9 += $z9; + $x10 += $z10; + $x11 += $z11; + $x12 += $z12; + $x13 += $z13; + $x14 += $z14; + $x15 += $z15; + + return pack('V*', $x0, $x1, $x2, $x3, $x4, $x5, $x6, $x7, $x8, $x9, $x10, $x11, $x12, $x13, $x14, $x15); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/AsymmetricKey.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/AsymmetricKey.php new file mode 100644 index 00000000..a261dfc4 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/AsymmetricKey.php @@ -0,0 +1,576 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\Crypt\Common; + +use phpseclib3\Crypt\DSA; +use phpseclib3\Crypt\Hash; +use phpseclib3\Crypt\RSA; +use phpseclib3\Exception\NoKeyLoadedException; +use phpseclib3\Exception\UnsupportedFormatException; +use phpseclib3\Math\BigInteger; + +/** + * Base Class for all asymmetric cipher classes + * + * @author Jim Wigginton + */ +abstract class AsymmetricKey +{ + /** + * Precomputed Zero + * + * @var \phpseclib3\Math\BigInteger + */ + protected static $zero; + + /** + * Precomputed One + * + * @var \phpseclib3\Math\BigInteger + */ + protected static $one; + + /** + * Format of the loaded key + * + * @var string + */ + protected $format; + + /** + * Hash function + * + * @var \phpseclib3\Crypt\Hash + */ + protected $hash; + + /** + * HMAC function + * + * @var \phpseclib3\Crypt\Hash + */ + private $hmac; + + /** + * Supported plugins (lower case) + * + * @see self::initialize_static_variables() + * @var array + */ + private static $plugins = []; + + /** + * Invisible plugins + * + * @see self::initialize_static_variables() + * @var array + */ + private static $invisiblePlugins = []; + + /** + * Available Engines + * + * @var boolean[] + */ + protected static $engines = []; + + /** + * Key Comment + * + * @var null|string + */ + private $comment; + + /** + * @param string $type + * @return string + */ + abstract public function toString($type, array $options = []); + + /** + * The constructor + */ + protected function __construct() + { + self::initialize_static_variables(); + + $this->hash = new Hash('sha256'); + $this->hmac = new Hash('sha256'); + } + + /** + * Initialize static variables + */ + protected static function initialize_static_variables() + { + if (!isset(self::$zero)) { + self::$zero = new BigInteger(0); + self::$one = new BigInteger(1); + } + + self::loadPlugins('Keys'); + if (static::ALGORITHM != 'RSA' && static::ALGORITHM != 'DH') { + self::loadPlugins('Signature'); + } + } + + /** + * Load the key + * + * @param string $key + * @param string $password optional + * @return AsymmetricKey + */ + public static function load($key, $password = false) + { + self::initialize_static_variables(); + + $components = false; + foreach (self::$plugins[static::ALGORITHM]['Keys'] as $format) { + if (isset(self::$invisiblePlugins[static::ALGORITHM]) && in_array($format, self::$invisiblePlugins[static::ALGORITHM])) { + continue; + } + try { + $components = $format::load($key, $password); + } catch (\Exception $e) { + $components = false; + } + if ($components !== false) { + break; + } + } + + if ($components === false) { + throw new NoKeyLoadedException('Unable to read key'); + } + + $components['format'] = $format; + $components['secret'] = isset($components['secret']) ? $components['secret'] : ''; + $comment = isset($components['comment']) ? $components['comment'] : null; + $new = static::onLoad($components); + $new->format = $format; + $new->comment = $comment; + return $new instanceof PrivateKey ? + $new->withPassword($password) : + $new; + } + + /** + * Loads a private key + * + * @return PrivateKey + * @param string|array $key + * @param string $password optional + */ + public static function loadPrivateKey($key, $password = '') + { + $key = self::load($key, $password); + if (!$key instanceof PrivateKey) { + throw new NoKeyLoadedException('The key that was loaded was not a private key'); + } + return $key; + } + + /** + * Loads a public key + * + * @return PublicKey + * @param string|array $key + */ + public static function loadPublicKey($key) + { + $key = self::load($key); + if (!$key instanceof PublicKey) { + throw new NoKeyLoadedException('The key that was loaded was not a public key'); + } + return $key; + } + + /** + * Loads parameters + * + * @return AsymmetricKey + * @param string|array $key + */ + public static function loadParameters($key) + { + $key = self::load($key); + if (!$key instanceof PrivateKey && !$key instanceof PublicKey) { + throw new NoKeyLoadedException('The key that was loaded was not a parameter'); + } + return $key; + } + + /** + * Load the key, assuming a specific format + * + * @param string $type + * @param string $key + * @param string $password optional + * @return static + */ + public static function loadFormat($type, $key, $password = false) + { + self::initialize_static_variables(); + + $components = false; + $format = strtolower($type); + if (isset(self::$plugins[static::ALGORITHM]['Keys'][$format])) { + $format = self::$plugins[static::ALGORITHM]['Keys'][$format]; + $components = $format::load($key, $password); + } + + if ($components === false) { + throw new NoKeyLoadedException('Unable to read key'); + } + + $components['format'] = $format; + $components['secret'] = isset($components['secret']) ? $components['secret'] : ''; + + $new = static::onLoad($components); + $new->format = $format; + return $new instanceof PrivateKey ? + $new->withPassword($password) : + $new; + } + + /** + * Loads a private key + * + * @return PrivateKey + * @param string $type + * @param string $key + * @param string $password optional + */ + public static function loadPrivateKeyFormat($type, $key, $password = false) + { + $key = self::loadFormat($type, $key, $password); + if (!$key instanceof PrivateKey) { + throw new NoKeyLoadedException('The key that was loaded was not a private key'); + } + return $key; + } + + /** + * Loads a public key + * + * @return PublicKey + * @param string $type + * @param string $key + */ + public static function loadPublicKeyFormat($type, $key) + { + $key = self::loadFormat($type, $key); + if (!$key instanceof PublicKey) { + throw new NoKeyLoadedException('The key that was loaded was not a public key'); + } + return $key; + } + + /** + * Loads parameters + * + * @return AsymmetricKey + * @param string $type + * @param string|array $key + */ + public static function loadParametersFormat($type, $key) + { + $key = self::loadFormat($type, $key); + if (!$key instanceof PrivateKey && !$key instanceof PublicKey) { + throw new NoKeyLoadedException('The key that was loaded was not a parameter'); + } + return $key; + } + + /** + * Validate Plugin + * + * @param string $format + * @param string $type + * @param string $method optional + * @return mixed + */ + protected static function validatePlugin($format, $type, $method = null) + { + $type = strtolower($type); + if (!isset(self::$plugins[static::ALGORITHM][$format][$type])) { + throw new UnsupportedFormatException("$type is not a supported format"); + } + $type = self::$plugins[static::ALGORITHM][$format][$type]; + if (isset($method) && !method_exists($type, $method)) { + throw new UnsupportedFormatException("$type does not implement $method"); + } + + return $type; + } + + /** + * Load Plugins + * + * @param string $format + */ + private static function loadPlugins($format) + { + if (!isset(self::$plugins[static::ALGORITHM][$format])) { + self::$plugins[static::ALGORITHM][$format] = []; + foreach (new \DirectoryIterator(__DIR__ . '/../' . static::ALGORITHM . '/Formats/' . $format . '/') as $file) { + if ($file->getExtension() != 'php') { + continue; + } + $name = $file->getBasename('.php'); + if ($name[0] == '.') { + continue; + } + $type = 'phpseclib3\Crypt\\' . static::ALGORITHM . '\\Formats\\' . $format . '\\' . $name; + $reflect = new \ReflectionClass($type); + if ($reflect->isTrait()) { + continue; + } + self::$plugins[static::ALGORITHM][$format][strtolower($name)] = $type; + if ($reflect->hasConstant('IS_INVISIBLE')) { + self::$invisiblePlugins[static::ALGORITHM][] = $type; + } + } + } + } + + /** + * Returns a list of supported formats. + * + * @return array + */ + public static function getSupportedKeyFormats() + { + self::initialize_static_variables(); + + return self::$plugins[static::ALGORITHM]['Keys']; + } + + /** + * Add a fileformat plugin + * + * The plugin needs to either already be loaded or be auto-loadable. + * Loading a plugin whose shortname overwrite an existing shortname will overwrite the old plugin. + * + * @see self::load() + * @param string $fullname + * @return bool + */ + public static function addFileFormat($fullname) + { + self::initialize_static_variables(); + + if (class_exists($fullname)) { + $meta = new \ReflectionClass($fullname); + $shortname = $meta->getShortName(); + self::$plugins[static::ALGORITHM]['Keys'][strtolower($shortname)] = $fullname; + if ($meta->hasConstant('IS_INVISIBLE')) { + self::$invisiblePlugins[static::ALGORITHM] = strtolower($name); + } + } + } + + /** + * Returns the format of the loaded key. + * + * If the key that was loaded wasn't in a valid or if the key was auto-generated + * with RSA::createKey() then this will throw an exception. + * + * @see self::load() + * @return mixed + */ + public function getLoadedFormat() + { + if (empty($this->format)) { + throw new NoKeyLoadedException('This key was created with createKey - it was not loaded with load. Therefore there is no "loaded format"'); + } + + $meta = new \ReflectionClass($this->format); + return $meta->getShortName(); + } + + /** + * Returns the key's comment + * + * Not all key formats support comments. If you want to set a comment use toString() + * + * @return null|string + */ + public function getComment() + { + return $this->comment; + } + + /** + * Tests engine validity + * + */ + public static function useBestEngine() + { + static::$engines = [ + 'PHP' => true, + 'OpenSSL' => extension_loaded('openssl'), + // this test can be satisfied by either of the following: + // http://php.net/manual/en/book.sodium.php + // https://github.com/paragonie/sodium_compat + 'libsodium' => function_exists('sodium_crypto_sign_keypair') + ]; + + return static::$engines; + } + + /** + * Flag to use internal engine only (useful for unit testing) + * + */ + public static function useInternalEngine() + { + static::$engines = [ + 'PHP' => true, + 'OpenSSL' => false, + 'libsodium' => false + ]; + } + + /** + * __toString() magic method + * + * @return string + */ + public function __toString() + { + return $this->toString('PKCS8'); + } + + /** + * Determines which hashing function should be used + * + * @param string $hash + */ + public function withHash($hash) + { + $new = clone $this; + + $new->hash = new Hash($hash); + $new->hmac = new Hash($hash); + + return $new; + } + + /** + * Returns the hash algorithm currently being used + * + */ + public function getHash() + { + return clone $this->hash; + } + + /** + * Compute the pseudorandom k for signature generation, + * using the process specified for deterministic DSA. + * + * @param string $h1 + * @return string + */ + protected function computek($h1) + { + $v = str_repeat("\1", strlen($h1)); + + $k = str_repeat("\0", strlen($h1)); + + $x = $this->int2octets($this->x); + $h1 = $this->bits2octets($h1); + + $this->hmac->setKey($k); + $k = $this->hmac->hash($v . "\0" . $x . $h1); + $this->hmac->setKey($k); + $v = $this->hmac->hash($v); + $k = $this->hmac->hash($v . "\1" . $x . $h1); + $this->hmac->setKey($k); + $v = $this->hmac->hash($v); + + $qlen = $this->q->getLengthInBytes(); + + while (true) { + $t = ''; + while (strlen($t) < $qlen) { + $v = $this->hmac->hash($v); + $t = $t . $v; + } + $k = $this->bits2int($t); + + if (!$k->equals(self::$zero) && $k->compare($this->q) < 0) { + break; + } + $k = $this->hmac->hash($v . "\0"); + $this->hmac->setKey($k); + $v = $this->hmac->hash($v); + } + + return $k; + } + + /** + * Integer to Octet String + * + * @param \phpseclib3\Math\BigInteger $v + * @return string + */ + private function int2octets($v) + { + $out = $v->toBytes(); + $rolen = $this->q->getLengthInBytes(); + if (strlen($out) < $rolen) { + return str_pad($out, $rolen, "\0", STR_PAD_LEFT); + } elseif (strlen($out) > $rolen) { + return substr($out, -$rolen); + } else { + return $out; + } + } + + /** + * Bit String to Integer + * + * @param string $in + * @return \phpseclib3\Math\BigInteger + */ + protected function bits2int($in) + { + $v = new BigInteger($in, 256); + $vlen = strlen($in) << 3; + $qlen = $this->q->getLength(); + if ($vlen > $qlen) { + return $v->bitwise_rightShift($vlen - $qlen); + } + return $v; + } + + /** + * Bit String to Octet String + * + * @param string $in + * @return string + */ + private function bits2octets($in) + { + $z1 = $this->bits2int($in); + $z2 = $z1->subtract($this->q); + return $z2->compare(self::$zero) < 0 ? + $this->int2octets($z1) : + $this->int2octets($z2); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/BlockCipher.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/BlockCipher.php new file mode 100644 index 00000000..b2642be1 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/BlockCipher.php @@ -0,0 +1,24 @@ + + * @author Hans-Juergen Petrich + * @copyright 2007 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\Crypt\Common; + +/** + * Base Class for all block cipher classes + * + * @author Jim Wigginton + */ +abstract class BlockCipher extends SymmetricKey +{ +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/Formats/Keys/JWK.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/Formats/Keys/JWK.php new file mode 100644 index 00000000..4c761b83 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/Formats/Keys/JWK.php @@ -0,0 +1,69 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\Crypt\Common\Formats\Keys; + +use phpseclib3\Common\Functions\Strings; + +/** + * JSON Web Key Formatted Key Handler + * + * @author Jim Wigginton + */ +abstract class JWK +{ + /** + * Break a public or private key down into its constituent components + * + * @param string $key + * @param string $password + * @return array + */ + public static function load($key, $password = '') + { + if (!Strings::is_stringable($key)) { + throw new \UnexpectedValueException('Key should be a string - not a ' . gettype($key)); + } + + $key = preg_replace('#\s#', '', $key); // remove whitespace + + if (PHP_VERSION_ID >= 73000) { + $key = json_decode($key, null, 512, JSON_THROW_ON_ERROR); + } else { + $key = json_decode($key); + if (!$key) { + throw new \RuntimeException('Unable to decode JSON'); + } + } + + if (isset($key->kty)) { + return $key; + } + + if (count($key->keys) != 1) { + throw new \RuntimeException('Although the JWK key format supports multiple keys phpseclib does not'); + } + + return $key->keys[0]; + } + + /** + * Wrap a key appropriately + * + * @return string + */ + protected static function wrapKey(array $key, array $options) + { + return json_encode(['keys' => [$key + $options]]); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/Formats/Keys/OpenSSH.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/Formats/Keys/OpenSSH.php new file mode 100644 index 00000000..fe3d85bd --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/Formats/Keys/OpenSSH.php @@ -0,0 +1,220 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\Crypt\Common\Formats\Keys; + +use phpseclib3\Common\Functions\Strings; +use phpseclib3\Crypt\AES; +use phpseclib3\Crypt\Random; + +/** + * OpenSSH Formatted RSA Key Handler + * + * @author Jim Wigginton + */ +abstract class OpenSSH +{ + /** + * Default comment + * + * @var string + */ + protected static $comment = 'phpseclib-generated-key'; + + /** + * Binary key flag + * + * @var bool + */ + protected static $binary = false; + + /** + * Sets the default comment + * + * @param string $comment + */ + public static function setComment($comment) + { + self::$comment = str_replace(["\r", "\n"], '', $comment); + } + + /** + * Break a public or private key down into its constituent components + * + * $type can be either ssh-dss or ssh-rsa + * + * @param string $key + * @param string $password + * @return array + */ + public static function load($key, $password = '') + { + if (!Strings::is_stringable($key)) { + throw new \UnexpectedValueException('Key should be a string - not a ' . gettype($key)); + } + + // key format is described here: + // https://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.key?annotate=HEAD + + if (strpos($key, 'BEGIN OPENSSH PRIVATE KEY') !== false) { + $key = preg_replace('#(?:^-.*?-[\r\n]*$)|\s#ms', '', $key); + $key = Strings::base64_decode($key); + $magic = Strings::shift($key, 15); + if ($magic != "openssh-key-v1\0") { + throw new \RuntimeException('Expected openssh-key-v1'); + } + list($ciphername, $kdfname, $kdfoptions, $numKeys) = Strings::unpackSSH2('sssN', $key); + if ($numKeys != 1) { + // if we wanted to support multiple keys we could update PublicKeyLoader to preview what the # of keys + // would be; it'd then call Common\Keys\OpenSSH.php::load() and get the paddedKey. it'd then pass + // that to the appropriate key loading parser $numKey times or something + throw new \RuntimeException('Although the OpenSSH private key format supports multiple keys phpseclib does not'); + } + switch ($ciphername) { + case 'none': + break; + case 'aes256-ctr': + if ($kdfname != 'bcrypt') { + throw new \RuntimeException('Only the bcrypt kdf is supported (' . $kdfname . ' encountered)'); + } + list($salt, $rounds) = Strings::unpackSSH2('sN', $kdfoptions); + $crypto = new AES('ctr'); + //$crypto->setKeyLength(256); + //$crypto->disablePadding(); + $crypto->setPassword($password, 'bcrypt', $salt, $rounds, 32); + break; + default: + throw new \RuntimeException('The only supported cipherse are: none, aes256-ctr (' . $ciphername . ' is being used)'); + } + + list($publicKey, $paddedKey) = Strings::unpackSSH2('ss', $key); + list($type) = Strings::unpackSSH2('s', $publicKey); + if (isset($crypto)) { + $paddedKey = $crypto->decrypt($paddedKey); + } + list($checkint1, $checkint2) = Strings::unpackSSH2('NN', $paddedKey); + // any leftover bytes in $paddedKey are for padding? but they should be sequential bytes. eg. 1, 2, 3, etc. + if ($checkint1 != $checkint2) { + throw new \RuntimeException('The two checkints do not match'); + } + self::checkType($type); + + return compact('type', 'publicKey', 'paddedKey'); + } + + $parts = explode(' ', $key, 3); + + if (!isset($parts[1])) { + $key = base64_decode($parts[0]); + $comment = false; + } else { + $asciiType = $parts[0]; + self::checkType($parts[0]); + $key = base64_decode($parts[1]); + $comment = isset($parts[2]) ? $parts[2] : false; + } + if ($key === false) { + throw new \UnexpectedValueException('Key should be a string - not a ' . gettype($key)); + } + + list($type) = Strings::unpackSSH2('s', $key); + self::checkType($type); + if (isset($asciiType) && $asciiType != $type) { + throw new \RuntimeException('Two different types of keys are claimed: ' . $asciiType . ' and ' . $type); + } + if (strlen($key) <= 4) { + throw new \UnexpectedValueException('Key appears to be malformed'); + } + + $publicKey = $key; + + return compact('type', 'publicKey', 'comment'); + } + + /** + * Toggle between binary and printable keys + * + * Printable keys are what are generated by default. These are the ones that go in + * $HOME/.ssh/authorized_key. + * + * @param bool $enabled + */ + public static function setBinaryOutput($enabled) + { + self::$binary = $enabled; + } + + /** + * Checks to see if the type is valid + * + * @param string $candidate + */ + private static function checkType($candidate) + { + if (!in_array($candidate, static::$types)) { + throw new \RuntimeException("The key type ($candidate) is not equal to: " . implode(',', static::$types)); + } + } + + /** + * Wrap a private key appropriately + * + * @param string $publicKey + * @param string $privateKey + * @param string $password + * @param array $options + * @return string + */ + protected static function wrapPrivateKey($publicKey, $privateKey, $password, $options) + { + list(, $checkint) = unpack('N', Random::string(4)); + + $comment = isset($options['comment']) ? $options['comment'] : self::$comment; + $paddedKey = Strings::packSSH2('NN', $checkint, $checkint) . + $privateKey . + Strings::packSSH2('s', $comment); + + $usesEncryption = !empty($password) && is_string($password); + + /* + from http://tools.ietf.org/html/rfc4253#section-6 : + + Note that the length of the concatenation of 'packet_length', + 'padding_length', 'payload', and 'random padding' MUST be a multiple + of the cipher block size or 8, whichever is larger. + */ + $blockSize = $usesEncryption ? 16 : 8; + $paddingLength = (($blockSize - 1) * strlen($paddedKey)) % $blockSize; + for ($i = 1; $i <= $paddingLength; $i++) { + $paddedKey .= chr($i); + } + if (!$usesEncryption) { + $key = Strings::packSSH2('sssNss', 'none', 'none', '', 1, $publicKey, $paddedKey); + } else { + $rounds = isset($options['rounds']) ? $options['rounds'] : 16; + $salt = Random::string(16); + $kdfoptions = Strings::packSSH2('sN', $salt, $rounds); + $crypto = new AES('ctr'); + $crypto->setPassword($password, 'bcrypt', $salt, $rounds, 32); + $paddedKey = $crypto->encrypt($paddedKey); + $key = Strings::packSSH2('sssNss', 'aes256-ctr', 'bcrypt', $kdfoptions, 1, $publicKey, $paddedKey); + } + $key = "openssh-key-v1\0$key"; + + return "-----BEGIN OPENSSH PRIVATE KEY-----\n" . + chunk_split(Strings::base64_encode($key), 70, "\n") . + "-----END OPENSSH PRIVATE KEY-----\n"; + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/Formats/Keys/PKCS.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/Formats/Keys/PKCS.php new file mode 100644 index 00000000..0219400b --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/Formats/Keys/PKCS.php @@ -0,0 +1,72 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\Crypt\Common\Formats\Keys; + +/** + * PKCS1 Formatted Key Handler + * + * @author Jim Wigginton + */ +abstract class PKCS +{ + /** + * Auto-detect the format + */ + const MODE_ANY = 0; + /** + * Require base64-encoded PEM's be supplied + */ + const MODE_PEM = 1; + /** + * Require raw DER's be supplied + */ + const MODE_DER = 2; + /**#@-*/ + + /** + * Is the key a base-64 encoded PEM, DER or should it be auto-detected? + * + * @var int + */ + protected static $format = self::MODE_ANY; + + /** + * Require base64-encoded PEM's be supplied + * + */ + public static function requirePEM() + { + self::$format = self::MODE_PEM; + } + + /** + * Require raw DER's be supplied + * + */ + public static function requireDER() + { + self::$format = self::MODE_DER; + } + + /** + * Accept any format and auto detect the format + * + * This is the default setting + * + */ + public static function requireAny() + { + self::$format = self::MODE_ANY; + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/Formats/Keys/PKCS1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/Formats/Keys/PKCS1.php new file mode 100644 index 00000000..4c639c05 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/Formats/Keys/PKCS1.php @@ -0,0 +1,209 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\Crypt\Common\Formats\Keys; + +use phpseclib3\Common\Functions\Strings; +use phpseclib3\Crypt\AES; +use phpseclib3\Crypt\DES; +use phpseclib3\Crypt\Random; +use phpseclib3\Crypt\TripleDES; +use phpseclib3\Exception\UnsupportedAlgorithmException; +use phpseclib3\File\ASN1; + +/** + * PKCS1 Formatted Key Handler + * + * @author Jim Wigginton + */ +abstract class PKCS1 extends PKCS +{ + /** + * Default encryption algorithm + * + * @var string + */ + private static $defaultEncryptionAlgorithm = 'AES-128-CBC'; + + /** + * Sets the default encryption algorithm + * + * @param string $algo + */ + public static function setEncryptionAlgorithm($algo) + { + self::$defaultEncryptionAlgorithm = $algo; + } + + /** + * Returns the mode constant corresponding to the mode string + * + * @param string $mode + * @return int + * @throws \UnexpectedValueException if the block cipher mode is unsupported + */ + private static function getEncryptionMode($mode) + { + switch ($mode) { + case 'CBC': + case 'ECB': + case 'CFB': + case 'OFB': + case 'CTR': + return $mode; + } + throw new \UnexpectedValueException('Unsupported block cipher mode of operation'); + } + + /** + * Returns a cipher object corresponding to a string + * + * @param string $algo + * @return string + * @throws \UnexpectedValueException if the encryption algorithm is unsupported + */ + private static function getEncryptionObject($algo) + { + $modes = '(CBC|ECB|CFB|OFB|CTR)'; + switch (true) { + case preg_match("#^AES-(128|192|256)-$modes$#", $algo, $matches): + $cipher = new AES(self::getEncryptionMode($matches[2])); + $cipher->setKeyLength($matches[1]); + return $cipher; + case preg_match("#^DES-EDE3-$modes$#", $algo, $matches): + return new TripleDES(self::getEncryptionMode($matches[1])); + case preg_match("#^DES-$modes$#", $algo, $matches): + return new DES(self::getEncryptionMode($matches[1])); + default: + throw new UnsupportedAlgorithmException($algo . ' is not a supported algorithm'); + } + } + + /** + * Generate a symmetric key for PKCS#1 keys + * + * @param string $password + * @param string $iv + * @param int $length + * @return string + */ + private static function generateSymmetricKey($password, $iv, $length) + { + $symkey = ''; + $iv = substr($iv, 0, 8); + while (strlen($symkey) < $length) { + $symkey .= md5($symkey . $password . $iv, true); + } + return substr($symkey, 0, $length); + } + + /** + * Break a public or private key down into its constituent components + * + * @param string $key + * @param string $password optional + * @return array + */ + protected static function load($key, $password) + { + if (!Strings::is_stringable($key)) { + throw new \UnexpectedValueException('Key should be a string - not a ' . gettype($key)); + } + + /* Although PKCS#1 proposes a format that public and private keys can use, encrypting them is + "outside the scope" of PKCS#1. PKCS#1 then refers you to PKCS#12 and PKCS#15 if you're wanting to + protect private keys, however, that's not what OpenSSL* does. OpenSSL protects private keys by adding + two new "fields" to the key - DEK-Info and Proc-Type. These fields are discussed here: + + http://tools.ietf.org/html/rfc1421#section-4.6.1.1 + http://tools.ietf.org/html/rfc1421#section-4.6.1.3 + + DES-EDE3-CBC as an algorithm, however, is not discussed anywhere, near as I can tell. + DES-CBC and DES-EDE are discussed in RFC1423, however, DES-EDE3-CBC isn't, nor is its key derivation + function. As is, the definitive authority on this encoding scheme isn't the IETF but rather OpenSSL's + own implementation. ie. the implementation *is* the standard and any bugs that may exist in that + implementation are part of the standard, as well. + + * OpenSSL is the de facto standard. It's utilized by OpenSSH and other projects */ + if (preg_match('#DEK-Info: (.+),(.+)#', $key, $matches)) { + $iv = Strings::hex2bin(trim($matches[2])); + // remove the Proc-Type / DEK-Info sections as they're no longer needed + $key = preg_replace('#^(?:Proc-Type|DEK-Info): .*#m', '', $key); + $ciphertext = ASN1::extractBER($key); + if ($ciphertext === false) { + $ciphertext = $key; + } + $crypto = self::getEncryptionObject($matches[1]); + $crypto->setKey(self::generateSymmetricKey($password, $iv, $crypto->getKeyLength() >> 3)); + $crypto->setIV($iv); + $key = $crypto->decrypt($ciphertext); + } else { + if (self::$format != self::MODE_DER) { + $decoded = ASN1::extractBER($key); + if ($decoded !== false) { + $key = $decoded; + } elseif (self::$format == self::MODE_PEM) { + throw new \UnexpectedValueException('Expected base64-encoded PEM format but was unable to decode base64 text'); + } + } + } + + return $key; + } + + /** + * Wrap a private key appropriately + * + * @param string $key + * @param string $type + * @param string $password + * @param array $options optional + * @return string + */ + protected static function wrapPrivateKey($key, $type, $password, array $options = []) + { + if (empty($password) || !is_string($password)) { + return "-----BEGIN $type PRIVATE KEY-----\r\n" . + chunk_split(Strings::base64_encode($key), 64) . + "-----END $type PRIVATE KEY-----"; + } + + $encryptionAlgorithm = isset($options['encryptionAlgorithm']) ? $options['encryptionAlgorithm'] : self::$defaultEncryptionAlgorithm; + + $cipher = self::getEncryptionObject($encryptionAlgorithm); + $iv = Random::string($cipher->getBlockLength() >> 3); + $cipher->setKey(self::generateSymmetricKey($password, $iv, $cipher->getKeyLength() >> 3)); + $cipher->setIV($iv); + $iv = strtoupper(Strings::bin2hex($iv)); + return "-----BEGIN $type PRIVATE KEY-----\r\n" . + "Proc-Type: 4,ENCRYPTED\r\n" . + "DEK-Info: " . $encryptionAlgorithm . ",$iv\r\n" . + "\r\n" . + chunk_split(Strings::base64_encode($cipher->encrypt($key)), 64) . + "-----END $type PRIVATE KEY-----"; + } + + /** + * Wrap a public key appropriately + * + * @param string $key + * @param string $type + * @return string + */ + protected static function wrapPublicKey($key, $type) + { + return "-----BEGIN $type PUBLIC KEY-----\r\n" . + chunk_split(Strings::base64_encode($key), 64) . + "-----END $type PUBLIC KEY-----"; + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/Formats/Keys/PKCS8.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/Formats/Keys/PKCS8.php new file mode 100644 index 00000000..9f540049 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/Formats/Keys/PKCS8.php @@ -0,0 +1,709 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\Crypt\Common\Formats\Keys; + +use phpseclib3\Common\Functions\Strings; +use phpseclib3\Crypt\AES; +use phpseclib3\Crypt\DES; +use phpseclib3\Crypt\Random; +use phpseclib3\Crypt\RC2; +use phpseclib3\Crypt\RC4; +use phpseclib3\Crypt\TripleDES; +use phpseclib3\Exception\InsufficientSetupException; +use phpseclib3\Exception\UnsupportedAlgorithmException; +use phpseclib3\File\ASN1; +use phpseclib3\File\ASN1\Maps; + +/** + * PKCS#8 Formatted Key Handler + * + * @author Jim Wigginton + */ +abstract class PKCS8 extends PKCS +{ + /** + * Default encryption algorithm + * + * @var string + */ + private static $defaultEncryptionAlgorithm = 'id-PBES2'; + + /** + * Default encryption scheme + * + * Only used when defaultEncryptionAlgorithm is id-PBES2 + * + * @var string + */ + private static $defaultEncryptionScheme = 'aes128-CBC-PAD'; + + /** + * Default PRF + * + * Only used when defaultEncryptionAlgorithm is id-PBES2 + * + * @var string + */ + private static $defaultPRF = 'id-hmacWithSHA256'; + + /** + * Default Iteration Count + * + * @var int + */ + private static $defaultIterationCount = 2048; + + /** + * OIDs loaded + * + * @var bool + */ + private static $oidsLoaded = false; + + /** + * Sets the default encryption algorithm + * + * @param string $algo + */ + public static function setEncryptionAlgorithm($algo) + { + self::$defaultEncryptionAlgorithm = $algo; + } + + /** + * Sets the default encryption algorithm for PBES2 + * + * @param string $algo + */ + public static function setEncryptionScheme($algo) + { + self::$defaultEncryptionScheme = $algo; + } + + /** + * Sets the iteration count + * + * @param int $count + */ + public static function setIterationCount($count) + { + self::$defaultIterationCount = $count; + } + + /** + * Sets the PRF for PBES2 + * + * @param string $algo + */ + public static function setPRF($algo) + { + self::$defaultPRF = $algo; + } + + /** + * Returns a SymmetricKey object based on a PBES1 $algo + * + * @return \phpseclib3\Crypt\Common\SymmetricKey + * @param string $algo + */ + private static function getPBES1EncryptionObject($algo) + { + $algo = preg_match('#^pbeWith(?:MD2|MD5|SHA1|SHA)And(.*?)-CBC$#', $algo, $matches) ? + $matches[1] : + substr($algo, 13); // strlen('pbeWithSHAAnd') == 13 + + switch ($algo) { + case 'DES': + $cipher = new DES('cbc'); + break; + case 'RC2': + $cipher = new RC2('cbc'); + break; + case '3-KeyTripleDES': + $cipher = new TripleDES('cbc'); + break; + case '2-KeyTripleDES': + $cipher = new TripleDES('cbc'); + $cipher->setKeyLength(128); + break; + case '128BitRC2': + $cipher = new RC2('cbc'); + $cipher->setKeyLength(128); + break; + case '40BitRC2': + $cipher = new RC2('cbc'); + $cipher->setKeyLength(40); + break; + case '128BitRC4': + $cipher = new RC4(); + $cipher->setKeyLength(128); + break; + case '40BitRC4': + $cipher = new RC4(); + $cipher->setKeyLength(40); + break; + default: + throw new UnsupportedAlgorithmException("$algo is not a supported algorithm"); + } + + return $cipher; + } + + /** + * Returns a hash based on a PBES1 $algo + * + * @return string + * @param string $algo + */ + private static function getPBES1Hash($algo) + { + if (preg_match('#^pbeWith(MD2|MD5|SHA1|SHA)And.*?-CBC$#', $algo, $matches)) { + return $matches[1] == 'SHA' ? 'sha1' : $matches[1]; + } + + return 'sha1'; + } + + /** + * Returns a KDF baesd on a PBES1 $algo + * + * @return string + * @param string $algo + */ + private static function getPBES1KDF($algo) + { + switch ($algo) { + case 'pbeWithMD2AndDES-CBC': + case 'pbeWithMD2AndRC2-CBC': + case 'pbeWithMD5AndDES-CBC': + case 'pbeWithMD5AndRC2-CBC': + case 'pbeWithSHA1AndDES-CBC': + case 'pbeWithSHA1AndRC2-CBC': + return 'pbkdf1'; + } + + return 'pkcs12'; + } + + /** + * Returns a SymmetricKey object baesd on a PBES2 $algo + * + * @return SymmetricKey + * @param string $algo + */ + private static function getPBES2EncryptionObject($algo) + { + switch ($algo) { + case 'desCBC': + $cipher = new TripleDES('cbc'); + break; + case 'des-EDE3-CBC': + $cipher = new TripleDES('cbc'); + break; + case 'rc2CBC': + $cipher = new RC2('cbc'); + // in theory this can be changed + $cipher->setKeyLength(128); + break; + case 'rc5-CBC-PAD': + throw new UnsupportedAlgorithmException('rc5-CBC-PAD is not supported for PBES2 PKCS#8 keys'); + case 'aes128-CBC-PAD': + case 'aes192-CBC-PAD': + case 'aes256-CBC-PAD': + $cipher = new AES('cbc'); + $cipher->setKeyLength(substr($algo, 3, 3)); + break; + default: + throw new UnsupportedAlgorithmException("$algo is not supported"); + } + + return $cipher; + } + + /** + * Initialize static variables + * + */ + private static function initialize_static_variables() + { + if (!isset(static::$childOIDsLoaded)) { + throw new InsufficientSetupException('This class should not be called directly'); + } + + if (!static::$childOIDsLoaded) { + ASN1::loadOIDs(is_array(static::OID_NAME) ? + array_combine(static::OID_NAME, static::OID_VALUE) : + [static::OID_NAME => static::OID_VALUE]); + static::$childOIDsLoaded = true; + } + if (!self::$oidsLoaded) { + // from https://tools.ietf.org/html/rfc2898 + ASN1::loadOIDs([ + // PBES1 encryption schemes + 'pbeWithMD2AndDES-CBC' => '1.2.840.113549.1.5.1', + 'pbeWithMD2AndRC2-CBC' => '1.2.840.113549.1.5.4', + 'pbeWithMD5AndDES-CBC' => '1.2.840.113549.1.5.3', + 'pbeWithMD5AndRC2-CBC' => '1.2.840.113549.1.5.6', + 'pbeWithSHA1AndDES-CBC' => '1.2.840.113549.1.5.10', + 'pbeWithSHA1AndRC2-CBC' => '1.2.840.113549.1.5.11', + + // from PKCS#12: + // https://tools.ietf.org/html/rfc7292 + 'pbeWithSHAAnd128BitRC4' => '1.2.840.113549.1.12.1.1', + 'pbeWithSHAAnd40BitRC4' => '1.2.840.113549.1.12.1.2', + 'pbeWithSHAAnd3-KeyTripleDES-CBC' => '1.2.840.113549.1.12.1.3', + 'pbeWithSHAAnd2-KeyTripleDES-CBC' => '1.2.840.113549.1.12.1.4', + 'pbeWithSHAAnd128BitRC2-CBC' => '1.2.840.113549.1.12.1.5', + 'pbeWithSHAAnd40BitRC2-CBC' => '1.2.840.113549.1.12.1.6', + + 'id-PBKDF2' => '1.2.840.113549.1.5.12', + 'id-PBES2' => '1.2.840.113549.1.5.13', + 'id-PBMAC1' => '1.2.840.113549.1.5.14', + + // from PKCS#5 v2.1: + // http://www.rsa.com/rsalabs/pkcs/files/h11302-wp-pkcs5v2-1-password-based-cryptography-standard.pdf + 'id-hmacWithSHA1' => '1.2.840.113549.2.7', + 'id-hmacWithSHA224' => '1.2.840.113549.2.8', + 'id-hmacWithSHA256' => '1.2.840.113549.2.9', + 'id-hmacWithSHA384' => '1.2.840.113549.2.10', + 'id-hmacWithSHA512' => '1.2.840.113549.2.11', + 'id-hmacWithSHA512-224' => '1.2.840.113549.2.12', + 'id-hmacWithSHA512-256' => '1.2.840.113549.2.13', + + 'desCBC' => '1.3.14.3.2.7', + 'des-EDE3-CBC' => '1.2.840.113549.3.7', + 'rc2CBC' => '1.2.840.113549.3.2', + 'rc5-CBC-PAD' => '1.2.840.113549.3.9', + + 'aes128-CBC-PAD' => '2.16.840.1.101.3.4.1.2', + 'aes192-CBC-PAD' => '2.16.840.1.101.3.4.1.22', + 'aes256-CBC-PAD' => '2.16.840.1.101.3.4.1.42' + ]); + self::$oidsLoaded = true; + } + } + + /** + * Break a public or private key down into its constituent components + * + * @param string $key + * @param string $password optional + * @return array + */ + protected static function load($key, $password = '') + { + $decoded = self::preParse($key); + + $meta = []; + + $decrypted = ASN1::asn1map($decoded[0], Maps\EncryptedPrivateKeyInfo::MAP); + if (strlen($password) && is_array($decrypted)) { + $algorithm = $decrypted['encryptionAlgorithm']['algorithm']; + switch ($algorithm) { + // PBES1 + case 'pbeWithMD2AndDES-CBC': + case 'pbeWithMD2AndRC2-CBC': + case 'pbeWithMD5AndDES-CBC': + case 'pbeWithMD5AndRC2-CBC': + case 'pbeWithSHA1AndDES-CBC': + case 'pbeWithSHA1AndRC2-CBC': + case 'pbeWithSHAAnd3-KeyTripleDES-CBC': + case 'pbeWithSHAAnd2-KeyTripleDES-CBC': + case 'pbeWithSHAAnd128BitRC2-CBC': + case 'pbeWithSHAAnd40BitRC2-CBC': + case 'pbeWithSHAAnd128BitRC4': + case 'pbeWithSHAAnd40BitRC4': + $cipher = self::getPBES1EncryptionObject($algorithm); + $hash = self::getPBES1Hash($algorithm); + $kdf = self::getPBES1KDF($algorithm); + + $meta['meta']['algorithm'] = $algorithm; + + $temp = ASN1::decodeBER($decrypted['encryptionAlgorithm']['parameters']); + if (!$temp) { + throw new \RuntimeException('Unable to decode BER'); + } + extract(ASN1::asn1map($temp[0], Maps\PBEParameter::MAP)); + $iterationCount = (int) $iterationCount->toString(); + $cipher->setPassword($password, $kdf, $hash, $salt, $iterationCount); + $key = $cipher->decrypt($decrypted['encryptedData']); + $decoded = ASN1::decodeBER($key); + if (!$decoded) { + throw new \RuntimeException('Unable to decode BER 2'); + } + + break; + case 'id-PBES2': + $meta['meta']['algorithm'] = $algorithm; + + $temp = ASN1::decodeBER($decrypted['encryptionAlgorithm']['parameters']); + if (!$temp) { + throw new \RuntimeException('Unable to decode BER'); + } + $temp = ASN1::asn1map($temp[0], Maps\PBES2params::MAP); + extract($temp); + + $cipher = self::getPBES2EncryptionObject($encryptionScheme['algorithm']); + $meta['meta']['cipher'] = $encryptionScheme['algorithm']; + + $temp = ASN1::decodeBER($decrypted['encryptionAlgorithm']['parameters']); + if (!$temp) { + throw new \RuntimeException('Unable to decode BER'); + } + $temp = ASN1::asn1map($temp[0], Maps\PBES2params::MAP); + extract($temp); + + if (!$cipher instanceof RC2) { + $cipher->setIV($encryptionScheme['parameters']['octetString']); + } else { + $temp = ASN1::decodeBER($encryptionScheme['parameters']); + if (!$temp) { + throw new \RuntimeException('Unable to decode BER'); + } + extract(ASN1::asn1map($temp[0], Maps\RC2CBCParameter::MAP)); + $effectiveKeyLength = (int) $rc2ParametersVersion->toString(); + switch ($effectiveKeyLength) { + case 160: + $effectiveKeyLength = 40; + break; + case 120: + $effectiveKeyLength = 64; + break; + case 58: + $effectiveKeyLength = 128; + break; + //default: // should be >= 256 + } + $cipher->setIV($iv); + $cipher->setKeyLength($effectiveKeyLength); + } + + $meta['meta']['keyDerivationFunc'] = $keyDerivationFunc['algorithm']; + switch ($keyDerivationFunc['algorithm']) { + case 'id-PBKDF2': + $temp = ASN1::decodeBER($keyDerivationFunc['parameters']); + if (!$temp) { + throw new \RuntimeException('Unable to decode BER'); + } + $prf = ['algorithm' => 'id-hmacWithSHA1']; + $params = ASN1::asn1map($temp[0], Maps\PBKDF2params::MAP); + extract($params); + $meta['meta']['prf'] = $prf['algorithm']; + $hash = str_replace('-', '/', substr($prf['algorithm'], 11)); + $params = [ + $password, + 'pbkdf2', + $hash, + $salt, + (int) $iterationCount->toString() + ]; + if (isset($keyLength)) { + $params[] = (int) $keyLength->toString(); + } + $cipher->setPassword(...$params); + $key = $cipher->decrypt($decrypted['encryptedData']); + $decoded = ASN1::decodeBER($key); + if (!$decoded) { + throw new \RuntimeException('Unable to decode BER 3'); + } + break; + default: + throw new UnsupportedAlgorithmException('Only PBKDF2 is supported for PBES2 PKCS#8 keys'); + } + break; + case 'id-PBMAC1': + //$temp = ASN1::decodeBER($decrypted['encryptionAlgorithm']['parameters']); + //$value = ASN1::asn1map($temp[0], Maps\PBMAC1params::MAP); + // since i can't find any implementation that does PBMAC1 it is unsupported + throw new UnsupportedAlgorithmException('Only PBES1 and PBES2 PKCS#8 keys are supported.'); + // at this point we'll assume that the key conforms to PublicKeyInfo + } + } + + $private = ASN1::asn1map($decoded[0], Maps\OneAsymmetricKey::MAP); + if (is_array($private)) { + if (isset($private['privateKeyAlgorithm']['parameters']) && !$private['privateKeyAlgorithm']['parameters'] instanceof ASN1\Element && isset($decoded[0]['content'][1]['content'][1])) { + $temp = $decoded[0]['content'][1]['content'][1]; + $private['privateKeyAlgorithm']['parameters'] = new ASN1\Element(substr($key, $temp['start'], $temp['length'])); + } + if (is_array(static::OID_NAME)) { + if (!in_array($private['privateKeyAlgorithm']['algorithm'], static::OID_NAME)) { + throw new UnsupportedAlgorithmException($private['privateKeyAlgorithm']['algorithm'] . ' is not a supported key type'); + } + } else { + if ($private['privateKeyAlgorithm']['algorithm'] != static::OID_NAME) { + throw new UnsupportedAlgorithmException('Only ' . static::OID_NAME . ' keys are supported; this is a ' . $private['privateKeyAlgorithm']['algorithm'] . ' key'); + } + } + if (isset($private['publicKey'])) { + if ($private['publicKey'][0] != "\0") { + throw new \UnexpectedValueException('The first byte of the public key should be null - not ' . bin2hex($private['publicKey'][0])); + } + $private['publicKey'] = substr($private['publicKey'], 1); + } + return $private + $meta; + } + + // EncryptedPrivateKeyInfo and PublicKeyInfo have largely identical "signatures". the only difference + // is that the former has an octet string and the later has a bit string. the first byte of a bit + // string represents the number of bits in the last byte that are to be ignored but, currently, + // bit strings wanting a non-zero amount of bits trimmed are not supported + $public = ASN1::asn1map($decoded[0], Maps\PublicKeyInfo::MAP); + + if (is_array($public)) { + if ($public['publicKey'][0] != "\0") { + throw new \UnexpectedValueException('The first byte of the public key should be null - not ' . bin2hex($public['publicKey'][0])); + } + if (is_array(static::OID_NAME)) { + if (!in_array($public['publicKeyAlgorithm']['algorithm'], static::OID_NAME)) { + throw new UnsupportedAlgorithmException($public['publicKeyAlgorithm']['algorithm'] . ' is not a supported key type'); + } + } else { + if ($public['publicKeyAlgorithm']['algorithm'] != static::OID_NAME) { + throw new UnsupportedAlgorithmException('Only ' . static::OID_NAME . ' keys are supported; this is a ' . $public['publicKeyAlgorithm']['algorithm'] . ' key'); + } + } + if (isset($public['publicKeyAlgorithm']['parameters']) && !$public['publicKeyAlgorithm']['parameters'] instanceof ASN1\Element && isset($decoded[0]['content'][0]['content'][1])) { + $temp = $decoded[0]['content'][0]['content'][1]; + $public['publicKeyAlgorithm']['parameters'] = new ASN1\Element(substr($key, $temp['start'], $temp['length'])); + } + $public['publicKey'] = substr($public['publicKey'], 1); + return $public; + } + + throw new \RuntimeException('Unable to parse using either OneAsymmetricKey or PublicKeyInfo ASN1 maps'); + } + + /** + * Wrap a private key appropriately + * + * @param string $key + * @param string $attr + * @param mixed $params + * @param string $password + * @param string $oid optional + * @param string $publicKey optional + * @param array $options optional + * @return string + */ + protected static function wrapPrivateKey($key, $attr, $params, $password, $oid = null, $publicKey = '', array $options = []) + { + self::initialize_static_variables(); + + $key = [ + 'version' => 'v1', + 'privateKeyAlgorithm' => [ + 'algorithm' => is_string(static::OID_NAME) ? static::OID_NAME : $oid + ], + 'privateKey' => $key + ]; + if ($oid != 'id-Ed25519' && $oid != 'id-Ed448') { + $key['privateKeyAlgorithm']['parameters'] = $params; + } + if (!empty($attr)) { + $key['attributes'] = $attr; + } + if (!empty($publicKey)) { + $key['version'] = 'v2'; + $key['publicKey'] = $publicKey; + } + $key = ASN1::encodeDER($key, Maps\OneAsymmetricKey::MAP); + if (!empty($password) && is_string($password)) { + $salt = Random::string(8); + + $iterationCount = isset($options['iterationCount']) ? $options['iterationCount'] : self::$defaultIterationCount; + $encryptionAlgorithm = isset($options['encryptionAlgorithm']) ? $options['encryptionAlgorithm'] : self::$defaultEncryptionAlgorithm; + $encryptionScheme = isset($options['encryptionScheme']) ? $options['encryptionScheme'] : self::$defaultEncryptionScheme; + $prf = isset($options['PRF']) ? $options['PRF'] : self::$defaultPRF; + + if ($encryptionAlgorithm == 'id-PBES2') { + $crypto = self::getPBES2EncryptionObject($encryptionScheme); + $hash = str_replace('-', '/', substr($prf, 11)); + $kdf = 'pbkdf2'; + $iv = Random::string($crypto->getBlockLength() >> 3); + + $PBKDF2params = [ + 'salt' => $salt, + 'iterationCount' => $iterationCount, + 'prf' => ['algorithm' => $prf, 'parameters' => null] + ]; + $PBKDF2params = ASN1::encodeDER($PBKDF2params, Maps\PBKDF2params::MAP); + + if (!$crypto instanceof RC2) { + $params = ['octetString' => $iv]; + } else { + $params = [ + 'rc2ParametersVersion' => 58, + 'iv' => $iv + ]; + $params = ASN1::encodeDER($params, Maps\RC2CBCParameter::MAP); + $params = new ASN1\Element($params); + } + + $params = [ + 'keyDerivationFunc' => [ + 'algorithm' => 'id-PBKDF2', + 'parameters' => new ASN1\Element($PBKDF2params) + ], + 'encryptionScheme' => [ + 'algorithm' => $encryptionScheme, + 'parameters' => $params + ] + ]; + $params = ASN1::encodeDER($params, Maps\PBES2params::MAP); + + $crypto->setIV($iv); + } else { + $crypto = self::getPBES1EncryptionObject($encryptionAlgorithm); + $hash = self::getPBES1Hash($encryptionAlgorithm); + $kdf = self::getPBES1KDF($encryptionAlgorithm); + + $params = [ + 'salt' => $salt, + 'iterationCount' => $iterationCount + ]; + $params = ASN1::encodeDER($params, Maps\PBEParameter::MAP); + } + $crypto->setPassword($password, $kdf, $hash, $salt, $iterationCount); + $key = $crypto->encrypt($key); + + $key = [ + 'encryptionAlgorithm' => [ + 'algorithm' => $encryptionAlgorithm, + 'parameters' => new ASN1\Element($params) + ], + 'encryptedData' => $key + ]; + + $key = ASN1::encodeDER($key, Maps\EncryptedPrivateKeyInfo::MAP); + + return "-----BEGIN ENCRYPTED PRIVATE KEY-----\r\n" . + chunk_split(Strings::base64_encode($key), 64) . + "-----END ENCRYPTED PRIVATE KEY-----"; + } + + return "-----BEGIN PRIVATE KEY-----\r\n" . + chunk_split(Strings::base64_encode($key), 64) . + "-----END PRIVATE KEY-----"; + } + + /** + * Wrap a public key appropriately + * + * @param string $key + * @param mixed $params + * @param string $oid + * @return string + */ + protected static function wrapPublicKey($key, $params, $oid = null) + { + self::initialize_static_variables(); + + $key = [ + 'publicKeyAlgorithm' => [ + 'algorithm' => is_string(static::OID_NAME) ? static::OID_NAME : $oid + ], + 'publicKey' => "\0" . $key + ]; + + if ($oid != 'id-Ed25519' && $oid != 'id-Ed448') { + $key['publicKeyAlgorithm']['parameters'] = $params; + } + + $key = ASN1::encodeDER($key, Maps\PublicKeyInfo::MAP); + + return "-----BEGIN PUBLIC KEY-----\r\n" . + chunk_split(Strings::base64_encode($key), 64) . + "-----END PUBLIC KEY-----"; + } + + /** + * Perform some preliminary parsing of the key + * + * @param string $key + * @return array + */ + private static function preParse(&$key) + { + self::initialize_static_variables(); + + if (!Strings::is_stringable($key)) { + throw new \UnexpectedValueException('Key should be a string - not a ' . gettype($key)); + } + + if (self::$format != self::MODE_DER) { + $decoded = ASN1::extractBER($key); + if ($decoded !== false) { + $key = $decoded; + } elseif (self::$format == self::MODE_PEM) { + throw new \UnexpectedValueException('Expected base64-encoded PEM format but was unable to decode base64 text'); + } + } + + $decoded = ASN1::decodeBER($key); + if (!$decoded) { + throw new \RuntimeException('Unable to decode BER'); + } + + return $decoded; + } + + /** + * Returns the encryption parameters used by the key + * + * @param string $key + * @return array + */ + public static function extractEncryptionAlgorithm($key) + { + $decoded = self::preParse($key); + + $r = ASN1::asn1map($decoded[0], ASN1\Maps\EncryptedPrivateKeyInfo::MAP); + if (!is_array($r)) { + throw new \RuntimeException('Unable to parse using EncryptedPrivateKeyInfo map'); + } + + if ($r['encryptionAlgorithm']['algorithm'] == 'id-PBES2') { + $decoded = ASN1::decodeBER($r['encryptionAlgorithm']['parameters']->element); + if (!$decoded) { + throw new \RuntimeException('Unable to decode BER'); + } + $r['encryptionAlgorithm']['parameters'] = ASN1::asn1map($decoded[0], ASN1\Maps\PBES2params::MAP); + + $kdf = &$r['encryptionAlgorithm']['parameters']['keyDerivationFunc']; + switch ($kdf['algorithm']) { + case 'id-PBKDF2': + $decoded = ASN1::decodeBER($kdf['parameters']->element); + if (!$decoded) { + throw new \RuntimeException('Unable to decode BER'); + } + $kdf['parameters'] = ASN1::asn1map($decoded[0], Maps\PBKDF2params::MAP); + } + } + + return $r['encryptionAlgorithm']; + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/Formats/Keys/PuTTY.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/Formats/Keys/PuTTY.php new file mode 100644 index 00000000..85da83a7 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/Formats/Keys/PuTTY.php @@ -0,0 +1,374 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\Crypt\Common\Formats\Keys; + +use phpseclib3\Common\Functions\Strings; +use phpseclib3\Crypt\AES; +use phpseclib3\Crypt\Hash; +use phpseclib3\Crypt\Random; +use phpseclib3\Exception\UnsupportedAlgorithmException; + +/** + * PuTTY Formatted Key Handler + * + * @author Jim Wigginton + */ +abstract class PuTTY +{ + /** + * Default comment + * + * @var string + */ + private static $comment = 'phpseclib-generated-key'; + + /** + * Default version + * + * @var int + */ + private static $version = 2; + + /** + * Sets the default comment + * + * @param string $comment + */ + public static function setComment($comment) + { + self::$comment = str_replace(["\r", "\n"], '', $comment); + } + + /** + * Sets the default version + * + * @param int $version + */ + public static function setVersion($version) + { + if ($version != 2 && $version != 3) { + throw new \RuntimeException('Only supported versions are 2 and 3'); + } + self::$version = $version; + } + + /** + * Generate a symmetric key for PuTTY v2 keys + * + * @param string $password + * @param int $length + * @return string + */ + private static function generateV2Key($password, $length) + { + $symkey = ''; + $sequence = 0; + while (strlen($symkey) < $length) { + $temp = pack('Na*', $sequence++, $password); + $symkey .= Strings::hex2bin(sha1($temp)); + } + return substr($symkey, 0, $length); + } + + /** + * Generate a symmetric key for PuTTY v3 keys + * + * @param string $password + * @param string $flavour + * @param int $memory + * @param int $passes + * @param string $salt + * @return array + */ + private static function generateV3Key($password, $flavour, $memory, $passes, $salt) + { + if (!function_exists('sodium_crypto_pwhash')) { + throw new \RuntimeException('sodium_crypto_pwhash needs to exist for Argon2 password hasing'); + } + + switch ($flavour) { + case 'Argon2i': + $flavour = SODIUM_CRYPTO_PWHASH_ALG_ARGON2I13; + break; + case 'Argon2id': + $flavour = SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13; + break; + default: + throw new UnsupportedAlgorithmException('Only Argon2i and Argon2id are supported'); + } + + $length = 80; // keylen + ivlen + mac_keylen + $temp = sodium_crypto_pwhash($length, $password, $salt, $passes, $memory << 10, $flavour); + + $symkey = substr($temp, 0, 32); + $symiv = substr($temp, 32, 16); + $hashkey = substr($temp, -32); + + return compact('symkey', 'symiv', 'hashkey'); + } + + /** + * Break a public or private key down into its constituent components + * + * @param string $key + * @param string $password + * @return array + */ + public static function load($key, $password) + { + if (!Strings::is_stringable($key)) { + throw new \UnexpectedValueException('Key should be a string - not a ' . gettype($key)); + } + + if (strpos($key, 'BEGIN SSH2 PUBLIC KEY') !== false) { + $lines = preg_split('#[\r\n]+#', $key); + switch (true) { + case $lines[0] != '---- BEGIN SSH2 PUBLIC KEY ----': + throw new \UnexpectedValueException('Key doesn\'t start with ---- BEGIN SSH2 PUBLIC KEY ----'); + case $lines[count($lines) - 1] != '---- END SSH2 PUBLIC KEY ----': + throw new \UnexpectedValueException('Key doesn\'t end with ---- END SSH2 PUBLIC KEY ----'); + } + $lines = array_splice($lines, 1, -1); + $lines = array_map(function ($line) { + return rtrim($line, "\r\n"); + }, $lines); + $data = $current = ''; + $values = []; + $in_value = false; + foreach ($lines as $line) { + switch (true) { + case preg_match('#^(.*?): (.*)#', $line, $match): + $in_value = $line[strlen($line) - 1] == '\\'; + $current = strtolower($match[1]); + $values[$current] = $in_value ? substr($match[2], 0, -1) : $match[2]; + break; + case $in_value: + $in_value = $line[strlen($line) - 1] == '\\'; + $values[$current] .= $in_value ? substr($line, 0, -1) : $line; + break; + default: + $data .= $line; + } + } + + $components = call_user_func([static::PUBLIC_HANDLER, 'load'], $data); + if ($components === false) { + throw new \UnexpectedValueException('Unable to decode public key'); + } + $components += $values; + $components['comment'] = str_replace(['\\\\', '\"'], ['\\', '"'], $values['comment']); + + return $components; + } + + $components = []; + + $key = preg_split('#\r\n|\r|\n#', trim($key)); + if (Strings::shift($key[0], strlen('PuTTY-User-Key-File-')) != 'PuTTY-User-Key-File-') { + return false; + } + $version = (int) Strings::shift($key[0], 3); // should be either "2: " or "3: 0" prior to int casting + if ($version != 2 && $version != 3) { + throw new \RuntimeException('Only v2 and v3 PuTTY private keys are supported'); + } + $components['type'] = $type = rtrim($key[0]); + if (!in_array($type, static::$types)) { + $error = count(static::$types) == 1 ? + 'Only ' . static::$types[0] . ' keys are supported. ' : + ''; + throw new UnsupportedAlgorithmException($error . 'This is an unsupported ' . $type . ' key'); + } + $encryption = trim(preg_replace('#Encryption: (.+)#', '$1', $key[1])); + $components['comment'] = trim(preg_replace('#Comment: (.+)#', '$1', $key[2])); + + $publicLength = trim(preg_replace('#Public-Lines: (\d+)#', '$1', $key[3])); + $public = Strings::base64_decode(implode('', array_map('trim', array_slice($key, 4, $publicLength)))); + + $source = Strings::packSSH2('ssss', $type, $encryption, $components['comment'], $public); + + extract(unpack('Nlength', Strings::shift($public, 4))); + $newtype = Strings::shift($public, $length); + if ($newtype != $type) { + throw new \RuntimeException('The binary type does not match the human readable type field'); + } + + $components['public'] = $public; + + switch ($version) { + case 3: + $hashkey = ''; + break; + case 2: + $hashkey = 'putty-private-key-file-mac-key'; + } + + $offset = $publicLength + 4; + switch ($encryption) { + case 'aes256-cbc': + $crypto = new AES('cbc'); + switch ($version) { + case 3: + $flavour = trim(preg_replace('#Key-Derivation: (.*)#', '$1', $key[$offset++])); + $memory = trim(preg_replace('#Argon2-Memory: (\d+)#', '$1', $key[$offset++])); + $passes = trim(preg_replace('#Argon2-Passes: (\d+)#', '$1', $key[$offset++])); + $parallelism = trim(preg_replace('#Argon2-Parallelism: (\d+)#', '$1', $key[$offset++])); + $salt = Strings::hex2bin(trim(preg_replace('#Argon2-Salt: ([0-9a-f]+)#', '$1', $key[$offset++]))); + + extract(self::generateV3Key($password, $flavour, $memory, $passes, $salt)); + + break; + case 2: + $symkey = self::generateV2Key($password, 32); + $symiv = str_repeat("\0", $crypto->getBlockLength() >> 3); + $hashkey .= $password; + } + } + + switch ($version) { + case 3: + $hash = new Hash('sha256'); + $hash->setKey($hashkey); + break; + case 2: + $hash = new Hash('sha1'); + $hash->setKey(sha1($hashkey, true)); + } + + $privateLength = trim(preg_replace('#Private-Lines: (\d+)#', '$1', $key[$offset++])); + $private = Strings::base64_decode(implode('', array_map('trim', array_slice($key, $offset, $privateLength)))); + + if ($encryption != 'none') { + $crypto->setKey($symkey); + $crypto->setIV($symiv); + $crypto->disablePadding(); + $private = $crypto->decrypt($private); + } + + $source .= Strings::packSSH2('s', $private); + + $hmac = trim(preg_replace('#Private-MAC: (.+)#', '$1', $key[$offset + $privateLength])); + $hmac = Strings::hex2bin($hmac); + + if (!hash_equals($hash->hash($source), $hmac)) { + throw new \UnexpectedValueException('MAC validation error'); + } + + $components['private'] = $private; + + return $components; + } + + /** + * Wrap a private key appropriately + * + * @param string $public + * @param string $private + * @param string $type + * @param string $password + * @param array $options optional + * @return string + */ + protected static function wrapPrivateKey($public, $private, $type, $password, array $options = []) + { + $encryption = (!empty($password) || is_string($password)) ? 'aes256-cbc' : 'none'; + $comment = isset($options['comment']) ? $options['comment'] : self::$comment; + $version = isset($options['version']) ? $options['version'] : self::$version; + + $key = "PuTTY-User-Key-File-$version: $type\r\n"; + $key .= "Encryption: $encryption\r\n"; + $key .= "Comment: $comment\r\n"; + + $public = Strings::packSSH2('s', $type) . $public; + + $source = Strings::packSSH2('ssss', $type, $encryption, $comment, $public); + + $public = Strings::base64_encode($public); + $key .= "Public-Lines: " . ((strlen($public) + 63) >> 6) . "\r\n"; + $key .= chunk_split($public, 64); + + if (empty($password) && !is_string($password)) { + $source .= Strings::packSSH2('s', $private); + switch ($version) { + case 3: + $hash = new Hash('sha256'); + $hash->setKey(''); + break; + case 2: + $hash = new Hash('sha1'); + $hash->setKey(sha1('putty-private-key-file-mac-key', true)); + } + } else { + $private .= Random::string(16 - (strlen($private) & 15)); + $source .= Strings::packSSH2('s', $private); + $crypto = new AES('cbc'); + + switch ($version) { + case 3: + $salt = Random::string(16); + $key .= "Key-Derivation: Argon2id\r\n"; + $key .= "Argon2-Memory: 8192\r\n"; + $key .= "Argon2-Passes: 13\r\n"; + $key .= "Argon2-Parallelism: 1\r\n"; + $key .= "Argon2-Salt: " . Strings::bin2hex($salt) . "\r\n"; + extract(self::generateV3Key($password, 'Argon2id', 8192, 13, $salt)); + + $hash = new Hash('sha256'); + $hash->setKey($hashkey); + + break; + case 2: + $symkey = self::generateV2Key($password, 32); + $symiv = str_repeat("\0", $crypto->getBlockLength() >> 3); + $hashkey = 'putty-private-key-file-mac-key' . $password; + + $hash = new Hash('sha1'); + $hash->setKey(sha1($hashkey, true)); + } + + $crypto->setKey($symkey); + $crypto->setIV($symiv); + $crypto->disablePadding(); + $private = $crypto->encrypt($private); + $mac = $hash->hash($source); + } + + $private = Strings::base64_encode($private); + $key .= 'Private-Lines: ' . ((strlen($private) + 63) >> 6) . "\r\n"; + $key .= chunk_split($private, 64); + $key .= 'Private-MAC: ' . Strings::bin2hex($hash->hash($source)) . "\r\n"; + + return $key; + } + + /** + * Wrap a public key appropriately + * + * This is basically the format described in RFC 4716 (https://tools.ietf.org/html/rfc4716) + * + * @param string $key + * @param string $type + * @return string + */ + protected static function wrapPublicKey($key, $type) + { + $key = pack('Na*a*', strlen($type), $type, $key); + $key = "---- BEGIN SSH2 PUBLIC KEY ----\r\n" . + 'Comment: "' . str_replace(['\\', '"'], ['\\\\', '\"'], self::$comment) . "\"\r\n" . + chunk_split(Strings::base64_encode($key), 64) . + '---- END SSH2 PUBLIC KEY ----'; + return $key; + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/Formats/Signature/Raw.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/Formats/Signature/Raw.php new file mode 100644 index 00000000..ab8e7e46 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/Formats/Signature/Raw.php @@ -0,0 +1,60 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\Crypt\Common\Formats\Signature; + +use phpseclib3\Math\BigInteger; + +/** + * Raw Signature Handler + * + * @author Jim Wigginton + */ +abstract class Raw +{ + /** + * Loads a signature + * + * @param array $sig + * @return array|bool + */ + public static function load($sig) + { + switch (true) { + case !is_array($sig): + case !isset($sig['r']) || !isset($sig['s']): + case !$sig['r'] instanceof BigInteger: + case !$sig['s'] instanceof BigInteger: + return false; + } + + return [ + 'r' => $sig['r'], + 's' => $sig['s'] + ]; + } + + /** + * Returns a signature in the appropriate format + * + * @param \phpseclib3\Math\BigInteger $r + * @param \phpseclib3\Math\BigInteger $s + * @return string + */ + public static function save(BigInteger $r, BigInteger $s) + { + return compact('r', 's'); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/PrivateKey.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/PrivateKey.php new file mode 100644 index 00000000..a6e1eb0b --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/PrivateKey.php @@ -0,0 +1,31 @@ + + * @copyright 2009 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\Crypt\Common; + +/** + * PrivateKey interface + * + * @author Jim Wigginton + */ +interface PrivateKey +{ + public function sign($message); + //public function decrypt($ciphertext); + public function getPublicKey(); + public function toString($type, array $options = []); + + /** + * @param string|false $password + * @return mixed + */ + public function withPassword($password = false); +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/PublicKey.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/PublicKey.php new file mode 100644 index 00000000..48a5875b --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/PublicKey.php @@ -0,0 +1,25 @@ + + * @copyright 2009 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\Crypt\Common; + +/** + * PublicKey interface + * + * @author Jim Wigginton + */ +interface PublicKey +{ + public function verify($message, $signature); + //public function encrypt($plaintext); + public function toString($type, array $options = []); + public function getFingerprint($algorithm); +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/StreamCipher.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/StreamCipher.php new file mode 100644 index 00000000..0e2d6f0c --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/StreamCipher.php @@ -0,0 +1,54 @@ + + * @author Hans-Juergen Petrich + * @copyright 2007 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\Crypt\Common; + +/** + * Base Class for all stream cipher classes + * + * @author Jim Wigginton + */ +abstract class StreamCipher extends SymmetricKey +{ + /** + * Block Length of the cipher + * + * Stream ciphers do not have a block size + * + * @see \phpseclib3\Crypt\Common\SymmetricKey::block_size + * @var int + */ + protected $block_size = 0; + + /** + * Default Constructor. + * + * @see \phpseclib3\Crypt\Common\SymmetricKey::__construct() + * @return \phpseclib3\Crypt\Common\StreamCipher + */ + public function __construct() + { + parent::__construct('stream'); + } + + /** + * Stream ciphers not use an IV + * + * @return bool + */ + public function usesIV() + { + return false; + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/SymmetricKey.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/SymmetricKey.php new file mode 100644 index 00000000..8cd09db1 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/SymmetricKey.php @@ -0,0 +1,3386 @@ + + * @author Hans-Juergen Petrich + * @copyright 2007 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\Crypt\Common; + +use phpseclib3\Common\Functions\Strings; +use phpseclib3\Crypt\Blowfish; +use phpseclib3\Crypt\Hash; +use phpseclib3\Exception\BadDecryptionException; +use phpseclib3\Exception\BadModeException; +use phpseclib3\Exception\InconsistentSetupException; +use phpseclib3\Exception\InsufficientSetupException; +use phpseclib3\Exception\UnsupportedAlgorithmException; +use phpseclib3\Math\BigInteger; +use phpseclib3\Math\BinaryField; +use phpseclib3\Math\PrimeField; + +/** + * Base Class for all \phpseclib3\Crypt\* cipher classes + * + * @author Jim Wigginton + * @author Hans-Juergen Petrich + */ +abstract class SymmetricKey +{ + /** + * Encrypt / decrypt using the Counter mode. + * + * Set to -1 since that's what Crypt/Random.php uses to index the CTR mode. + * + * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Counter_.28CTR.29 + * @see \phpseclib3\Crypt\Common\SymmetricKey::encrypt() + * @see \phpseclib3\Crypt\Common\SymmetricKey::decrypt() + */ + const MODE_CTR = -1; + /** + * Encrypt / decrypt using the Electronic Code Book mode. + * + * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Electronic_codebook_.28ECB.29 + * @see \phpseclib3\Crypt\Common\SymmetricKey::encrypt() + * @see \phpseclib3\Crypt\Common\SymmetricKey::decrypt() + */ + const MODE_ECB = 1; + /** + * Encrypt / decrypt using the Code Book Chaining mode. + * + * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Cipher-block_chaining_.28CBC.29 + * @see \phpseclib3\Crypt\Common\SymmetricKey::encrypt() + * @see \phpseclib3\Crypt\Common\SymmetricKey::decrypt() + */ + const MODE_CBC = 2; + /** + * Encrypt / decrypt using the Cipher Feedback mode. + * + * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Cipher_feedback_.28CFB.29 + * @see \phpseclib3\Crypt\Common\SymmetricKey::encrypt() + * @see \phpseclib3\Crypt\Common\SymmetricKey::decrypt() + */ + const MODE_CFB = 3; + /** + * Encrypt / decrypt using the Cipher Feedback mode (8bit) + * + * @see \phpseclib3\Crypt\Common\SymmetricKey::encrypt() + * @see \phpseclib3\Crypt\Common\SymmetricKey::decrypt() + */ + const MODE_CFB8 = 7; + /** + * Encrypt / decrypt using the Output Feedback mode (8bit) + * + * @see \phpseclib3\Crypt\Common\SymmetricKey::encrypt() + * @see \phpseclib3\Crypt\Common\SymmetricKey::decrypt() + */ + const MODE_OFB8 = 8; + /** + * Encrypt / decrypt using the Output Feedback mode. + * + * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Output_feedback_.28OFB.29 + * @see \phpseclib3\Crypt\Common\SymmetricKey::encrypt() + * @see \phpseclib3\Crypt\Common\SymmetricKey::decrypt() + */ + const MODE_OFB = 4; + /** + * Encrypt / decrypt using Galois/Counter mode. + * + * @link https://en.wikipedia.org/wiki/Galois/Counter_Mode + * @see \phpseclib3\Crypt\Common\SymmetricKey::encrypt() + * @see \phpseclib3\Crypt\Common\SymmetricKey::decrypt() + */ + const MODE_GCM = 5; + /** + * Encrypt / decrypt using streaming mode. + * + * @see \phpseclib3\Crypt\Common\SymmetricKey::encrypt() + * @see \phpseclib3\Crypt\Common\SymmetricKey::decrypt() + */ + const MODE_STREAM = 6; + + /** + * Mode Map + * + * @see \phpseclib3\Crypt\Common\SymmetricKey::__construct() + */ + const MODE_MAP = [ + 'ctr' => self::MODE_CTR, + 'ecb' => self::MODE_ECB, + 'cbc' => self::MODE_CBC, + 'cfb' => self::MODE_CFB, + 'cfb8' => self::MODE_CFB8, + 'ofb' => self::MODE_OFB, + 'ofb8' => self::MODE_OFB8, + 'gcm' => self::MODE_GCM, + 'stream' => self::MODE_STREAM + ]; + + /** + * Base value for the internal implementation $engine switch + * + * @see \phpseclib3\Crypt\Common\SymmetricKey::__construct() + */ + const ENGINE_INTERNAL = 1; + /** + * Base value for the eval() implementation $engine switch + * + * @see \phpseclib3\Crypt\Common\SymmetricKey::__construct() + */ + const ENGINE_EVAL = 2; + /** + * Base value for the mcrypt implementation $engine switch + * + * @see \phpseclib3\Crypt\Common\SymmetricKey::__construct() + */ + const ENGINE_MCRYPT = 3; + /** + * Base value for the openssl implementation $engine switch + * + * @see \phpseclib3\Crypt\Common\SymmetricKey::__construct() + */ + const ENGINE_OPENSSL = 4; + /** + * Base value for the libsodium implementation $engine switch + * + * @see \phpseclib3\Crypt\Common\SymmetricKey::__construct() + */ + const ENGINE_LIBSODIUM = 5; + /** + * Base value for the openssl / gcm implementation $engine switch + * + * @see \phpseclib3\Crypt\Common\SymmetricKey::__construct() + */ + const ENGINE_OPENSSL_GCM = 6; + + /** + * Engine Reverse Map + * + * @see \phpseclib3\Crypt\Common\SymmetricKey::getEngine() + */ + const ENGINE_MAP = [ + self::ENGINE_INTERNAL => 'PHP', + self::ENGINE_EVAL => 'Eval', + self::ENGINE_MCRYPT => 'mcrypt', + self::ENGINE_OPENSSL => 'OpenSSL', + self::ENGINE_LIBSODIUM => 'libsodium', + self::ENGINE_OPENSSL_GCM => 'OpenSSL (GCM)' + ]; + + /** + * The Encryption Mode + * + * @see self::__construct() + * @var int + */ + protected $mode; + + /** + * The Block Length of the block cipher + * + * @var int + */ + protected $block_size = 16; + + /** + * The Key + * + * @see self::setKey() + * @var string + */ + protected $key = false; + + /** + * HMAC Key + * + * @see self::setupGCM() + * @var ?string + */ + protected $hKey = false; + + /** + * The Initialization Vector + * + * @see self::setIV() + * @var string + */ + protected $iv = false; + + /** + * A "sliding" Initialization Vector + * + * @see self::enableContinuousBuffer() + * @see self::clearBuffers() + * @var string + */ + protected $encryptIV; + + /** + * A "sliding" Initialization Vector + * + * @see self::enableContinuousBuffer() + * @see self::clearBuffers() + * @var string + */ + protected $decryptIV; + + /** + * Continuous Buffer status + * + * @see self::enableContinuousBuffer() + * @var bool + */ + protected $continuousBuffer = false; + + /** + * Encryption buffer for CTR, OFB and CFB modes + * + * @see self::encrypt() + * @see self::clearBuffers() + * @var array + */ + protected $enbuffer; + + /** + * Decryption buffer for CTR, OFB and CFB modes + * + * @see self::decrypt() + * @see self::clearBuffers() + * @var array + */ + protected $debuffer; + + /** + * mcrypt resource for encryption + * + * The mcrypt resource can be recreated every time something needs to be created or it can be created just once. + * Since mcrypt operates in continuous mode, by default, it'll need to be recreated when in non-continuous mode. + * + * @see self::encrypt() + * @var resource + */ + private $enmcrypt; + + /** + * mcrypt resource for decryption + * + * The mcrypt resource can be recreated every time something needs to be created or it can be created just once. + * Since mcrypt operates in continuous mode, by default, it'll need to be recreated when in non-continuous mode. + * + * @see self::decrypt() + * @var resource + */ + private $demcrypt; + + /** + * Does the enmcrypt resource need to be (re)initialized? + * + * @see \phpseclib3\Crypt\Twofish::setKey() + * @see \phpseclib3\Crypt\Twofish::setIV() + * @var bool + */ + private $enchanged = true; + + /** + * Does the demcrypt resource need to be (re)initialized? + * + * @see \phpseclib3\Crypt\Twofish::setKey() + * @see \phpseclib3\Crypt\Twofish::setIV() + * @var bool + */ + private $dechanged = true; + + /** + * mcrypt resource for CFB mode + * + * mcrypt's CFB mode, in (and only in) buffered context, + * is broken, so phpseclib implements the CFB mode by it self, + * even when the mcrypt php extension is available. + * + * In order to do the CFB-mode work (fast) phpseclib + * use a separate ECB-mode mcrypt resource. + * + * @link http://phpseclib.sourceforge.net/cfb-demo.phps + * @see self::encrypt() + * @see self::decrypt() + * @see self::setupMcrypt() + * @var resource + */ + private $ecb; + + /** + * Optimizing value while CFB-encrypting + * + * Only relevant if $continuousBuffer enabled + * and $engine == self::ENGINE_MCRYPT + * + * It's faster to re-init $enmcrypt if + * $buffer bytes > $cfb_init_len than + * using the $ecb resource furthermore. + * + * This value depends of the chosen cipher + * and the time it would be needed for it's + * initialization [by mcrypt_generic_init()] + * which, typically, depends on the complexity + * on its internaly Key-expanding algorithm. + * + * @see self::encrypt() + * @var int + */ + protected $cfb_init_len = 600; + + /** + * Does internal cipher state need to be (re)initialized? + * + * @see self::setKey() + * @see self::setIV() + * @see self::disableContinuousBuffer() + * @var bool + */ + protected $changed = true; + + /** + * Does Eval engie need to be (re)initialized? + * + * @see self::setup() + * @var bool + */ + protected $nonIVChanged = true; + + /** + * Padding status + * + * @see self::enablePadding() + * @var bool + */ + private $padding = true; + + /** + * Is the mode one that is paddable? + * + * @see self::__construct() + * @var bool + */ + private $paddable = false; + + /** + * Holds which crypt engine internaly should be use, + * which will be determined automatically on __construct() + * + * Currently available $engines are: + * - self::ENGINE_LIBSODIUM (very fast, php-extension: libsodium, extension_loaded('libsodium') required) + * - self::ENGINE_OPENSSL_GCM (very fast, php-extension: openssl, extension_loaded('openssl') required) + * - self::ENGINE_OPENSSL (very fast, php-extension: openssl, extension_loaded('openssl') required) + * - self::ENGINE_MCRYPT (fast, php-extension: mcrypt, extension_loaded('mcrypt') required) + * - self::ENGINE_EVAL (medium, pure php-engine, no php-extension required) + * - self::ENGINE_INTERNAL (slower, pure php-engine, no php-extension required) + * + * @see self::setEngine() + * @see self::encrypt() + * @see self::decrypt() + * @var int + */ + protected $engine; + + /** + * Holds the preferred crypt engine + * + * @see self::setEngine() + * @see self::setPreferredEngine() + * @var int + */ + private $preferredEngine; + + /** + * The mcrypt specific name of the cipher + * + * Only used if $engine == self::ENGINE_MCRYPT + * + * @link http://www.php.net/mcrypt_module_open + * @link http://www.php.net/mcrypt_list_algorithms + * @see self::setupMcrypt() + * @var string + */ + protected $cipher_name_mcrypt; + + /** + * The openssl specific name of the cipher + * + * Only used if $engine == self::ENGINE_OPENSSL + * + * @link http://www.php.net/openssl-get-cipher-methods + * @var string + */ + protected $cipher_name_openssl; + + /** + * The openssl specific name of the cipher in ECB mode + * + * If OpenSSL does not support the mode we're trying to use (CTR) + * it can still be emulated with ECB mode. + * + * @link http://www.php.net/openssl-get-cipher-methods + * @var string + */ + protected $cipher_name_openssl_ecb; + + /** + * The default salt used by setPassword() + * + * @see self::setPassword() + * @var string + */ + private $password_default_salt = 'phpseclib/salt'; + + /** + * The name of the performance-optimized callback function + * + * Used by encrypt() / decrypt() + * only if $engine == self::ENGINE_INTERNAL + * + * @see self::encrypt() + * @see self::decrypt() + * @see self::setupInlineCrypt() + * @var Callback + */ + protected $inline_crypt; + + /** + * If OpenSSL can be used in ECB but not in CTR we can emulate CTR + * + * @see self::openssl_ctr_process() + * @var bool + */ + private $openssl_emulate_ctr = false; + + /** + * Don't truncate / null pad key + * + * @see self::clearBuffers() + * @var bool + */ + private $skip_key_adjustment = false; + + /** + * Has the key length explicitly been set or should it be derived from the key, itself? + * + * @see self::setKeyLength() + * @var bool + */ + protected $explicit_key_length = false; + + /** + * Hash subkey for GHASH + * + * @see self::setupGCM() + * @see self::ghash() + * @var BinaryField\Integer + */ + private $h; + + /** + * Additional authenticated data + * + * @var string + */ + protected $aad = ''; + + /** + * Authentication Tag produced after a round of encryption + * + * @var string + */ + protected $newtag = false; + + /** + * Authentication Tag to be verified during decryption + * + * @var string + */ + protected $oldtag = false; + + /** + * GCM Binary Field + * + * @see self::__construct() + * @see self::ghash() + * @var BinaryField + */ + private static $gcmField; + + /** + * Poly1305 Prime Field + * + * @see self::enablePoly1305() + * @see self::poly1305() + * @var PrimeField + */ + private static $poly1305Field; + + /** + * Flag for using regular vs "safe" intval + * + * @see self::initialize_static_variables() + * @var boolean + */ + protected static $use_reg_intval; + + /** + * Poly1305 Key + * + * @see self::setPoly1305Key() + * @see self::poly1305() + * @var string + */ + protected $poly1305Key; + + /** + * Poly1305 Flag + * + * @see self::setPoly1305Key() + * @see self::enablePoly1305() + * @var boolean + */ + protected $usePoly1305 = false; + + /** + * The Original Initialization Vector + * + * GCM uses the nonce to build the IV but we want to be able to distinguish between nonce-derived + * IV's and user-set IV's + * + * @see self::setIV() + * @var string + */ + private $origIV = false; + + /** + * Nonce + * + * Only used with GCM. We could re-use setIV() but nonce's can be of a different length and + * toggling between GCM and other modes could be more complicated if we re-used setIV() + * + * @see self::setNonce() + * @var string + */ + protected $nonce = false; + + /** + * Default Constructor. + * + * $mode could be: + * + * - ecb + * + * - cbc + * + * - ctr + * + * - cfb + * + * - cfb8 + * + * - ofb + * + * - ofb8 + * + * - gcm + * + * @param string $mode + * @throws BadModeException if an invalid / unsupported mode is provided + */ + public function __construct($mode) + { + $mode = strtolower($mode); + // necessary because of 5.6 compatibility; we can't do isset(self::MODE_MAP[$mode]) in 5.6 + $map = self::MODE_MAP; + if (!isset($map[$mode])) { + throw new BadModeException('No valid mode has been specified'); + } + + $mode = self::MODE_MAP[$mode]; + + // $mode dependent settings + switch ($mode) { + case self::MODE_ECB: + case self::MODE_CBC: + $this->paddable = true; + break; + case self::MODE_CTR: + case self::MODE_CFB: + case self::MODE_CFB8: + case self::MODE_OFB: + case self::MODE_OFB8: + case self::MODE_STREAM: + $this->paddable = false; + break; + case self::MODE_GCM: + if ($this->block_size != 16) { + throw new BadModeException('GCM is only valid for block ciphers with a block size of 128 bits'); + } + if (!isset(self::$gcmField)) { + self::$gcmField = new BinaryField(128, 7, 2, 1, 0); + } + $this->paddable = false; + break; + default: + throw new BadModeException('No valid mode has been specified'); + } + + $this->mode = $mode; + + static::initialize_static_variables(); + } + + /** + * Initialize static variables + */ + protected static function initialize_static_variables() + { + if (!isset(self::$use_reg_intval)) { + switch (true) { + // PHP_OS & "\xDF\xDF\xDF" == strtoupper(substr(PHP_OS, 0, 3)), but a lot faster + case (PHP_OS & "\xDF\xDF\xDF") === 'WIN': + case (php_uname('m') & "\xDF\xDF\xDF") != 'ARM': + case defined('PHP_INT_SIZE') && PHP_INT_SIZE == 8: + self::$use_reg_intval = true; + break; + case (php_uname('m') & "\xDF\xDF\xDF") == 'ARM': + switch (true) { + /* PHP 7.0.0 introduced a bug that affected 32-bit ARM processors: + + https://github.com/php/php-src/commit/716da71446ebbd40fa6cf2cea8a4b70f504cc3cd + + altho the changelogs make no mention of it, this bug was fixed with this commit: + + https://github.com/php/php-src/commit/c1729272b17a1fe893d1a54e423d3b71470f3ee8 + + affected versions of PHP are: 7.0.x, 7.1.0 - 7.1.23 and 7.2.0 - 7.2.11 */ + case PHP_VERSION_ID >= 70000 && PHP_VERSION_ID <= 70123: + case PHP_VERSION_ID >= 70200 && PHP_VERSION_ID <= 70211: + self::$use_reg_intval = false; + break; + default: + self::$use_reg_intval = true; + } + } + } + } + + /** + * Sets the initialization vector. + * + * setIV() is not required when ecb or gcm modes are being used. + * + * {@internal Can be overwritten by a sub class, but does not have to be} + * + * @param string $iv + * @throws \LengthException if the IV length isn't equal to the block size + * @throws \BadMethodCallException if an IV is provided when one shouldn't be + */ + public function setIV($iv) + { + if ($this->mode == self::MODE_ECB) { + throw new \BadMethodCallException('This mode does not require an IV.'); + } + + if ($this->mode == self::MODE_GCM) { + throw new \BadMethodCallException('Use setNonce instead'); + } + + if (!$this->usesIV()) { + throw new \BadMethodCallException('This algorithm does not use an IV.'); + } + + if (strlen($iv) != $this->block_size) { + throw new \LengthException('Received initialization vector of size ' . strlen($iv) . ', but size ' . $this->block_size . ' is required'); + } + + $this->iv = $this->origIV = $iv; + $this->changed = true; + } + + /** + * Enables Poly1305 mode. + * + * Once enabled Poly1305 cannot be disabled. + * + * @throws \BadMethodCallException if Poly1305 is enabled whilst in GCM mode + */ + public function enablePoly1305() + { + if ($this->mode == self::MODE_GCM) { + throw new \BadMethodCallException('Poly1305 cannot be used in GCM mode'); + } + + $this->usePoly1305 = true; + } + + /** + * Enables Poly1305 mode. + * + * Once enabled Poly1305 cannot be disabled. If $key is not passed then an attempt to call createPoly1305Key + * will be made. + * + * @param string $key optional + * @throws \LengthException if the key isn't long enough + * @throws \BadMethodCallException if Poly1305 is enabled whilst in GCM mode + */ + public function setPoly1305Key($key = null) + { + if ($this->mode == self::MODE_GCM) { + throw new \BadMethodCallException('Poly1305 cannot be used in GCM mode'); + } + + if (!is_string($key) || strlen($key) != 32) { + throw new \LengthException('The Poly1305 key must be 32 bytes long (256 bits)'); + } + + if (!isset(self::$poly1305Field)) { + // 2^130-5 + self::$poly1305Field = new PrimeField(new BigInteger('3fffffffffffffffffffffffffffffffb', 16)); + } + + $this->poly1305Key = $key; + $this->usePoly1305 = true; + } + + /** + * Sets the nonce. + * + * setNonce() is only required when gcm is used + * + * @param string $nonce + * @throws \BadMethodCallException if an nonce is provided when one shouldn't be + */ + public function setNonce($nonce) + { + if ($this->mode != self::MODE_GCM) { + throw new \BadMethodCallException('Nonces are only used in GCM mode.'); + } + + $this->nonce = $nonce; + $this->setEngine(); + } + + /** + * Sets additional authenticated data + * + * setAAD() is only used by gcm or in poly1305 mode + * + * @param string $aad + * @throws \BadMethodCallException if mode isn't GCM or if poly1305 isn't being utilized + */ + public function setAAD($aad) + { + if ($this->mode != self::MODE_GCM && !$this->usePoly1305) { + throw new \BadMethodCallException('Additional authenticated data is only utilized in GCM mode or with Poly1305'); + } + + $this->aad = $aad; + } + + /** + * Returns whether or not the algorithm uses an IV + * + * @return bool + */ + public function usesIV() + { + return $this->mode != self::MODE_GCM && $this->mode != self::MODE_ECB; + } + + /** + * Returns whether or not the algorithm uses a nonce + * + * @return bool + */ + public function usesNonce() + { + return $this->mode == self::MODE_GCM; + } + + /** + * Returns the current key length in bits + * + * @return int + */ + public function getKeyLength() + { + return $this->key_length << 3; + } + + /** + * Returns the current block length in bits + * + * @return int + */ + public function getBlockLength() + { + return $this->block_size << 3; + } + + /** + * Returns the current block length in bytes + * + * @return int + */ + public function getBlockLengthInBytes() + { + return $this->block_size; + } + + /** + * Sets the key length. + * + * Keys with explicitly set lengths need to be treated accordingly + * + * @param int $length + */ + public function setKeyLength($length) + { + $this->explicit_key_length = $length >> 3; + + if (is_string($this->key) && strlen($this->key) != $this->explicit_key_length) { + $this->key = false; + throw new InconsistentSetupException('Key has already been set and is not ' . $this->explicit_key_length . ' bytes long'); + } + } + + /** + * Sets the key. + * + * The min/max length(s) of the key depends on the cipher which is used. + * If the key not fits the length(s) of the cipher it will paded with null bytes + * up to the closest valid key length. If the key is more than max length, + * we trim the excess bits. + * + * If the key is not explicitly set, it'll be assumed to be all null bytes. + * + * {@internal Could, but not must, extend by the child Crypt_* class} + * + * @param string $key + */ + public function setKey($key) + { + if ($this->explicit_key_length !== false && strlen($key) != $this->explicit_key_length) { + throw new InconsistentSetupException('Key length has already been set to ' . $this->explicit_key_length . ' bytes and this key is ' . strlen($key) . ' bytes'); + } + + $this->key = $key; + $this->key_length = strlen($key); + $this->setEngine(); + } + + /** + * Sets the password. + * + * Depending on what $method is set to, setPassword()'s (optional) parameters are as follows: + * {@link http://en.wikipedia.org/wiki/PBKDF2 pbkdf2} or pbkdf1: + * $hash, $salt, $count, $dkLen + * + * Where $hash (default = sha1) currently supports the following hashes: see: Crypt/Hash.php + * {@link https://en.wikipedia.org/wiki/Bcrypt bcypt}: + * $salt, $rounds, $keylen + * + * This is a modified version of bcrypt used by OpenSSH. + * + * {@internal Could, but not must, extend by the child Crypt_* class} + * + * @see Crypt/Hash.php + * @param string $password + * @param string $method + * @param string[] ...$func_args + * @throws \LengthException if pbkdf1 is being used and the derived key length exceeds the hash length + * @throws \RuntimeException if bcrypt is being used and a salt isn't provided + * @return bool + */ + public function setPassword($password, $method = 'pbkdf2', ...$func_args) + { + $key = ''; + + $method = strtolower($method); + switch ($method) { + case 'bcrypt': + if (!isset($func_args[2])) { + throw new \RuntimeException('A salt must be provided for bcrypt to work'); + } + + $salt = $func_args[0]; + + $rounds = isset($func_args[1]) ? $func_args[1] : 16; + $keylen = isset($func_args[2]) ? $func_args[2] : $this->key_length; + + $key = Blowfish::bcrypt_pbkdf($password, $salt, $keylen + $this->block_size, $rounds); + + $this->setKey(substr($key, 0, $keylen)); + $this->setIV(substr($key, $keylen)); + + return true; + case 'pkcs12': // from https://tools.ietf.org/html/rfc7292#appendix-B.2 + case 'pbkdf1': + case 'pbkdf2': + // Hash function + $hash = isset($func_args[0]) ? strtolower($func_args[0]) : 'sha1'; + $hashObj = new Hash(); + $hashObj->setHash($hash); + + // WPA and WPA2 use the SSID as the salt + $salt = isset($func_args[1]) ? $func_args[1] : $this->password_default_salt; + + // RFC2898#section-4.2 uses 1,000 iterations by default + // WPA and WPA2 use 4,096. + $count = isset($func_args[2]) ? $func_args[2] : 1000; + + // Keylength + if (isset($func_args[3])) { + if ($func_args[3] <= 0) { + throw new \LengthException('Derived key length cannot be longer 0 or less'); + } + $dkLen = $func_args[3]; + } else { + $key_length = $this->explicit_key_length !== false ? $this->explicit_key_length : $this->key_length; + $dkLen = $method == 'pbkdf1' ? 2 * $key_length : $key_length; + } + + switch (true) { + case $method == 'pkcs12': + /* + In this specification, however, all passwords are created from + BMPStrings with a NULL terminator. This means that each character in + the original BMPString is encoded in 2 bytes in big-endian format + (most-significant byte first). There are no Unicode byte order + marks. The 2 bytes produced from the last character in the BMPString + are followed by 2 additional bytes with the value 0x00. + + -- https://tools.ietf.org/html/rfc7292#appendix-B.1 + */ + $password = "\0" . chunk_split($password, 1, "\0") . "\0"; + + /* + This standard specifies 3 different values for the ID byte mentioned + above: + + 1. If ID=1, then the pseudorandom bits being produced are to be used + as key material for performing encryption or decryption. + + 2. If ID=2, then the pseudorandom bits being produced are to be used + as an IV (Initial Value) for encryption or decryption. + + 3. If ID=3, then the pseudorandom bits being produced are to be used + as an integrity key for MACing. + */ + // Construct a string, D (the "diversifier"), by concatenating v/8 + // copies of ID. + $blockLength = $hashObj->getBlockLengthInBytes(); + $d1 = str_repeat(chr(1), $blockLength); + $d2 = str_repeat(chr(2), $blockLength); + $s = ''; + if (strlen($salt)) { + while (strlen($s) < $blockLength) { + $s .= $salt; + } + } + $s = substr($s, 0, $blockLength); + + $p = ''; + if (strlen($password)) { + while (strlen($p) < $blockLength) { + $p .= $password; + } + } + $p = substr($p, 0, $blockLength); + + $i = $s . $p; + + $this->setKey(self::pkcs12helper($dkLen, $hashObj, $i, $d1, $count)); + if ($this->usesIV()) { + $this->setIV(self::pkcs12helper($this->block_size, $hashObj, $i, $d2, $count)); + } + + return true; + case $method == 'pbkdf1': + if ($dkLen > $hashObj->getLengthInBytes()) { + throw new \LengthException('Derived key length cannot be longer than the hash length'); + } + $t = $password . $salt; + for ($i = 0; $i < $count; ++$i) { + $t = $hashObj->hash($t); + } + $key = substr($t, 0, $dkLen); + + $this->setKey(substr($key, 0, $dkLen >> 1)); + if ($this->usesIV()) { + $this->setIV(substr($key, $dkLen >> 1)); + } + + return true; + case !in_array($hash, hash_algos()): + $i = 1; + $hashObj->setKey($password); + while (strlen($key) < $dkLen) { + $f = $u = $hashObj->hash($salt . pack('N', $i++)); + for ($j = 2; $j <= $count; ++$j) { + $u = $hashObj->hash($u); + $f ^= $u; + } + $key .= $f; + } + $key = substr($key, 0, $dkLen); + break; + default: + $key = hash_pbkdf2($hash, $password, $salt, $count, $dkLen, true); + } + break; + default: + throw new UnsupportedAlgorithmException($method . ' is not a supported password hashing method'); + } + + $this->setKey($key); + + return true; + } + + /** + * PKCS#12 KDF Helper Function + * + * As discussed here: + * + * {@link https://tools.ietf.org/html/rfc7292#appendix-B} + * + * @see self::setPassword() + * @param int $n + * @param \phpseclib3\Crypt\Hash $hashObj + * @param string $i + * @param string $d + * @param int $count + * @return string $a + */ + private static function pkcs12helper($n, $hashObj, $i, $d, $count) + { + static $one; + if (!isset($one)) { + $one = new BigInteger(1); + } + + $blockLength = $hashObj->getBlockLength() >> 3; + + $c = ceil($n / $hashObj->getLengthInBytes()); + $a = ''; + for ($j = 1; $j <= $c; $j++) { + $ai = $d . $i; + for ($k = 0; $k < $count; $k++) { + $ai = $hashObj->hash($ai); + } + $b = ''; + while (strlen($b) < $blockLength) { + $b .= $ai; + } + $b = substr($b, 0, $blockLength); + $b = new BigInteger($b, 256); + $newi = ''; + for ($k = 0; $k < strlen($i); $k += $blockLength) { + $temp = substr($i, $k, $blockLength); + $temp = new BigInteger($temp, 256); + $temp->setPrecision($blockLength << 3); + $temp = $temp->add($b); + $temp = $temp->add($one); + $newi .= $temp->toBytes(false); + } + $i = $newi; + $a .= $ai; + } + + return substr($a, 0, $n); + } + + /** + * Encrypts a message. + * + * $plaintext will be padded with additional bytes such that it's length is a multiple of the block size. Other cipher + * implementations may or may not pad in the same manner. Other common approaches to padding and the reasons why it's + * necessary are discussed in the following + * URL: + * + * {@link http://www.di-mgt.com.au/cryptopad.html http://www.di-mgt.com.au/cryptopad.html} + * + * An alternative to padding is to, separately, send the length of the file. This is what SSH, in fact, does. + * strlen($plaintext) will still need to be a multiple of the block size, however, arbitrary values can be added to make it that + * length. + * + * {@internal Could, but not must, extend by the child Crypt_* class} + * + * @see self::decrypt() + * @param string $plaintext + * @return string $ciphertext + */ + public function encrypt($plaintext) + { + if ($this->paddable) { + $plaintext = $this->pad($plaintext); + } + + $this->setup(); + + if ($this->mode == self::MODE_GCM) { + $oldIV = $this->iv; + Strings::increment_str($this->iv); + $cipher = new static('ctr'); + $cipher->setKey($this->key); + $cipher->setIV($this->iv); + $ciphertext = $cipher->encrypt($plaintext); + + $s = $this->ghash( + self::nullPad128($this->aad) . + self::nullPad128($ciphertext) . + self::len64($this->aad) . + self::len64($ciphertext) + ); + $cipher->encryptIV = $this->iv = $this->encryptIV = $this->decryptIV = $oldIV; + $this->newtag = $cipher->encrypt($s); + return $ciphertext; + } + + if (isset($this->poly1305Key)) { + $cipher = clone $this; + unset($cipher->poly1305Key); + $this->usePoly1305 = false; + $ciphertext = $cipher->encrypt($plaintext); + $this->newtag = $this->poly1305($ciphertext); + return $ciphertext; + } + + if ($this->engine === self::ENGINE_OPENSSL) { + switch ($this->mode) { + case self::MODE_STREAM: + return openssl_encrypt($plaintext, $this->cipher_name_openssl, $this->key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING); + case self::MODE_ECB: + return openssl_encrypt($plaintext, $this->cipher_name_openssl, $this->key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING); + case self::MODE_CBC: + $result = openssl_encrypt($plaintext, $this->cipher_name_openssl, $this->key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $this->encryptIV); + if ($this->continuousBuffer) { + $this->encryptIV = substr($result, -$this->block_size); + } + return $result; + case self::MODE_CTR: + return $this->openssl_ctr_process($plaintext, $this->encryptIV, $this->enbuffer); + case self::MODE_CFB: + // cfb loosely routines inspired by openssl's: + // {@link http://cvs.openssl.org/fileview?f=openssl/crypto/modes/cfb128.c&v=1.3.2.2.2.1} + $ciphertext = ''; + if ($this->continuousBuffer) { + $iv = &$this->encryptIV; + $pos = &$this->enbuffer['pos']; + } else { + $iv = $this->encryptIV; + $pos = 0; + } + $len = strlen($plaintext); + $i = 0; + if ($pos) { + $orig_pos = $pos; + $max = $this->block_size - $pos; + if ($len >= $max) { + $i = $max; + $len -= $max; + $pos = 0; + } else { + $i = $len; + $pos += $len; + $len = 0; + } + // ie. $i = min($max, $len), $len-= $i, $pos+= $i, $pos%= $blocksize + $ciphertext = substr($iv, $orig_pos) ^ $plaintext; + $iv = substr_replace($iv, $ciphertext, $orig_pos, $i); + $plaintext = substr($plaintext, $i); + } + + $overflow = $len % $this->block_size; + + if ($overflow) { + $ciphertext .= openssl_encrypt(substr($plaintext, 0, -$overflow) . str_repeat("\0", $this->block_size), $this->cipher_name_openssl, $this->key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $iv); + $iv = Strings::pop($ciphertext, $this->block_size); + + $size = $len - $overflow; + $block = $iv ^ substr($plaintext, -$overflow); + $iv = substr_replace($iv, $block, 0, $overflow); + $ciphertext .= $block; + $pos = $overflow; + } elseif ($len) { + $ciphertext = openssl_encrypt($plaintext, $this->cipher_name_openssl, $this->key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $iv); + $iv = substr($ciphertext, -$this->block_size); + } + + return $ciphertext; + case self::MODE_CFB8: + $ciphertext = openssl_encrypt($plaintext, $this->cipher_name_openssl, $this->key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $this->encryptIV); + if ($this->continuousBuffer) { + if (($len = strlen($ciphertext)) >= $this->block_size) { + $this->encryptIV = substr($ciphertext, -$this->block_size); + } else { + $this->encryptIV = substr($this->encryptIV, $len - $this->block_size) . substr($ciphertext, -$len); + } + } + return $ciphertext; + case self::MODE_OFB8: + $ciphertext = ''; + $len = strlen($plaintext); + $iv = $this->encryptIV; + + for ($i = 0; $i < $len; ++$i) { + $xor = openssl_encrypt($iv, $this->cipher_name_openssl_ecb, $this->key, $this->openssl_options, $this->decryptIV); + $ciphertext .= $plaintext[$i] ^ $xor; + $iv = substr($iv, 1) . $xor[0]; + } + + if ($this->continuousBuffer) { + $this->encryptIV = $iv; + } + break; + case self::MODE_OFB: + return $this->openssl_ofb_process($plaintext, $this->encryptIV, $this->enbuffer); + } + } + + if ($this->engine === self::ENGINE_MCRYPT) { + set_error_handler(function () { + }); + if ($this->enchanged) { + mcrypt_generic_init($this->enmcrypt, $this->key, $this->getIV($this->encryptIV)); + $this->enchanged = false; + } + + // re: {@link http://phpseclib.sourceforge.net/cfb-demo.phps} + // using mcrypt's default handing of CFB the above would output two different things. using phpseclib's + // rewritten CFB implementation the above outputs the same thing twice. + if ($this->mode == self::MODE_CFB && $this->continuousBuffer) { + $block_size = $this->block_size; + $iv = &$this->encryptIV; + $pos = &$this->enbuffer['pos']; + $len = strlen($plaintext); + $ciphertext = ''; + $i = 0; + if ($pos) { + $orig_pos = $pos; + $max = $block_size - $pos; + if ($len >= $max) { + $i = $max; + $len -= $max; + $pos = 0; + } else { + $i = $len; + $pos += $len; + $len = 0; + } + $ciphertext = substr($iv, $orig_pos) ^ $plaintext; + $iv = substr_replace($iv, $ciphertext, $orig_pos, $i); + $this->enbuffer['enmcrypt_init'] = true; + } + if ($len >= $block_size) { + if ($this->enbuffer['enmcrypt_init'] === false || $len > $this->cfb_init_len) { + if ($this->enbuffer['enmcrypt_init'] === true) { + mcrypt_generic_init($this->enmcrypt, $this->key, $iv); + $this->enbuffer['enmcrypt_init'] = false; + } + $ciphertext .= mcrypt_generic($this->enmcrypt, substr($plaintext, $i, $len - $len % $block_size)); + $iv = substr($ciphertext, -$block_size); + $len %= $block_size; + } else { + while ($len >= $block_size) { + $iv = mcrypt_generic($this->ecb, $iv) ^ substr($plaintext, $i, $block_size); + $ciphertext .= $iv; + $len -= $block_size; + $i += $block_size; + } + } + } + + if ($len) { + $iv = mcrypt_generic($this->ecb, $iv); + $block = $iv ^ substr($plaintext, -$len); + $iv = substr_replace($iv, $block, 0, $len); + $ciphertext .= $block; + $pos = $len; + } + + restore_error_handler(); + + return $ciphertext; + } + + $ciphertext = mcrypt_generic($this->enmcrypt, $plaintext); + + if (!$this->continuousBuffer) { + mcrypt_generic_init($this->enmcrypt, $this->key, $this->getIV($this->encryptIV)); + } + + restore_error_handler(); + + return $ciphertext; + } + + if ($this->engine === self::ENGINE_EVAL) { + $inline = $this->inline_crypt; + return $inline('encrypt', $plaintext); + } + + $buffer = &$this->enbuffer; + $block_size = $this->block_size; + $ciphertext = ''; + switch ($this->mode) { + case self::MODE_ECB: + for ($i = 0; $i < strlen($plaintext); $i += $block_size) { + $ciphertext .= $this->encryptBlock(substr($plaintext, $i, $block_size)); + } + break; + case self::MODE_CBC: + $xor = $this->encryptIV; + for ($i = 0; $i < strlen($plaintext); $i += $block_size) { + $block = substr($plaintext, $i, $block_size); + $block = $this->encryptBlock($block ^ $xor); + $xor = $block; + $ciphertext .= $block; + } + if ($this->continuousBuffer) { + $this->encryptIV = $xor; + } + break; + case self::MODE_CTR: + $xor = $this->encryptIV; + if (strlen($buffer['ciphertext'])) { + for ($i = 0; $i < strlen($plaintext); $i += $block_size) { + $block = substr($plaintext, $i, $block_size); + if (strlen($block) > strlen($buffer['ciphertext'])) { + $buffer['ciphertext'] .= $this->encryptBlock($xor); + Strings::increment_str($xor); + } + $key = Strings::shift($buffer['ciphertext'], $block_size); + $ciphertext .= $block ^ $key; + } + } else { + for ($i = 0; $i < strlen($plaintext); $i += $block_size) { + $block = substr($plaintext, $i, $block_size); + $key = $this->encryptBlock($xor); + Strings::increment_str($xor); + $ciphertext .= $block ^ $key; + } + } + if ($this->continuousBuffer) { + $this->encryptIV = $xor; + if ($start = strlen($plaintext) % $block_size) { + $buffer['ciphertext'] = substr($key, $start) . $buffer['ciphertext']; + } + } + break; + case self::MODE_CFB: + // cfb loosely routines inspired by openssl's: + // {@link http://cvs.openssl.org/fileview?f=openssl/crypto/modes/cfb128.c&v=1.3.2.2.2.1} + if ($this->continuousBuffer) { + $iv = &$this->encryptIV; + $pos = &$buffer['pos']; + } else { + $iv = $this->encryptIV; + $pos = 0; + } + $len = strlen($plaintext); + $i = 0; + if ($pos) { + $orig_pos = $pos; + $max = $block_size - $pos; + if ($len >= $max) { + $i = $max; + $len -= $max; + $pos = 0; + } else { + $i = $len; + $pos += $len; + $len = 0; + } + // ie. $i = min($max, $len), $len-= $i, $pos+= $i, $pos%= $blocksize + $ciphertext = substr($iv, $orig_pos) ^ $plaintext; + $iv = substr_replace($iv, $ciphertext, $orig_pos, $i); + } + while ($len >= $block_size) { + $iv = $this->encryptBlock($iv) ^ substr($plaintext, $i, $block_size); + $ciphertext .= $iv; + $len -= $block_size; + $i += $block_size; + } + if ($len) { + $iv = $this->encryptBlock($iv); + $block = $iv ^ substr($plaintext, $i); + $iv = substr_replace($iv, $block, 0, $len); + $ciphertext .= $block; + $pos = $len; + } + break; + case self::MODE_CFB8: + $ciphertext = ''; + $len = strlen($plaintext); + $iv = $this->encryptIV; + + for ($i = 0; $i < $len; ++$i) { + $ciphertext .= ($c = $plaintext[$i] ^ $this->encryptBlock($iv)); + $iv = substr($iv, 1) . $c; + } + + if ($this->continuousBuffer) { + if ($len >= $block_size) { + $this->encryptIV = substr($ciphertext, -$block_size); + } else { + $this->encryptIV = substr($this->encryptIV, $len - $block_size) . substr($ciphertext, -$len); + } + } + break; + case self::MODE_OFB8: + $ciphertext = ''; + $len = strlen($plaintext); + $iv = $this->encryptIV; + + for ($i = 0; $i < $len; ++$i) { + $xor = $this->encryptBlock($iv); + $ciphertext .= $plaintext[$i] ^ $xor; + $iv = substr($iv, 1) . $xor[0]; + } + + if ($this->continuousBuffer) { + $this->encryptIV = $iv; + } + break; + case self::MODE_OFB: + $xor = $this->encryptIV; + if (strlen($buffer['xor'])) { + for ($i = 0; $i < strlen($plaintext); $i += $block_size) { + $block = substr($plaintext, $i, $block_size); + if (strlen($block) > strlen($buffer['xor'])) { + $xor = $this->encryptBlock($xor); + $buffer['xor'] .= $xor; + } + $key = Strings::shift($buffer['xor'], $block_size); + $ciphertext .= $block ^ $key; + } + } else { + for ($i = 0; $i < strlen($plaintext); $i += $block_size) { + $xor = $this->encryptBlock($xor); + $ciphertext .= substr($plaintext, $i, $block_size) ^ $xor; + } + $key = $xor; + } + if ($this->continuousBuffer) { + $this->encryptIV = $xor; + if ($start = strlen($plaintext) % $block_size) { + $buffer['xor'] = substr($key, $start) . $buffer['xor']; + } + } + break; + case self::MODE_STREAM: + $ciphertext = $this->encryptBlock($plaintext); + break; + } + + return $ciphertext; + } + + /** + * Decrypts a message. + * + * If strlen($ciphertext) is not a multiple of the block size, null bytes will be added to the end of the string until + * it is. + * + * {@internal Could, but not must, extend by the child Crypt_* class} + * + * @see self::encrypt() + * @param string $ciphertext + * @return string $plaintext + * @throws \LengthException if we're inside a block cipher and the ciphertext length is not a multiple of the block size + */ + public function decrypt($ciphertext) + { + if ($this->paddable && strlen($ciphertext) % $this->block_size) { + throw new \LengthException('The ciphertext length (' . strlen($ciphertext) . ') needs to be a multiple of the block size (' . $this->block_size . ')'); + } + $this->setup(); + + if ($this->mode == self::MODE_GCM || isset($this->poly1305Key)) { + if ($this->oldtag === false) { + throw new InsufficientSetupException('Authentication Tag has not been set'); + } + + if (isset($this->poly1305Key)) { + $newtag = $this->poly1305($ciphertext); + } else { + $oldIV = $this->iv; + Strings::increment_str($this->iv); + $cipher = new static('ctr'); + $cipher->setKey($this->key); + $cipher->setIV($this->iv); + $plaintext = $cipher->decrypt($ciphertext); + + $s = $this->ghash( + self::nullPad128($this->aad) . + self::nullPad128($ciphertext) . + self::len64($this->aad) . + self::len64($ciphertext) + ); + $cipher->encryptIV = $this->iv = $this->encryptIV = $this->decryptIV = $oldIV; + $newtag = $cipher->encrypt($s); + } + if ($this->oldtag != substr($newtag, 0, strlen($newtag))) { + $cipher = clone $this; + unset($cipher->poly1305Key); + $this->usePoly1305 = false; + $plaintext = $cipher->decrypt($ciphertext); + $this->oldtag = false; + throw new BadDecryptionException('Derived authentication tag and supplied authentication tag do not match'); + } + $this->oldtag = false; + return $plaintext; + } + + if ($this->engine === self::ENGINE_OPENSSL) { + switch ($this->mode) { + case self::MODE_STREAM: + $plaintext = openssl_decrypt($ciphertext, $this->cipher_name_openssl, $this->key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING); + break; + case self::MODE_ECB: + $plaintext = openssl_decrypt($ciphertext, $this->cipher_name_openssl, $this->key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING); + break; + case self::MODE_CBC: + $offset = $this->block_size; + $plaintext = openssl_decrypt($ciphertext, $this->cipher_name_openssl, $this->key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $this->decryptIV); + if ($this->continuousBuffer) { + $this->decryptIV = substr($ciphertext, -$offset, $this->block_size); + } + break; + case self::MODE_CTR: + $plaintext = $this->openssl_ctr_process($ciphertext, $this->decryptIV, $this->debuffer); + break; + case self::MODE_CFB: + // cfb loosely routines inspired by openssl's: + // {@link http://cvs.openssl.org/fileview?f=openssl/crypto/modes/cfb128.c&v=1.3.2.2.2.1} + $plaintext = ''; + if ($this->continuousBuffer) { + $iv = &$this->decryptIV; + $pos = &$this->debuffer['pos']; + } else { + $iv = $this->decryptIV; + $pos = 0; + } + $len = strlen($ciphertext); + $i = 0; + if ($pos) { + $orig_pos = $pos; + $max = $this->block_size - $pos; + if ($len >= $max) { + $i = $max; + $len -= $max; + $pos = 0; + } else { + $i = $len; + $pos += $len; + $len = 0; + } + // ie. $i = min($max, $len), $len-= $i, $pos+= $i, $pos%= $this->blocksize + $plaintext = substr($iv, $orig_pos) ^ $ciphertext; + $iv = substr_replace($iv, substr($ciphertext, 0, $i), $orig_pos, $i); + $ciphertext = substr($ciphertext, $i); + } + $overflow = $len % $this->block_size; + if ($overflow) { + $plaintext .= openssl_decrypt(substr($ciphertext, 0, -$overflow), $this->cipher_name_openssl, $this->key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $iv); + if ($len - $overflow) { + $iv = substr($ciphertext, -$overflow - $this->block_size, -$overflow); + } + $iv = openssl_encrypt(str_repeat("\0", $this->block_size), $this->cipher_name_openssl, $this->key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $iv); + $plaintext .= $iv ^ substr($ciphertext, -$overflow); + $iv = substr_replace($iv, substr($ciphertext, -$overflow), 0, $overflow); + $pos = $overflow; + } elseif ($len) { + $plaintext .= openssl_decrypt($ciphertext, $this->cipher_name_openssl, $this->key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $iv); + $iv = substr($ciphertext, -$this->block_size); + } + break; + case self::MODE_CFB8: + $plaintext = openssl_decrypt($ciphertext, $this->cipher_name_openssl, $this->key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $this->decryptIV); + if ($this->continuousBuffer) { + if (($len = strlen($ciphertext)) >= $this->block_size) { + $this->decryptIV = substr($ciphertext, -$this->block_size); + } else { + $this->decryptIV = substr($this->decryptIV, $len - $this->block_size) . substr($ciphertext, -$len); + } + } + break; + case self::MODE_OFB8: + $plaintext = ''; + $len = strlen($ciphertext); + $iv = $this->decryptIV; + + for ($i = 0; $i < $len; ++$i) { + $xor = openssl_encrypt($iv, $this->cipher_name_openssl_ecb, $this->key, $this->openssl_options, $this->decryptIV); + $plaintext .= $ciphertext[$i] ^ $xor; + $iv = substr($iv, 1) . $xor[0]; + } + + if ($this->continuousBuffer) { + $this->decryptIV = $iv; + } + break; + case self::MODE_OFB: + $plaintext = $this->openssl_ofb_process($ciphertext, $this->decryptIV, $this->debuffer); + } + + return $this->paddable ? $this->unpad($plaintext) : $plaintext; + } + + if ($this->engine === self::ENGINE_MCRYPT) { + set_error_handler(function () { + }); + $block_size = $this->block_size; + if ($this->dechanged) { + mcrypt_generic_init($this->demcrypt, $this->key, $this->getIV($this->decryptIV)); + $this->dechanged = false; + } + + if ($this->mode == self::MODE_CFB && $this->continuousBuffer) { + $iv = &$this->decryptIV; + $pos = &$this->debuffer['pos']; + $len = strlen($ciphertext); + $plaintext = ''; + $i = 0; + if ($pos) { + $orig_pos = $pos; + $max = $block_size - $pos; + if ($len >= $max) { + $i = $max; + $len -= $max; + $pos = 0; + } else { + $i = $len; + $pos += $len; + $len = 0; + } + // ie. $i = min($max, $len), $len-= $i, $pos+= $i, $pos%= $blocksize + $plaintext = substr($iv, $orig_pos) ^ $ciphertext; + $iv = substr_replace($iv, substr($ciphertext, 0, $i), $orig_pos, $i); + } + if ($len >= $block_size) { + $cb = substr($ciphertext, $i, $len - $len % $block_size); + $plaintext .= mcrypt_generic($this->ecb, $iv . $cb) ^ $cb; + $iv = substr($cb, -$block_size); + $len %= $block_size; + } + if ($len) { + $iv = mcrypt_generic($this->ecb, $iv); + $plaintext .= $iv ^ substr($ciphertext, -$len); + $iv = substr_replace($iv, substr($ciphertext, -$len), 0, $len); + $pos = $len; + } + + restore_error_handler(); + + return $plaintext; + } + + $plaintext = mdecrypt_generic($this->demcrypt, $ciphertext); + + if (!$this->continuousBuffer) { + mcrypt_generic_init($this->demcrypt, $this->key, $this->getIV($this->decryptIV)); + } + + restore_error_handler(); + + return $this->paddable ? $this->unpad($plaintext) : $plaintext; + } + + if ($this->engine === self::ENGINE_EVAL) { + $inline = $this->inline_crypt; + return $inline('decrypt', $ciphertext); + } + + $block_size = $this->block_size; + + $buffer = &$this->debuffer; + $plaintext = ''; + switch ($this->mode) { + case self::MODE_ECB: + for ($i = 0; $i < strlen($ciphertext); $i += $block_size) { + $plaintext .= $this->decryptBlock(substr($ciphertext, $i, $block_size)); + } + break; + case self::MODE_CBC: + $xor = $this->decryptIV; + for ($i = 0; $i < strlen($ciphertext); $i += $block_size) { + $block = substr($ciphertext, $i, $block_size); + $plaintext .= $this->decryptBlock($block) ^ $xor; + $xor = $block; + } + if ($this->continuousBuffer) { + $this->decryptIV = $xor; + } + break; + case self::MODE_CTR: + $xor = $this->decryptIV; + if (strlen($buffer['ciphertext'])) { + for ($i = 0; $i < strlen($ciphertext); $i += $block_size) { + $block = substr($ciphertext, $i, $block_size); + if (strlen($block) > strlen($buffer['ciphertext'])) { + $buffer['ciphertext'] .= $this->encryptBlock($xor); + Strings::increment_str($xor); + } + $key = Strings::shift($buffer['ciphertext'], $block_size); + $plaintext .= $block ^ $key; + } + } else { + for ($i = 0; $i < strlen($ciphertext); $i += $block_size) { + $block = substr($ciphertext, $i, $block_size); + $key = $this->encryptBlock($xor); + Strings::increment_str($xor); + $plaintext .= $block ^ $key; + } + } + if ($this->continuousBuffer) { + $this->decryptIV = $xor; + if ($start = strlen($ciphertext) % $block_size) { + $buffer['ciphertext'] = substr($key, $start) . $buffer['ciphertext']; + } + } + break; + case self::MODE_CFB: + if ($this->continuousBuffer) { + $iv = &$this->decryptIV; + $pos = &$buffer['pos']; + } else { + $iv = $this->decryptIV; + $pos = 0; + } + $len = strlen($ciphertext); + $i = 0; + if ($pos) { + $orig_pos = $pos; + $max = $block_size - $pos; + if ($len >= $max) { + $i = $max; + $len -= $max; + $pos = 0; + } else { + $i = $len; + $pos += $len; + $len = 0; + } + // ie. $i = min($max, $len), $len-= $i, $pos+= $i, $pos%= $blocksize + $plaintext = substr($iv, $orig_pos) ^ $ciphertext; + $iv = substr_replace($iv, substr($ciphertext, 0, $i), $orig_pos, $i); + } + while ($len >= $block_size) { + $iv = $this->encryptBlock($iv); + $cb = substr($ciphertext, $i, $block_size); + $plaintext .= $iv ^ $cb; + $iv = $cb; + $len -= $block_size; + $i += $block_size; + } + if ($len) { + $iv = $this->encryptBlock($iv); + $plaintext .= $iv ^ substr($ciphertext, $i); + $iv = substr_replace($iv, substr($ciphertext, $i), 0, $len); + $pos = $len; + } + break; + case self::MODE_CFB8: + $plaintext = ''; + $len = strlen($ciphertext); + $iv = $this->decryptIV; + + for ($i = 0; $i < $len; ++$i) { + $plaintext .= $ciphertext[$i] ^ $this->encryptBlock($iv); + $iv = substr($iv, 1) . $ciphertext[$i]; + } + + if ($this->continuousBuffer) { + if ($len >= $block_size) { + $this->decryptIV = substr($ciphertext, -$block_size); + } else { + $this->decryptIV = substr($this->decryptIV, $len - $block_size) . substr($ciphertext, -$len); + } + } + break; + case self::MODE_OFB8: + $plaintext = ''; + $len = strlen($ciphertext); + $iv = $this->decryptIV; + + for ($i = 0; $i < $len; ++$i) { + $xor = $this->encryptBlock($iv); + $plaintext .= $ciphertext[$i] ^ $xor; + $iv = substr($iv, 1) . $xor[0]; + } + + if ($this->continuousBuffer) { + $this->decryptIV = $iv; + } + break; + case self::MODE_OFB: + $xor = $this->decryptIV; + if (strlen($buffer['xor'])) { + for ($i = 0; $i < strlen($ciphertext); $i += $block_size) { + $block = substr($ciphertext, $i, $block_size); + if (strlen($block) > strlen($buffer['xor'])) { + $xor = $this->encryptBlock($xor); + $buffer['xor'] .= $xor; + } + $key = Strings::shift($buffer['xor'], $block_size); + $plaintext .= $block ^ $key; + } + } else { + for ($i = 0; $i < strlen($ciphertext); $i += $block_size) { + $xor = $this->encryptBlock($xor); + $plaintext .= substr($ciphertext, $i, $block_size) ^ $xor; + } + $key = $xor; + } + if ($this->continuousBuffer) { + $this->decryptIV = $xor; + if ($start = strlen($ciphertext) % $block_size) { + $buffer['xor'] = substr($key, $start) . $buffer['xor']; + } + } + break; + case self::MODE_STREAM: + $plaintext = $this->decryptBlock($ciphertext); + break; + } + return $this->paddable ? $this->unpad($plaintext) : $plaintext; + } + + /** + * Get the authentication tag + * + * Only used in GCM or Poly1305 mode + * + * @see self::encrypt() + * @param int $length optional + * @return string + * @throws \LengthException if $length isn't of a sufficient length + * @throws \RuntimeException if GCM mode isn't being used + */ + public function getTag($length = 16) + { + if ($this->mode != self::MODE_GCM && !$this->usePoly1305) { + throw new \BadMethodCallException('Authentication tags are only utilized in GCM mode or with Poly1305'); + } + + if ($this->newtag === false) { + throw new \BadMethodCallException('A tag can only be returned after a round of encryption has been performed'); + } + + // the tag is 128-bits. it can't be greater than 16 bytes because that's bigger than the tag is. if it + // were 0 you might as well be doing CTR and less than 4 provides minimal security that could be trivially + // easily brute forced. + // see https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf#page=36 + // for more info + if ($length < 4 || $length > 16) { + throw new \LengthException('The authentication tag must be between 4 and 16 bytes long'); + } + + return $length == 16 ? + $this->newtag : + substr($this->newtag, 0, $length); + } + + /** + * Sets the authentication tag + * + * Only used in GCM mode + * + * @see self::decrypt() + * @param string $tag + * @throws \LengthException if $length isn't of a sufficient length + * @throws \RuntimeException if GCM mode isn't being used + */ + public function setTag($tag) + { + if ($this->usePoly1305 && !isset($this->poly1305Key) && method_exists($this, 'createPoly1305Key')) { + $this->createPoly1305Key(); + } + + if ($this->mode != self::MODE_GCM && !$this->usePoly1305) { + throw new \BadMethodCallException('Authentication tags are only utilized in GCM mode or with Poly1305'); + } + + $length = strlen($tag); + if ($length < 4 || $length > 16) { + throw new \LengthException('The authentication tag must be between 4 and 16 bytes long'); + } + $this->oldtag = $tag; + } + + /** + * Get the IV + * + * mcrypt requires an IV even if ECB is used + * + * @see self::encrypt() + * @see self::decrypt() + * @param string $iv + * @return string + */ + protected function getIV($iv) + { + return $this->mode == self::MODE_ECB ? str_repeat("\0", $this->block_size) : $iv; + } + + /** + * OpenSSL CTR Processor + * + * PHP's OpenSSL bindings do not operate in continuous mode so we'll wrap around it. Since the keystream + * for CTR is the same for both encrypting and decrypting this function is re-used by both SymmetricKey::encrypt() + * and SymmetricKey::decrypt(). Also, OpenSSL doesn't implement CTR for all of it's symmetric ciphers so this + * function will emulate CTR with ECB when necessary. + * + * @see self::encrypt() + * @see self::decrypt() + * @param string $plaintext + * @param string $encryptIV + * @param array $buffer + * @return string + */ + private function openssl_ctr_process($plaintext, &$encryptIV, &$buffer) + { + $ciphertext = ''; + + $block_size = $this->block_size; + $key = $this->key; + + if ($this->openssl_emulate_ctr) { + $xor = $encryptIV; + if (strlen($buffer['ciphertext'])) { + for ($i = 0; $i < strlen($plaintext); $i += $block_size) { + $block = substr($plaintext, $i, $block_size); + if (strlen($block) > strlen($buffer['ciphertext'])) { + $buffer['ciphertext'] .= openssl_encrypt($xor, $this->cipher_name_openssl_ecb, $key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING); + } + Strings::increment_str($xor); + $otp = Strings::shift($buffer['ciphertext'], $block_size); + $ciphertext .= $block ^ $otp; + } + } else { + for ($i = 0; $i < strlen($plaintext); $i += $block_size) { + $block = substr($plaintext, $i, $block_size); + $otp = openssl_encrypt($xor, $this->cipher_name_openssl_ecb, $key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING); + Strings::increment_str($xor); + $ciphertext .= $block ^ $otp; + } + } + if ($this->continuousBuffer) { + $encryptIV = $xor; + if ($start = strlen($plaintext) % $block_size) { + $buffer['ciphertext'] = substr($key, $start) . $buffer['ciphertext']; + } + } + + return $ciphertext; + } + + if (strlen($buffer['ciphertext'])) { + $ciphertext = $plaintext ^ Strings::shift($buffer['ciphertext'], strlen($plaintext)); + $plaintext = substr($plaintext, strlen($ciphertext)); + + if (!strlen($plaintext)) { + return $ciphertext; + } + } + + $overflow = strlen($plaintext) % $block_size; + if ($overflow) { + $plaintext2 = Strings::pop($plaintext, $overflow); // ie. trim $plaintext to a multiple of $block_size and put rest of $plaintext in $plaintext2 + $encrypted = openssl_encrypt($plaintext . str_repeat("\0", $block_size), $this->cipher_name_openssl, $key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $encryptIV); + $temp = Strings::pop($encrypted, $block_size); + $ciphertext .= $encrypted . ($plaintext2 ^ $temp); + if ($this->continuousBuffer) { + $buffer['ciphertext'] = substr($temp, $overflow); + $encryptIV = $temp; + } + } elseif (!strlen($buffer['ciphertext'])) { + $ciphertext .= openssl_encrypt($plaintext . str_repeat("\0", $block_size), $this->cipher_name_openssl, $key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $encryptIV); + $temp = Strings::pop($ciphertext, $block_size); + if ($this->continuousBuffer) { + $encryptIV = $temp; + } + } + if ($this->continuousBuffer) { + $encryptIV = openssl_decrypt($encryptIV, $this->cipher_name_openssl_ecb, $key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING); + if ($overflow) { + Strings::increment_str($encryptIV); + } + } + + return $ciphertext; + } + + /** + * OpenSSL OFB Processor + * + * PHP's OpenSSL bindings do not operate in continuous mode so we'll wrap around it. Since the keystream + * for OFB is the same for both encrypting and decrypting this function is re-used by both SymmetricKey::encrypt() + * and SymmetricKey::decrypt(). + * + * @see self::encrypt() + * @see self::decrypt() + * @param string $plaintext + * @param string $encryptIV + * @param array $buffer + * @return string + */ + private function openssl_ofb_process($plaintext, &$encryptIV, &$buffer) + { + if (strlen($buffer['xor'])) { + $ciphertext = $plaintext ^ $buffer['xor']; + $buffer['xor'] = substr($buffer['xor'], strlen($ciphertext)); + $plaintext = substr($plaintext, strlen($ciphertext)); + } else { + $ciphertext = ''; + } + + $block_size = $this->block_size; + + $len = strlen($plaintext); + $key = $this->key; + $overflow = $len % $block_size; + + if (strlen($plaintext)) { + if ($overflow) { + $ciphertext .= openssl_encrypt(substr($plaintext, 0, -$overflow) . str_repeat("\0", $block_size), $this->cipher_name_openssl, $key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $encryptIV); + $xor = Strings::pop($ciphertext, $block_size); + if ($this->continuousBuffer) { + $encryptIV = $xor; + } + $ciphertext .= Strings::shift($xor, $overflow) ^ substr($plaintext, -$overflow); + if ($this->continuousBuffer) { + $buffer['xor'] = $xor; + } + } else { + $ciphertext = openssl_encrypt($plaintext, $this->cipher_name_openssl, $key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $encryptIV); + if ($this->continuousBuffer) { + $encryptIV = substr($ciphertext, -$block_size) ^ substr($plaintext, -$block_size); + } + } + } + + return $ciphertext; + } + + /** + * phpseclib <-> OpenSSL Mode Mapper + * + * May need to be overwritten by classes extending this one in some cases + * + * @return string + */ + protected function openssl_translate_mode() + { + switch ($this->mode) { + case self::MODE_ECB: + return 'ecb'; + case self::MODE_CBC: + return 'cbc'; + case self::MODE_CTR: + case self::MODE_GCM: + return 'ctr'; + case self::MODE_CFB: + return 'cfb'; + case self::MODE_CFB8: + return 'cfb8'; + case self::MODE_OFB: + return 'ofb'; + } + } + + /** + * Pad "packets". + * + * Block ciphers working by encrypting between their specified [$this->]block_size at a time + * If you ever need to encrypt or decrypt something that isn't of the proper length, it becomes necessary to + * pad the input so that it is of the proper length. + * + * Padding is enabled by default. Sometimes, however, it is undesirable to pad strings. Such is the case in SSH, + * where "packets" are padded with random bytes before being encrypted. Unpad these packets and you risk stripping + * away characters that shouldn't be stripped away. (SSH knows how many bytes are added because the length is + * transmitted separately) + * + * @see self::disablePadding() + */ + public function enablePadding() + { + $this->padding = true; + } + + /** + * Do not pad packets. + * + * @see self::enablePadding() + */ + public function disablePadding() + { + $this->padding = false; + } + + /** + * Treat consecutive "packets" as if they are a continuous buffer. + * + * Say you have a 32-byte plaintext $plaintext. Using the default behavior, the two following code snippets + * will yield different outputs: + * + * + * echo $rijndael->encrypt(substr($plaintext, 0, 16)); + * echo $rijndael->encrypt(substr($plaintext, 16, 16)); + * + * + * echo $rijndael->encrypt($plaintext); + * + * + * The solution is to enable the continuous buffer. Although this will resolve the above discrepancy, it creates + * another, as demonstrated with the following: + * + * + * $rijndael->encrypt(substr($plaintext, 0, 16)); + * echo $rijndael->decrypt($rijndael->encrypt(substr($plaintext, 16, 16))); + * + * + * echo $rijndael->decrypt($rijndael->encrypt(substr($plaintext, 16, 16))); + * + * + * With the continuous buffer disabled, these would yield the same output. With it enabled, they yield different + * outputs. The reason is due to the fact that the initialization vector's change after every encryption / + * decryption round when the continuous buffer is enabled. When it's disabled, they remain constant. + * + * Put another way, when the continuous buffer is enabled, the state of the \phpseclib3\Crypt\*() object changes after each + * encryption / decryption round, whereas otherwise, it'd remain constant. For this reason, it's recommended that + * continuous buffers not be used. They do offer better security and are, in fact, sometimes required (SSH uses them), + * however, they are also less intuitive and more likely to cause you problems. + * + * {@internal Could, but not must, extend by the child Crypt_* class} + * + * @see self::disableContinuousBuffer() + */ + public function enableContinuousBuffer() + { + if ($this->mode == self::MODE_ECB) { + return; + } + + if ($this->mode == self::MODE_GCM) { + throw new \BadMethodCallException('This mode does not run in continuous mode'); + } + + $this->continuousBuffer = true; + + $this->setEngine(); + } + + /** + * Treat consecutive packets as if they are a discontinuous buffer. + * + * The default behavior. + * + * {@internal Could, but not must, extend by the child Crypt_* class} + * + * @see self::enableContinuousBuffer() + */ + public function disableContinuousBuffer() + { + if ($this->mode == self::MODE_ECB) { + return; + } + if (!$this->continuousBuffer) { + return; + } + + $this->continuousBuffer = false; + + $this->setEngine(); + } + + /** + * Test for engine validity + * + * @see self::__construct() + * @param int $engine + * @return bool + */ + protected function isValidEngineHelper($engine) + { + switch ($engine) { + case self::ENGINE_OPENSSL: + $this->openssl_emulate_ctr = false; + $result = $this->cipher_name_openssl && + extension_loaded('openssl'); + if (!$result) { + return false; + } + + $methods = openssl_get_cipher_methods(); + if (in_array($this->cipher_name_openssl, $methods)) { + return true; + } + // not all of openssl's symmetric cipher's support ctr. for those + // that don't we'll emulate it + switch ($this->mode) { + case self::MODE_CTR: + if (in_array($this->cipher_name_openssl_ecb, $methods)) { + $this->openssl_emulate_ctr = true; + return true; + } + } + return false; + case self::ENGINE_MCRYPT: + set_error_handler(function () { + }); + $result = $this->cipher_name_mcrypt && + extension_loaded('mcrypt') && + in_array($this->cipher_name_mcrypt, mcrypt_list_algorithms()); + restore_error_handler(); + return $result; + case self::ENGINE_EVAL: + return method_exists($this, 'setupInlineCrypt'); + case self::ENGINE_INTERNAL: + return true; + } + + return false; + } + + /** + * Test for engine validity + * + * @see self::__construct() + * @param string $engine + * @return bool + */ + public function isValidEngine($engine) + { + static $reverseMap; + if (!isset($reverseMap)) { + $reverseMap = array_map('strtolower', self::ENGINE_MAP); + $reverseMap = array_flip($reverseMap); + } + $engine = strtolower($engine); + if (!isset($reverseMap[$engine])) { + return false; + } + + return $this->isValidEngineHelper($reverseMap[$engine]); + } + + /** + * Sets the preferred crypt engine + * + * Currently, $engine could be: + * + * - libsodium[very fast] + * + * - OpenSSL [very fast] + * + * - mcrypt [fast] + * + * - Eval [slow] + * + * - PHP [slowest] + * + * If the preferred crypt engine is not available the fastest available one will be used + * + * @see self::__construct() + * @param string $engine + */ + public function setPreferredEngine($engine) + { + static $reverseMap; + if (!isset($reverseMap)) { + $reverseMap = array_map('strtolower', self::ENGINE_MAP); + $reverseMap = array_flip($reverseMap); + } + $engine = is_string($engine) ? strtolower($engine) : ''; + $this->preferredEngine = isset($reverseMap[$engine]) ? $reverseMap[$engine] : self::ENGINE_LIBSODIUM; + + $this->setEngine(); + } + + /** + * Returns the engine currently being utilized + * + * @see self::setEngine() + */ + public function getEngine() + { + return self::ENGINE_MAP[$this->engine]; + } + + /** + * Sets the engine as appropriate + * + * @see self::__construct() + */ + protected function setEngine() + { + $this->engine = null; + + $candidateEngines = [ + self::ENGINE_LIBSODIUM, + self::ENGINE_OPENSSL_GCM, + self::ENGINE_OPENSSL, + self::ENGINE_MCRYPT, + self::ENGINE_EVAL + ]; + if (isset($this->preferredEngine)) { + $temp = [$this->preferredEngine]; + $candidateEngines = array_merge( + $temp, + array_diff($candidateEngines, $temp) + ); + } + foreach ($candidateEngines as $engine) { + if ($this->isValidEngineHelper($engine)) { + $this->engine = $engine; + break; + } + } + if (!$this->engine) { + $this->engine = self::ENGINE_INTERNAL; + } + + if ($this->engine != self::ENGINE_MCRYPT && $this->enmcrypt) { + set_error_handler(function () { + }); + // Closing the current mcrypt resource(s). _mcryptSetup() will, if needed, + // (re)open them with the module named in $this->cipher_name_mcrypt + mcrypt_module_close($this->enmcrypt); + mcrypt_module_close($this->demcrypt); + $this->enmcrypt = null; + $this->demcrypt = null; + + if ($this->ecb) { + mcrypt_module_close($this->ecb); + $this->ecb = null; + } + restore_error_handler(); + } + + $this->changed = $this->nonIVChanged = true; + } + + /** + * Encrypts a block + * + * Note: Must be extended by the child \phpseclib3\Crypt\* class + * + * @param string $in + * @return string + */ + abstract protected function encryptBlock($in); + + /** + * Decrypts a block + * + * Note: Must be extended by the child \phpseclib3\Crypt\* class + * + * @param string $in + * @return string + */ + abstract protected function decryptBlock($in); + + /** + * Setup the key (expansion) + * + * Only used if $engine == self::ENGINE_INTERNAL + * + * Note: Must extend by the child \phpseclib3\Crypt\* class + * + * @see self::setup() + */ + abstract protected function setupKey(); + + /** + * Setup the self::ENGINE_INTERNAL $engine + * + * (re)init, if necessary, the internal cipher $engine and flush all $buffers + * Used (only) if $engine == self::ENGINE_INTERNAL + * + * _setup() will be called each time if $changed === true + * typically this happens when using one or more of following public methods: + * + * - setKey() + * + * - setIV() + * + * - disableContinuousBuffer() + * + * - First run of encrypt() / decrypt() with no init-settings + * + * {@internal setup() is always called before en/decryption.} + * + * {@internal Could, but not must, extend by the child Crypt_* class} + * + * @see self::setKey() + * @see self::setIV() + * @see self::disableContinuousBuffer() + */ + protected function setup() + { + if (!$this->changed) { + return; + } + + $this->changed = false; + + if ($this->usePoly1305 && !isset($this->poly1305Key) && method_exists($this, 'createPoly1305Key')) { + $this->createPoly1305Key(); + } + + $this->enbuffer = $this->debuffer = ['ciphertext' => '', 'xor' => '', 'pos' => 0, 'enmcrypt_init' => true]; + //$this->newtag = $this->oldtag = false; + + if ($this->usesNonce()) { + if ($this->nonce === false) { + throw new InsufficientSetupException('No nonce has been defined'); + } + if ($this->mode == self::MODE_GCM && !in_array($this->engine, [self::ENGINE_LIBSODIUM, self::ENGINE_OPENSSL_GCM])) { + $this->setupGCM(); + } + } else { + $this->iv = $this->origIV; + } + + if ($this->iv === false && !in_array($this->mode, [self::MODE_STREAM, self::MODE_ECB])) { + if ($this->mode != self::MODE_GCM || !in_array($this->engine, [self::ENGINE_LIBSODIUM, self::ENGINE_OPENSSL_GCM])) { + throw new InsufficientSetupException('No IV has been defined'); + } + } + + if ($this->key === false) { + throw new InsufficientSetupException('No key has been defined'); + } + + $this->encryptIV = $this->decryptIV = $this->iv; + + switch ($this->engine) { + case self::ENGINE_MCRYPT: + $this->enchanged = $this->dechanged = true; + + set_error_handler(function () { + }); + + if (!isset($this->enmcrypt)) { + static $mcrypt_modes = [ + self::MODE_CTR => 'ctr', + self::MODE_ECB => MCRYPT_MODE_ECB, + self::MODE_CBC => MCRYPT_MODE_CBC, + self::MODE_CFB => 'ncfb', + self::MODE_CFB8 => MCRYPT_MODE_CFB, + self::MODE_OFB => MCRYPT_MODE_NOFB, + self::MODE_OFB8 => MCRYPT_MODE_OFB, + self::MODE_STREAM => MCRYPT_MODE_STREAM, + ]; + + $this->demcrypt = mcrypt_module_open($this->cipher_name_mcrypt, '', $mcrypt_modes[$this->mode], ''); + $this->enmcrypt = mcrypt_module_open($this->cipher_name_mcrypt, '', $mcrypt_modes[$this->mode], ''); + + // we need the $ecb mcrypt resource (only) in MODE_CFB with enableContinuousBuffer() + // to workaround mcrypt's broken ncfb implementation in buffered mode + // see: {@link http://phpseclib.sourceforge.net/cfb-demo.phps} + if ($this->mode == self::MODE_CFB) { + $this->ecb = mcrypt_module_open($this->cipher_name_mcrypt, '', MCRYPT_MODE_ECB, ''); + } + } // else should mcrypt_generic_deinit be called? + + if ($this->mode == self::MODE_CFB) { + mcrypt_generic_init($this->ecb, $this->key, str_repeat("\0", $this->block_size)); + } + + restore_error_handler(); + + break; + case self::ENGINE_INTERNAL: + $this->setupKey(); + break; + case self::ENGINE_EVAL: + if ($this->nonIVChanged) { + $this->setupKey(); + $this->setupInlineCrypt(); + } + } + + $this->nonIVChanged = false; + } + + /** + * Pads a string + * + * Pads a string using the RSA PKCS padding standards so that its length is a multiple of the blocksize. + * $this->block_size - (strlen($text) % $this->block_size) bytes are added, each of which is equal to + * chr($this->block_size - (strlen($text) % $this->block_size) + * + * If padding is disabled and $text is not a multiple of the blocksize, the string will be padded regardless + * and padding will, hence forth, be enabled. + * + * @see self::unpad() + * @param string $text + * @throws \LengthException if padding is disabled and the plaintext's length is not a multiple of the block size + * @return string + */ + protected function pad($text) + { + $length = strlen($text); + + if (!$this->padding) { + if ($length % $this->block_size == 0) { + return $text; + } else { + throw new \LengthException("The plaintext's length ($length) is not a multiple of the block size ({$this->block_size}). Try enabling padding."); + } + } + + $pad = $this->block_size - ($length % $this->block_size); + + return str_pad($text, $length + $pad, chr($pad)); + } + + /** + * Unpads a string. + * + * If padding is enabled and the reported padding length is invalid the encryption key will be assumed to be wrong + * and false will be returned. + * + * @see self::pad() + * @param string $text + * @throws \LengthException if the ciphertext's length is not a multiple of the block size + * @return string + */ + protected function unpad($text) + { + if (!$this->padding) { + return $text; + } + + $length = ord($text[strlen($text) - 1]); + + if (!$length || $length > $this->block_size) { + throw new BadDecryptionException("The ciphertext has an invalid padding length ($length) compared to the block size ({$this->block_size})"); + } + + return substr($text, 0, -$length); + } + + /** + * Setup the performance-optimized function for de/encrypt() + * + * Stores the created (or existing) callback function-name + * in $this->inline_crypt + * + * Internally for phpseclib developers: + * + * _setupInlineCrypt() would be called only if: + * + * - $this->engine === self::ENGINE_EVAL + * + * - each time on _setup(), after(!) _setupKey() + * + * + * This ensures that _setupInlineCrypt() has always a + * full ready2go initializated internal cipher $engine state + * where, for example, the keys already expanded, + * keys/block_size calculated and such. + * + * It is, each time if called, the responsibility of _setupInlineCrypt(): + * + * - to set $this->inline_crypt to a valid and fully working callback function + * as a (faster) replacement for encrypt() / decrypt() + * + * - NOT to create unlimited callback functions (for memory reasons!) + * no matter how often _setupInlineCrypt() would be called. At some + * point of amount they must be generic re-useable. + * + * - the code of _setupInlineCrypt() it self, + * and the generated callback code, + * must be, in following order: + * - 100% safe + * - 100% compatible to encrypt()/decrypt() + * - using only php5+ features/lang-constructs/php-extensions if + * compatibility (down to php4) or fallback is provided + * - readable/maintainable/understandable/commented and... not-cryptic-styled-code :-) + * - >= 10% faster than encrypt()/decrypt() [which is, by the way, + * the reason for the existence of _setupInlineCrypt() :-)] + * - memory-nice + * - short (as good as possible) + * + * Note: - _setupInlineCrypt() is using _createInlineCryptFunction() to create the full callback function code. + * - In case of using inline crypting, _setupInlineCrypt() must extend by the child \phpseclib3\Crypt\* class. + * - The following variable names are reserved: + * - $_* (all variable names prefixed with an underscore) + * - $self (object reference to it self. Do not use $this, but $self instead) + * - $in (the content of $in has to en/decrypt by the generated code) + * - The callback function should not use the 'return' statement, but en/decrypt'ing the content of $in only + * + * {@internal If a Crypt_* class providing inline crypting it must extend _setupInlineCrypt()} + * + * @see self::setup() + * @see self::createInlineCryptFunction() + * @see self::encrypt() + * @see self::decrypt() + */ + //protected function setupInlineCrypt(); + + /** + * Creates the performance-optimized function for en/decrypt() + * + * Internally for phpseclib developers: + * + * _createInlineCryptFunction(): + * + * - merge the $cipher_code [setup'ed by _setupInlineCrypt()] + * with the current [$this->]mode of operation code + * + * - create the $inline function, which called by encrypt() / decrypt() + * as its replacement to speed up the en/decryption operations. + * + * - return the name of the created $inline callback function + * + * - used to speed up en/decryption + * + * + * + * The main reason why can speed up things [up to 50%] this way are: + * + * - using variables more effective then regular. + * (ie no use of expensive arrays but integers $k_0, $k_1 ... + * or even, for example, the pure $key[] values hardcoded) + * + * - avoiding 1000's of function calls of ie _encryptBlock() + * but inlining the crypt operations. + * in the mode of operation for() loop. + * + * - full loop unroll the (sometimes key-dependent) rounds + * avoiding this way ++$i counters and runtime-if's etc... + * + * The basic code architectur of the generated $inline en/decrypt() + * lambda function, in pseudo php, is: + * + * + * +----------------------------------------------------------------------------------------------+ + * | callback $inline = create_function: | + * | lambda_function_0001_crypt_ECB($action, $text) | + * | { | + * | INSERT PHP CODE OF: | + * | $cipher_code['init_crypt']; // general init code. | + * | // ie: $sbox'es declarations used for | + * | // encrypt and decrypt'ing. | + * | | + * | switch ($action) { | + * | case 'encrypt': | + * | INSERT PHP CODE OF: | + * | $cipher_code['init_encrypt']; // encrypt sepcific init code. | + * | ie: specified $key or $box | + * | declarations for encrypt'ing. | + * | | + * | foreach ($ciphertext) { | + * | $in = $block_size of $ciphertext; | + * | | + * | INSERT PHP CODE OF: | + * | $cipher_code['encrypt_block']; // encrypt's (string) $in, which is always: | + * | // strlen($in) == $this->block_size | + * | // here comes the cipher algorithm in action | + * | // for encryption. | + * | // $cipher_code['encrypt_block'] has to | + * | // encrypt the content of the $in variable | + * | | + * | $plaintext .= $in; | + * | } | + * | return $plaintext; | + * | | + * | case 'decrypt': | + * | INSERT PHP CODE OF: | + * | $cipher_code['init_decrypt']; // decrypt sepcific init code | + * | ie: specified $key or $box | + * | declarations for decrypt'ing. | + * | foreach ($plaintext) { | + * | $in = $block_size of $plaintext; | + * | | + * | INSERT PHP CODE OF: | + * | $cipher_code['decrypt_block']; // decrypt's (string) $in, which is always | + * | // strlen($in) == $this->block_size | + * | // here comes the cipher algorithm in action | + * | // for decryption. | + * | // $cipher_code['decrypt_block'] has to | + * | // decrypt the content of the $in variable | + * | $ciphertext .= $in; | + * | } | + * | return $ciphertext; | + * | } | + * | } | + * +----------------------------------------------------------------------------------------------+ + * + * + * See also the \phpseclib3\Crypt\*::_setupInlineCrypt()'s for + * productive inline $cipher_code's how they works. + * + * Structure of: + * + * $cipher_code = [ + * 'init_crypt' => (string) '', // optional + * 'init_encrypt' => (string) '', // optional + * 'init_decrypt' => (string) '', // optional + * 'encrypt_block' => (string) '', // required + * 'decrypt_block' => (string) '' // required + * ]; + * + * + * @see self::setupInlineCrypt() + * @see self::encrypt() + * @see self::decrypt() + * @param array $cipher_code + * @return string (the name of the created callback function) + */ + protected function createInlineCryptFunction($cipher_code) + { + $block_size = $this->block_size; + + // optional + $init_crypt = isset($cipher_code['init_crypt']) ? $cipher_code['init_crypt'] : ''; + $init_encrypt = isset($cipher_code['init_encrypt']) ? $cipher_code['init_encrypt'] : ''; + $init_decrypt = isset($cipher_code['init_decrypt']) ? $cipher_code['init_decrypt'] : ''; + // required + $encrypt_block = $cipher_code['encrypt_block']; + $decrypt_block = $cipher_code['decrypt_block']; + + // Generating mode of operation inline code, + // merged with the $cipher_code algorithm + // for encrypt- and decryption. + switch ($this->mode) { + case self::MODE_ECB: + $encrypt = $init_encrypt . ' + $_ciphertext = ""; + $_plaintext_len = strlen($_text); + + for ($_i = 0; $_i < $_plaintext_len; $_i+= ' . $block_size . ') { + $in = substr($_text, $_i, ' . $block_size . '); + ' . $encrypt_block . ' + $_ciphertext.= $in; + } + + return $_ciphertext; + '; + + $decrypt = $init_decrypt . ' + $_plaintext = ""; + $_text = str_pad($_text, strlen($_text) + (' . $block_size . ' - strlen($_text) % ' . $block_size . ') % ' . $block_size . ', chr(0)); + $_ciphertext_len = strlen($_text); + + for ($_i = 0; $_i < $_ciphertext_len; $_i+= ' . $block_size . ') { + $in = substr($_text, $_i, ' . $block_size . '); + ' . $decrypt_block . ' + $_plaintext.= $in; + } + + return $this->unpad($_plaintext); + '; + break; + case self::MODE_CTR: + $encrypt = $init_encrypt . ' + $_ciphertext = ""; + $_plaintext_len = strlen($_text); + $_xor = $this->encryptIV; + $_buffer = &$this->enbuffer; + if (strlen($_buffer["ciphertext"])) { + for ($_i = 0; $_i < $_plaintext_len; $_i+= ' . $block_size . ') { + $_block = substr($_text, $_i, ' . $block_size . '); + if (strlen($_block) > strlen($_buffer["ciphertext"])) { + $in = $_xor; + ' . $encrypt_block . ' + \phpseclib3\Common\Functions\Strings::increment_str($_xor); + $_buffer["ciphertext"].= $in; + } + $_key = \phpseclib3\Common\Functions\Strings::shift($_buffer["ciphertext"], ' . $block_size . '); + $_ciphertext.= $_block ^ $_key; + } + } else { + for ($_i = 0; $_i < $_plaintext_len; $_i+= ' . $block_size . ') { + $_block = substr($_text, $_i, ' . $block_size . '); + $in = $_xor; + ' . $encrypt_block . ' + \phpseclib3\Common\Functions\Strings::increment_str($_xor); + $_key = $in; + $_ciphertext.= $_block ^ $_key; + } + } + if ($this->continuousBuffer) { + $this->encryptIV = $_xor; + if ($_start = $_plaintext_len % ' . $block_size . ') { + $_buffer["ciphertext"] = substr($_key, $_start) . $_buffer["ciphertext"]; + } + } + + return $_ciphertext; + '; + + $decrypt = $init_encrypt . ' + $_plaintext = ""; + $_ciphertext_len = strlen($_text); + $_xor = $this->decryptIV; + $_buffer = &$this->debuffer; + + if (strlen($_buffer["ciphertext"])) { + for ($_i = 0; $_i < $_ciphertext_len; $_i+= ' . $block_size . ') { + $_block = substr($_text, $_i, ' . $block_size . '); + if (strlen($_block) > strlen($_buffer["ciphertext"])) { + $in = $_xor; + ' . $encrypt_block . ' + \phpseclib3\Common\Functions\Strings::increment_str($_xor); + $_buffer["ciphertext"].= $in; + } + $_key = \phpseclib3\Common\Functions\Strings::shift($_buffer["ciphertext"], ' . $block_size . '); + $_plaintext.= $_block ^ $_key; + } + } else { + for ($_i = 0; $_i < $_ciphertext_len; $_i+= ' . $block_size . ') { + $_block = substr($_text, $_i, ' . $block_size . '); + $in = $_xor; + ' . $encrypt_block . ' + \phpseclib3\Common\Functions\Strings::increment_str($_xor); + $_key = $in; + $_plaintext.= $_block ^ $_key; + } + } + if ($this->continuousBuffer) { + $this->decryptIV = $_xor; + if ($_start = $_ciphertext_len % ' . $block_size . ') { + $_buffer["ciphertext"] = substr($_key, $_start) . $_buffer["ciphertext"]; + } + } + + return $_plaintext; + '; + break; + case self::MODE_CFB: + $encrypt = $init_encrypt . ' + $_ciphertext = ""; + $_buffer = &$this->enbuffer; + + if ($this->continuousBuffer) { + $_iv = &$this->encryptIV; + $_pos = &$_buffer["pos"]; + } else { + $_iv = $this->encryptIV; + $_pos = 0; + } + $_len = strlen($_text); + $_i = 0; + if ($_pos) { + $_orig_pos = $_pos; + $_max = ' . $block_size . ' - $_pos; + if ($_len >= $_max) { + $_i = $_max; + $_len-= $_max; + $_pos = 0; + } else { + $_i = $_len; + $_pos+= $_len; + $_len = 0; + } + $_ciphertext = substr($_iv, $_orig_pos) ^ $_text; + $_iv = substr_replace($_iv, $_ciphertext, $_orig_pos, $_i); + } + while ($_len >= ' . $block_size . ') { + $in = $_iv; + ' . $encrypt_block . '; + $_iv = $in ^ substr($_text, $_i, ' . $block_size . '); + $_ciphertext.= $_iv; + $_len-= ' . $block_size . '; + $_i+= ' . $block_size . '; + } + if ($_len) { + $in = $_iv; + ' . $encrypt_block . ' + $_iv = $in; + $_block = $_iv ^ substr($_text, $_i); + $_iv = substr_replace($_iv, $_block, 0, $_len); + $_ciphertext.= $_block; + $_pos = $_len; + } + return $_ciphertext; + '; + + $decrypt = $init_encrypt . ' + $_plaintext = ""; + $_buffer = &$this->debuffer; + + if ($this->continuousBuffer) { + $_iv = &$this->decryptIV; + $_pos = &$_buffer["pos"]; + } else { + $_iv = $this->decryptIV; + $_pos = 0; + } + $_len = strlen($_text); + $_i = 0; + if ($_pos) { + $_orig_pos = $_pos; + $_max = ' . $block_size . ' - $_pos; + if ($_len >= $_max) { + $_i = $_max; + $_len-= $_max; + $_pos = 0; + } else { + $_i = $_len; + $_pos+= $_len; + $_len = 0; + } + $_plaintext = substr($_iv, $_orig_pos) ^ $_text; + $_iv = substr_replace($_iv, substr($_text, 0, $_i), $_orig_pos, $_i); + } + while ($_len >= ' . $block_size . ') { + $in = $_iv; + ' . $encrypt_block . ' + $_iv = $in; + $cb = substr($_text, $_i, ' . $block_size . '); + $_plaintext.= $_iv ^ $cb; + $_iv = $cb; + $_len-= ' . $block_size . '; + $_i+= ' . $block_size . '; + } + if ($_len) { + $in = $_iv; + ' . $encrypt_block . ' + $_iv = $in; + $_plaintext.= $_iv ^ substr($_text, $_i); + $_iv = substr_replace($_iv, substr($_text, $_i), 0, $_len); + $_pos = $_len; + } + + return $_plaintext; + '; + break; + case self::MODE_CFB8: + $encrypt = $init_encrypt . ' + $_ciphertext = ""; + $_len = strlen($_text); + $_iv = $this->encryptIV; + + for ($_i = 0; $_i < $_len; ++$_i) { + $in = $_iv; + ' . $encrypt_block . ' + $_ciphertext .= ($_c = $_text[$_i] ^ $in); + $_iv = substr($_iv, 1) . $_c; + } + + if ($this->continuousBuffer) { + if ($_len >= ' . $block_size . ') { + $this->encryptIV = substr($_ciphertext, -' . $block_size . '); + } else { + $this->encryptIV = substr($this->encryptIV, $_len - ' . $block_size . ') . substr($_ciphertext, -$_len); + } + } + + return $_ciphertext; + '; + $decrypt = $init_encrypt . ' + $_plaintext = ""; + $_len = strlen($_text); + $_iv = $this->decryptIV; + + for ($_i = 0; $_i < $_len; ++$_i) { + $in = $_iv; + ' . $encrypt_block . ' + $_plaintext .= $_text[$_i] ^ $in; + $_iv = substr($_iv, 1) . $_text[$_i]; + } + + if ($this->continuousBuffer) { + if ($_len >= ' . $block_size . ') { + $this->decryptIV = substr($_text, -' . $block_size . '); + } else { + $this->decryptIV = substr($this->decryptIV, $_len - ' . $block_size . ') . substr($_text, -$_len); + } + } + + return $_plaintext; + '; + break; + case self::MODE_OFB8: + $encrypt = $init_encrypt . ' + $_ciphertext = ""; + $_len = strlen($_text); + $_iv = $this->encryptIV; + + for ($_i = 0; $_i < $_len; ++$_i) { + $in = $_iv; + ' . $encrypt_block . ' + $_ciphertext.= $_text[$_i] ^ $in; + $_iv = substr($_iv, 1) . $in[0]; + } + + if ($this->continuousBuffer) { + $this->encryptIV = $_iv; + } + + return $_ciphertext; + '; + $decrypt = $init_encrypt . ' + $_plaintext = ""; + $_len = strlen($_text); + $_iv = $this->decryptIV; + + for ($_i = 0; $_i < $_len; ++$_i) { + $in = $_iv; + ' . $encrypt_block . ' + $_plaintext.= $_text[$_i] ^ $in; + $_iv = substr($_iv, 1) . $in[0]; + } + + if ($this->continuousBuffer) { + $this->decryptIV = $_iv; + } + + return $_plaintext; + '; + break; + case self::MODE_OFB: + $encrypt = $init_encrypt . ' + $_ciphertext = ""; + $_plaintext_len = strlen($_text); + $_xor = $this->encryptIV; + $_buffer = &$this->enbuffer; + + if (strlen($_buffer["xor"])) { + for ($_i = 0; $_i < $_plaintext_len; $_i+= ' . $block_size . ') { + $_block = substr($_text, $_i, ' . $block_size . '); + if (strlen($_block) > strlen($_buffer["xor"])) { + $in = $_xor; + ' . $encrypt_block . ' + $_xor = $in; + $_buffer["xor"].= $_xor; + } + $_key = \phpseclib3\Common\Functions\Strings::shift($_buffer["xor"], ' . $block_size . '); + $_ciphertext.= $_block ^ $_key; + } + } else { + for ($_i = 0; $_i < $_plaintext_len; $_i+= ' . $block_size . ') { + $in = $_xor; + ' . $encrypt_block . ' + $_xor = $in; + $_ciphertext.= substr($_text, $_i, ' . $block_size . ') ^ $_xor; + } + $_key = $_xor; + } + if ($this->continuousBuffer) { + $this->encryptIV = $_xor; + if ($_start = $_plaintext_len % ' . $block_size . ') { + $_buffer["xor"] = substr($_key, $_start) . $_buffer["xor"]; + } + } + return $_ciphertext; + '; + + $decrypt = $init_encrypt . ' + $_plaintext = ""; + $_ciphertext_len = strlen($_text); + $_xor = $this->decryptIV; + $_buffer = &$this->debuffer; + + if (strlen($_buffer["xor"])) { + for ($_i = 0; $_i < $_ciphertext_len; $_i+= ' . $block_size . ') { + $_block = substr($_text, $_i, ' . $block_size . '); + if (strlen($_block) > strlen($_buffer["xor"])) { + $in = $_xor; + ' . $encrypt_block . ' + $_xor = $in; + $_buffer["xor"].= $_xor; + } + $_key = \phpseclib3\Common\Functions\Strings::shift($_buffer["xor"], ' . $block_size . '); + $_plaintext.= $_block ^ $_key; + } + } else { + for ($_i = 0; $_i < $_ciphertext_len; $_i+= ' . $block_size . ') { + $in = $_xor; + ' . $encrypt_block . ' + $_xor = $in; + $_plaintext.= substr($_text, $_i, ' . $block_size . ') ^ $_xor; + } + $_key = $_xor; + } + if ($this->continuousBuffer) { + $this->decryptIV = $_xor; + if ($_start = $_ciphertext_len % ' . $block_size . ') { + $_buffer["xor"] = substr($_key, $_start) . $_buffer["xor"]; + } + } + return $_plaintext; + '; + break; + case self::MODE_STREAM: + $encrypt = $init_encrypt . ' + $_ciphertext = ""; + ' . $encrypt_block . ' + return $_ciphertext; + '; + $decrypt = $init_decrypt . ' + $_plaintext = ""; + ' . $decrypt_block . ' + return $_plaintext; + '; + break; + // case self::MODE_CBC: + default: + $encrypt = $init_encrypt . ' + $_ciphertext = ""; + $_plaintext_len = strlen($_text); + + $in = $this->encryptIV; + + for ($_i = 0; $_i < $_plaintext_len; $_i+= ' . $block_size . ') { + $in = substr($_text, $_i, ' . $block_size . ') ^ $in; + ' . $encrypt_block . ' + $_ciphertext.= $in; + } + + if ($this->continuousBuffer) { + $this->encryptIV = $in; + } + + return $_ciphertext; + '; + + $decrypt = $init_decrypt . ' + $_plaintext = ""; + $_text = str_pad($_text, strlen($_text) + (' . $block_size . ' - strlen($_text) % ' . $block_size . ') % ' . $block_size . ', chr(0)); + $_ciphertext_len = strlen($_text); + + $_iv = $this->decryptIV; + + for ($_i = 0; $_i < $_ciphertext_len; $_i+= ' . $block_size . ') { + $in = $_block = substr($_text, $_i, ' . $block_size . '); + ' . $decrypt_block . ' + $_plaintext.= $in ^ $_iv; + $_iv = $_block; + } + + if ($this->continuousBuffer) { + $this->decryptIV = $_iv; + } + + return $this->unpad($_plaintext); + '; + break; + } + + // Before discrediting this, please read the following: + // @see https://github.com/phpseclib/phpseclib/issues/1293 + // @see https://github.com/phpseclib/phpseclib/pull/1143 + eval('$func = function ($_action, $_text) { ' . $init_crypt . 'if ($_action == "encrypt") { ' . $encrypt . ' } else { ' . $decrypt . ' }};'); + + return \Closure::bind($func, $this, static::class); + } + + /** + * Convert float to int + * + * On ARM CPUs converting floats to ints doesn't always work + * + * @param string $x + * @return int + */ + protected static function safe_intval($x) + { + if (is_int($x)) { + return $x; + } + + if (self::$use_reg_intval) { + return PHP_INT_SIZE == 4 && PHP_VERSION_ID >= 80100 ? intval($x) : $x; + } + + return (fmod($x, 0x80000000) & 0x7FFFFFFF) | + ((fmod(floor($x / 0x80000000), 2) & 1) << 31); + } + + /** + * eval()'able string for in-line float to int + * + * @return string + */ + protected static function safe_intval_inline() + { + if (self::$use_reg_intval) { + return PHP_INT_SIZE == 4 && PHP_VERSION_ID >= 80100 ? 'intval(%s)' : '%s'; + } + + $safeint = '(is_int($temp = %s) ? $temp : (fmod($temp, 0x80000000) & 0x7FFFFFFF) | '; + return $safeint . '((fmod(floor($temp / 0x80000000), 2) & 1) << 31))'; + } + + /** + * Sets up GCM parameters + * + * See steps 1-2 of https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf#page=23 + * for more info + * + */ + private function setupGCM() + { + // don't keep on re-calculating $this->h + if (!$this->h || $this->hKey != $this->key) { + $cipher = new static('ecb'); + $cipher->setKey($this->key); + $cipher->disablePadding(); + + $this->h = self::$gcmField->newInteger( + Strings::switchEndianness($cipher->encrypt("\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0")) + ); + $this->hKey = $this->key; + } + + if (strlen($this->nonce) == 12) { + $this->iv = $this->nonce . "\0\0\0\1"; + } else { + $this->iv = $this->ghash( + self::nullPad128($this->nonce) . str_repeat("\0", 8) . self::len64($this->nonce) + ); + } + } + + /** + * Performs GHASH operation + * + * See https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf#page=20 + * for more info + * + * @see self::decrypt() + * @see self::encrypt() + * @param string $x + * @return string + */ + private function ghash($x) + { + $h = $this->h; + $y = ["\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"]; + $x = str_split($x, 16); + $n = 0; + // the switchEndianness calls are necessary because the multiplication algorithm in BinaryField/Integer + // interprets strings as polynomials in big endian order whereas in GCM they're interpreted in little + // endian order per https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf#page=19. + // big endian order is what binary field elliptic curves use per http://www.secg.org/sec1-v2.pdf#page=18. + + // we could switchEndianness here instead of in the while loop but doing so in the while loop seems like it + // might be slightly more performant + //$x = Strings::switchEndianness($x); + foreach ($x as $xn) { + $xn = Strings::switchEndianness($xn); + $t = $y[$n] ^ $xn; + $temp = self::$gcmField->newInteger($t); + $y[++$n] = $temp->multiply($h)->toBytes(); + $y[$n] = substr($y[$n], 1); + } + $y[$n] = Strings::switchEndianness($y[$n]); + return $y[$n]; + } + + /** + * Returns the bit length of a string in a packed format + * + * @see self::decrypt() + * @see self::encrypt() + * @see self::setupGCM() + * @param string $str + * @return string + */ + private static function len64($str) + { + return "\0\0\0\0" . pack('N', 8 * strlen($str)); + } + + /** + * NULL pads a string to be a multiple of 128 + * + * @see self::decrypt() + * @see self::encrypt() + * @see self::setupGCM() + * @param string $str + * @return string + */ + protected static function nullPad128($str) + { + $len = strlen($str); + return $str . str_repeat("\0", 16 * ceil($len / 16) - $len); + } + + /** + * Calculates Poly1305 MAC + * + * On my system ChaCha20, with libsodium, takes 0.5s. With this custom Poly1305 implementation + * it takes 1.2s. + * + * @see self::decrypt() + * @see self::encrypt() + * @param string $text + * @return string + */ + protected function poly1305($text) + { + $s = $this->poly1305Key; // strlen($this->poly1305Key) == 32 + $r = Strings::shift($s, 16); + $r = strrev($r); + $r &= "\x0f\xff\xff\xfc\x0f\xff\xff\xfc\x0f\xff\xff\xfc\x0f\xff\xff\xff"; + $s = strrev($s); + + $r = self::$poly1305Field->newInteger(new BigInteger($r, 256)); + $s = self::$poly1305Field->newInteger(new BigInteger($s, 256)); + $a = self::$poly1305Field->newInteger(new BigInteger()); + + $blocks = str_split($text, 16); + foreach ($blocks as $block) { + $n = strrev($block . chr(1)); + $n = self::$poly1305Field->newInteger(new BigInteger($n, 256)); + $a = $a->add($n); + $a = $a->multiply($r); + } + $r = $a->toBigInteger()->add($s->toBigInteger()); + $mask = "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"; + return strrev($r->toBytes()) & $mask; + } + + /** + * Return the mode + * + * You can do $obj instanceof AES or whatever to get the cipher but you can't do that to get the mode + * + * @return string + */ + public function getMode() + { + return array_flip(self::MODE_MAP)[$this->mode]; + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/Traits/Fingerprint.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/Traits/Fingerprint.php new file mode 100644 index 00000000..9ca8926d --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/Traits/Fingerprint.php @@ -0,0 +1,57 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\Crypt\Common\Traits; + +use phpseclib3\Crypt\Hash; + +/** + * Fingerprint Trait for Private Keys + * + * @author Jim Wigginton + */ +trait Fingerprint +{ + /** + * Returns the public key's fingerprint + * + * The public key's fingerprint is returned, which is equivalent to running `ssh-keygen -lf rsa.pub`. If there is + * no public key currently loaded, false is returned. + * Example output (md5): "c1:b1:30:29:d7:b8:de:6c:97:77:10:d7:46:41:63:87" (as specified by RFC 4716) + * + * @param string $algorithm The hashing algorithm to be used. Valid options are 'md5' and 'sha256'. False is returned + * for invalid values. + * @return mixed + */ + public function getFingerprint($algorithm = 'md5') + { + $type = self::validatePlugin('Keys', 'OpenSSH', 'savePublicKey'); + if ($type === false) { + return false; + } + $key = $this->toString('OpenSSH', ['binary' => true]); + if ($key === false) { + return false; + } + switch ($algorithm) { + case 'sha256': + $hash = new Hash('sha256'); + $base = base64_encode($hash->hash($key)); + return substr($base, 0, strlen($base) - 1); + case 'md5': + return substr(chunk_split(md5($key), 2, ':'), 0, -1); + default: + return false; + } + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/Traits/PasswordProtected.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/Traits/PasswordProtected.php new file mode 100644 index 00000000..0ac274e8 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/Traits/PasswordProtected.php @@ -0,0 +1,46 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\Crypt\Common\Traits; + +/** + * Password Protected Trait for Private Keys + * + * @author Jim Wigginton + */ +trait PasswordProtected +{ + /** + * Password + * + * @var string|bool + */ + private $password = false; + + /** + * Sets the password + * + * Private keys can be encrypted with a password. To unset the password, pass in the empty string or false. + * Or rather, pass in $password such that empty($password) && !is_string($password) is true. + * + * @see self::createKey() + * @see self::load() + * @param string|bool $password + */ + public function withPassword($password = false) + { + $new = clone $this; + $new->password = $password; + return $new; + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/DES.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/DES.php index 9a8225fb..48977c96 100644 --- a/vendor/phpseclib/phpseclib/phpseclib/Crypt/DES.php +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/DES.php @@ -18,7 +18,7 @@ * setKey('abcdefgh'); * @@ -32,120 +32,119 @@ * ?> * * - * @category Crypt - * @package DES * @author Jim Wigginton * @copyright 2007 Jim Wigginton * @license http://www.opensource.org/licenses/mit-license.html MIT License * @link http://phpseclib.sourceforge.net */ -namespace phpseclib\Crypt; +namespace phpseclib3\Crypt; + +use phpseclib3\Crypt\Common\BlockCipher; +use phpseclib3\Exception\BadModeException; /** * Pure-PHP implementation of DES. * - * @package DES * @author Jim Wigginton - * @access public */ -class DES extends Base +class DES extends BlockCipher { - /**#@+ - * @access private - * @see \phpseclib\Crypt\DES::_setupKey() - * @see \phpseclib\Crypt\DES::_processBlock() - */ /** * Contains $keys[self::ENCRYPT] + * + * @see \phpseclib3\Crypt\DES::setupKey() + * @see \phpseclib3\Crypt\DES::processBlock() */ const ENCRYPT = 0; /** * Contains $keys[self::DECRYPT] + * + * @see \phpseclib3\Crypt\DES::setupKey() + * @see \phpseclib3\Crypt\DES::processBlock() */ const DECRYPT = 1; - /**#@-*/ /** * Block Length of the cipher * - * @see \phpseclib\Crypt\Base::block_size + * @see \phpseclib3\Crypt\Common\SymmetricKey::block_size * @var int - * @access private */ - var $block_size = 8; + protected $block_size = 8; /** * Key Length (in bytes) * - * @see \phpseclib\Crypt\Base::setKeyLength() + * @see \phpseclib3\Crypt\Common\SymmetricKey::setKeyLength() * @var int - * @access private */ - var $key_length = 8; + protected $key_length = 8; /** * The mcrypt specific name of the cipher * - * @see \phpseclib\Crypt\Base::cipher_name_mcrypt + * @see \phpseclib3\Crypt\Common\SymmetricKey::cipher_name_mcrypt * @var string - * @access private */ - var $cipher_name_mcrypt = 'des'; + protected $cipher_name_mcrypt = 'des'; /** * The OpenSSL names of the cipher / modes * - * @see \phpseclib\Crypt\Base::openssl_mode_names + * @see \phpseclib3\Crypt\Common\SymmetricKey::openssl_mode_names * @var array - * @access private */ - var $openssl_mode_names = array( + protected $openssl_mode_names = [ self::MODE_ECB => 'des-ecb', self::MODE_CBC => 'des-cbc', self::MODE_CFB => 'des-cfb', self::MODE_OFB => 'des-ofb' // self::MODE_CTR is undefined for DES - ); + ]; /** * Optimizing value while CFB-encrypting * - * @see \phpseclib\Crypt\Base::cfb_init_len + * @see \phpseclib3\Crypt\Common\SymmetricKey::cfb_init_len * @var int - * @access private */ - var $cfb_init_len = 500; + protected $cfb_init_len = 500; /** * Switch for DES/3DES encryption * * Used only if $engine == self::ENGINE_INTERNAL * - * @see self::_setupKey() - * @see self::_processBlock() + * @see self::setupKey() + * @see self::processBlock() * @var int - * @access private */ - var $des_rounds = 1; + protected $des_rounds = 1; /** * max possible size of $key * * @see self::setKey() * @var string - * @access private */ - var $key_length_max = 8; + protected $key_length_max = 8; /** * The Key Schedule * - * @see self::_setupKey() + * @see self::setupKey() * @var array - * @access private */ - var $keys; + private $keys; + + /** + * Key Cache "key" + * + * @see self::setupKey() + * @var array + */ + private $kl; /** * Shuffle table. @@ -154,12 +153,11 @@ class DES extends Base * with each byte containing all bits in the same state as the * corresponding bit in the index value. * - * @see self::_processBlock() - * @see self::_setupKey() + * @see self::processBlock() + * @see self::setupKey() * @var array - * @access private */ - var $shuffle = array( + protected static $shuffle = [ "\x00\x00\x00\x00\x00\x00\x00\x00", "\x00\x00\x00\x00\x00\x00\x00\xFF", "\x00\x00\x00\x00\x00\x00\xFF\x00", "\x00\x00\x00\x00\x00\x00\xFF\xFF", "\x00\x00\x00\x00\x00\xFF\x00\x00", "\x00\x00\x00\x00\x00\xFF\x00\xFF", @@ -288,7 +286,7 @@ class DES extends Base "\xFF\xFF\xFF\xFF\xFF\x00\xFF\x00", "\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF", "\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00", "\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF", "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00", "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF" - ); + ]; /** * IP mapping helper table. @@ -296,9 +294,8 @@ class DES extends Base * Indexing this table with each source byte performs the initial bit permutation. * * @var array - * @access private */ - var $ipmap = array( + protected static $ipmap = [ 0x00, 0x10, 0x01, 0x11, 0x20, 0x30, 0x21, 0x31, 0x02, 0x12, 0x03, 0x13, 0x22, 0x32, 0x23, 0x33, 0x40, 0x50, 0x41, 0x51, 0x60, 0x70, 0x61, 0x71, @@ -331,16 +328,15 @@ class DES extends Base 0x8E, 0x9E, 0x8F, 0x9F, 0xAE, 0xBE, 0xAF, 0xBF, 0xCC, 0xDC, 0xCD, 0xDD, 0xEC, 0xFC, 0xED, 0xFD, 0xCE, 0xDE, 0xCF, 0xDF, 0xEE, 0xFE, 0xEF, 0xFF - ); + ]; /** * Inverse IP mapping helper table. * Indexing this table with a byte value reverses the bit order. * * @var array - * @access private */ - var $invipmap = array( + protected static $invipmap = [ 0x00, 0x80, 0x40, 0xC0, 0x20, 0xA0, 0x60, 0xE0, 0x10, 0x90, 0x50, 0xD0, 0x30, 0xB0, 0x70, 0xF0, 0x08, 0x88, 0x48, 0xC8, 0x28, 0xA8, 0x68, 0xE8, @@ -373,7 +369,7 @@ class DES extends Base 0x17, 0x97, 0x57, 0xD7, 0x37, 0xB7, 0x77, 0xF7, 0x0F, 0x8F, 0x4F, 0xCF, 0x2F, 0xAF, 0x6F, 0xEF, 0x1F, 0x9F, 0x5F, 0xDF, 0x3F, 0xBF, 0x7F, 0xFF - ); + ]; /** * Pre-permuted S-box1 @@ -382,9 +378,8 @@ class DES extends Base * P table: concatenation can then be replaced by exclusive ORs. * * @var array - * @access private */ - var $sbox1 = array( + protected static $sbox1 = [ 0x00808200, 0x00000000, 0x00008000, 0x00808202, 0x00808002, 0x00008202, 0x00000002, 0x00008000, 0x00000200, 0x00808200, 0x00808202, 0x00000200, @@ -401,15 +396,14 @@ class DES extends Base 0x00800002, 0x00000202, 0x00008202, 0x00808200, 0x00000202, 0x00800200, 0x00800200, 0x00000000, 0x00008002, 0x00008200, 0x00000000, 0x00808002 - ); + ]; /** * Pre-permuted S-box2 * * @var array - * @access private */ - var $sbox2 = array( + protected static $sbox2 = [ 0x40084010, 0x40004000, 0x00004000, 0x00084010, 0x00080000, 0x00000010, 0x40080010, 0x40004010, 0x40000010, 0x40084010, 0x40084000, 0x40000000, @@ -426,15 +420,14 @@ class DES extends Base 0x00080010, 0x40004010, 0x40000010, 0x00080010, 0x00084000, 0x00000000, 0x40004000, 0x00004010, 0x40000000, 0x40080010, 0x40084010, 0x00084000 - ); + ]; /** * Pre-permuted S-box3 * * @var array - * @access private */ - var $sbox3 = array( + protected static $sbox3 = [ 0x00000104, 0x04010100, 0x00000000, 0x04010004, 0x04000100, 0x00000000, 0x00010104, 0x04000100, 0x00010004, 0x04000004, 0x04000004, 0x00010000, @@ -451,15 +444,14 @@ class DES extends Base 0x00000004, 0x00010104, 0x00010100, 0x04000004, 0x04010000, 0x04000104, 0x00000104, 0x04010000, 0x00010104, 0x00000004, 0x04010004, 0x00010100 - ); + ]; /** * Pre-permuted S-box4 * * @var array - * @access private */ - var $sbox4 = array( + protected static $sbox4 = [ 0x80401000, 0x80001040, 0x80001040, 0x00000040, 0x00401040, 0x80400040, 0x80400000, 0x80001000, 0x00000000, 0x00401000, 0x00401000, 0x80401040, @@ -476,15 +468,14 @@ class DES extends Base 0x80400000, 0x80001000, 0x00401040, 0x80400040, 0x80001000, 0x00001040, 0x00400000, 0x80401000, 0x00000040, 0x00400000, 0x00001000, 0x00401040 - ); + ]; /** * Pre-permuted S-box5 * * @var array - * @access private */ - var $sbox5 = array( + protected static $sbox5 = [ 0x00000080, 0x01040080, 0x01040000, 0x21000080, 0x00040000, 0x00000080, 0x20000000, 0x01040000, 0x20040080, 0x00040000, 0x01000080, 0x20040080, @@ -501,15 +492,14 @@ class DES extends Base 0x01040000, 0x00000000, 0x20040000, 0x21000000, 0x00040080, 0x01000080, 0x20000080, 0x00040000, 0x00000000, 0x20040000, 0x01040080, 0x20000080 - ); + ]; /** * Pre-permuted S-box6 * * @var array - * @access private */ - var $sbox6 = array( + protected static $sbox6 = [ 0x10000008, 0x10200000, 0x00002000, 0x10202008, 0x10200000, 0x00000008, 0x10202008, 0x00200000, 0x10002000, 0x00202008, 0x00200000, 0x10000008, @@ -526,15 +516,14 @@ class DES extends Base 0x00000008, 0x00002000, 0x10200000, 0x00202008, 0x00002000, 0x00200008, 0x10002008, 0x00000000, 0x10202000, 0x10000000, 0x00200008, 0x10002008 - ); + ]; /** * Pre-permuted S-box7 * * @var array - * @access private */ - var $sbox7 = array( + protected static $sbox7 = [ 0x00100000, 0x02100001, 0x02000401, 0x00000000, 0x00000400, 0x02000401, 0x00100401, 0x02100400, 0x02100401, 0x00100000, 0x00000000, 0x02000001, @@ -551,15 +540,14 @@ class DES extends Base 0x00100400, 0x00000000, 0x00000001, 0x02100401, 0x00000000, 0x00100401, 0x02100000, 0x00000400, 0x02000001, 0x02000400, 0x00000400, 0x00100001 - ); + ]; /** * Pre-permuted S-box8 * * @var array - * @access private */ - var $sbox8 = array( + protected static $sbox8 = [ 0x08000820, 0x00000800, 0x00020000, 0x08020820, 0x08000000, 0x08000820, 0x00000020, 0x08000000, 0x00020020, 0x08020000, 0x08020820, 0x00020800, @@ -576,51 +564,58 @@ class DES extends Base 0x08020000, 0x08000800, 0x08000820, 0x00000000, 0x08020820, 0x00020800, 0x00020800, 0x00000820, 0x00000820, 0x00020020, 0x08000000, 0x08020800 - ); + ]; + + /** + * Default Constructor. + * + * @param string $mode + * @throws BadModeException if an invalid / unsupported mode is provided + */ + public function __construct($mode) + { + parent::__construct($mode); + + if ($this->mode == self::MODE_STREAM) { + throw new BadModeException('Block ciphers cannot be ran in stream mode'); + } + } /** * Test for engine validity * - * This is mainly just a wrapper to set things up for \phpseclib\Crypt\Base::isValidEngine() + * This is mainly just a wrapper to set things up for \phpseclib3\Crypt\Common\SymmetricKey::isValidEngine() * - * @see \phpseclib\Crypt\Base::isValidEngine() + * @see \phpseclib3\Crypt\Common\SymmetricKey::isValidEngine() * @param int $engine - * @access public * @return bool */ - function isValidEngine($engine) + protected function isValidEngineHelper($engine) { if ($this->key_length_max == 8) { if ($engine == self::ENGINE_OPENSSL) { $this->cipher_name_openssl_ecb = 'des-ecb'; - $this->cipher_name_openssl = 'des-' . $this->_openssl_translate_mode(); + $this->cipher_name_openssl = 'des-' . $this->openssl_translate_mode(); } } - return parent::isValidEngine($engine); + return parent::isValidEngineHelper($engine); } /** * Sets the key. * - * Keys can be of any length. DES, itself, uses 64-bit keys (eg. strlen($key) == 8), however, we - * only use the first eight, if $key has more then eight characters in it, and pad $key with the - * null byte if it is less then eight characters long. + * Keys must be 64-bits long or 8 bytes long. * * DES also requires that every eighth bit be a parity bit, however, we'll ignore that. * - * If the key is not explicitly set, it'll be assumed to be all zero's. - * - * @see \phpseclib\Crypt\Base::setKey() - * @access public + * @see \phpseclib3\Crypt\Common\SymmetricKey::setKey() * @param string $key */ - function setKey($key) + public function setKey($key) { - // We check/cut here only up to max length of the key. - // Key padding to the proper length will be done in _setupKey() - if (strlen($key) > $this->key_length_max) { - $key = substr($key, 0, $this->key_length_max); + if (!($this instanceof TripleDES) && strlen($key) != 8) { + throw new \LengthException('Key of size ' . strlen($key) . ' not supported by this algorithm. Only keys of size 8 are supported'); } // Sets the key @@ -630,31 +625,29 @@ function setKey($key) /** * Encrypts a block * - * @see \phpseclib\Crypt\Base::_encryptBlock() - * @see \phpseclib\Crypt\Base::encrypt() + * @see \phpseclib3\Crypt\Common\SymmetricKey::encryptBlock() + * @see \phpseclib3\Crypt\Common\SymmetricKey::encrypt() * @see self::encrypt() - * @access private * @param string $in * @return string */ - function _encryptBlock($in) + protected function encryptBlock($in) { - return $this->_processBlock($in, self::ENCRYPT); + return $this->processBlock($in, self::ENCRYPT); } /** * Decrypts a block * - * @see \phpseclib\Crypt\Base::_decryptBlock() - * @see \phpseclib\Crypt\Base::decrypt() + * @see \phpseclib3\Crypt\Common\SymmetricKey::decryptBlock() + * @see \phpseclib3\Crypt\Common\SymmetricKey::decrypt() * @see self::decrypt() - * @access private * @param string $in * @return string */ - function _decryptBlock($in) + protected function decryptBlock($in) { - return $this->_processBlock($in, self::DECRYPT); + return $this->processBlock($in, self::DECRYPT); } /** @@ -664,29 +657,28 @@ function _decryptBlock($in) * {@link http://en.wikipedia.org/wiki/Image:Feistel.png Feistel.png} to get a general * idea of what this function does. * - * @see self::_encryptBlock() - * @see self::_decryptBlock() - * @access private + * @see self::encryptBlock() + * @see self::decryptBlock() * @param string $block * @param int $mode * @return string */ - function _processBlock($block, $mode) + private function processBlock($block, $mode) { static $sbox1, $sbox2, $sbox3, $sbox4, $sbox5, $sbox6, $sbox7, $sbox8, $shuffleip, $shuffleinvip; if (!$sbox1) { - $sbox1 = array_map("intval", $this->sbox1); - $sbox2 = array_map("intval", $this->sbox2); - $sbox3 = array_map("intval", $this->sbox3); - $sbox4 = array_map("intval", $this->sbox4); - $sbox5 = array_map("intval", $this->sbox5); - $sbox6 = array_map("intval", $this->sbox6); - $sbox7 = array_map("intval", $this->sbox7); - $sbox8 = array_map("intval", $this->sbox8); + $sbox1 = array_map('intval', self::$sbox1); + $sbox2 = array_map('intval', self::$sbox2); + $sbox3 = array_map('intval', self::$sbox3); + $sbox4 = array_map('intval', self::$sbox4); + $sbox5 = array_map('intval', self::$sbox5); + $sbox6 = array_map('intval', self::$sbox6); + $sbox7 = array_map('intval', self::$sbox7); + $sbox8 = array_map('intval', self::$sbox8); /* Merge $shuffle with $[inv]ipmap */ for ($i = 0; $i < 256; ++$i) { - $shuffleip[] = $this->shuffle[$this->ipmap[$i]]; - $shuffleinvip[] = $this->shuffle[$this->invipmap[$i]]; + $shuffleip[] = self::$shuffle[self::$ipmap[$i]]; + $shuffleinvip[] = self::$shuffle[self::$invipmap[$i]]; } } @@ -695,7 +687,7 @@ function _processBlock($block, $mode) // Do the initial IP permutation. $t = unpack('Nl/Nr', $block); - list($l, $r) = array($t['l'], $t['r']); + list($l, $r) = [$t['l'], $t['r']]; $block = ($shuffleip[ $r & 0xFF] & "\x80\x80\x80\x80\x80\x80\x80\x80") | ($shuffleip[($r >> 8) & 0xFF] & "\x40\x40\x40\x40\x40\x40\x40\x40") | ($shuffleip[($r >> 16) & 0xFF] & "\x20\x20\x20\x20\x20\x20\x20\x20") | @@ -707,7 +699,7 @@ function _processBlock($block, $mode) // Extract L0 and R0. $t = unpack('Nl/Nr', $block); - list($l, $r) = array($t['l'], $t['r']); + list($l, $r) = [$t['l'], $t['r']]; for ($des_round = 0; $des_round < $this->des_rounds; ++$des_round) { // Perform the 16 steps. @@ -749,22 +741,21 @@ function _processBlock($block, $mode) /** * Creates the key schedule * - * @see \phpseclib\Crypt\Base::_setupKey() - * @access private + * @see \phpseclib3\Crypt\Common\SymmetricKey::setupKey() */ - function _setupKey() + protected function setupKey() { if (isset($this->kl['key']) && $this->key === $this->kl['key'] && $this->des_rounds === $this->kl['des_rounds']) { // already expanded return; } - $this->kl = array('key' => $this->key, 'des_rounds' => $this->des_rounds); + $this->kl = ['key' => $this->key, 'des_rounds' => $this->des_rounds]; - static $shifts = array( // number of key bits shifted per round + static $shifts = [ // number of key bits shifted per round 1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1 - ); + ]; - static $pc1map = array( + static $pc1map = [ 0x00, 0x00, 0x08, 0x08, 0x04, 0x04, 0x0C, 0x0C, 0x02, 0x02, 0x0A, 0x0A, 0x06, 0x06, 0x0E, 0x0E, 0x10, 0x10, 0x18, 0x18, 0x14, 0x14, 0x1C, 0x1C, @@ -797,16 +788,16 @@ function _setupKey() 0xE2, 0xE2, 0xEA, 0xEA, 0xE6, 0xE6, 0xEE, 0xEE, 0xF0, 0xF0, 0xF8, 0xF8, 0xF4, 0xF4, 0xFC, 0xFC, 0xF2, 0xF2, 0xFA, 0xFA, 0xF6, 0xF6, 0xFE, 0xFE - ); + ]; // Mapping tables for the PC-2 transformation. - static $pc2mapc1 = array( + static $pc2mapc1 = [ 0x00000000, 0x00000400, 0x00200000, 0x00200400, 0x00000001, 0x00000401, 0x00200001, 0x00200401, 0x02000000, 0x02000400, 0x02200000, 0x02200400, 0x02000001, 0x02000401, 0x02200001, 0x02200401 - ); - static $pc2mapc2 = array( + ]; + static $pc2mapc2 = [ 0x00000000, 0x00000800, 0x08000000, 0x08000800, 0x00010000, 0x00010800, 0x08010000, 0x08010800, 0x00000000, 0x00000800, 0x08000000, 0x08000800, @@ -871,8 +862,8 @@ function _setupKey() 0x01050110, 0x01050910, 0x09050110, 0x09050910, 0x01040110, 0x01040910, 0x09040110, 0x09040910, 0x01050110, 0x01050910, 0x09050110, 0x09050910 - ); - static $pc2mapc3 = array( + ]; + static $pc2mapc3 = [ 0x00000000, 0x00000004, 0x00001000, 0x00001004, 0x00000000, 0x00000004, 0x00001000, 0x00001004, 0x10000000, 0x10000004, 0x10001000, 0x10001004, @@ -937,8 +928,8 @@ function _setupKey() 0x20080022, 0x20080026, 0x20081022, 0x20081026, 0x30080022, 0x30080026, 0x30081022, 0x30081026, 0x30080022, 0x30080026, 0x30081022, 0x30081026 - ); - static $pc2mapc4 = array( + ]; + static $pc2mapc4 = [ 0x00000000, 0x00100000, 0x00000008, 0x00100008, 0x00000200, 0x00100200, 0x00000208, 0x00100208, 0x00000000, 0x00100000, 0x00000008, 0x00100008, @@ -1003,14 +994,14 @@ function _setupKey() 0x04022200, 0x04122200, 0x04022208, 0x04122208, 0x04022000, 0x04122000, 0x04022008, 0x04122008, 0x04022200, 0x04122200, 0x04022208, 0x04122208 - ); - static $pc2mapd1 = array( + ]; + static $pc2mapd1 = [ 0x00000000, 0x00000001, 0x08000000, 0x08000001, 0x00200000, 0x00200001, 0x08200000, 0x08200001, 0x00000002, 0x00000003, 0x08000002, 0x08000003, 0x00200002, 0x00200003, 0x08200002, 0x08200003 - ); - static $pc2mapd2 = array( + ]; + static $pc2mapd2 = [ 0x00000000, 0x00100000, 0x00000800, 0x00100800, 0x00000000, 0x00100000, 0x00000800, 0x00100800, 0x04000000, 0x04100000, 0x04000800, 0x04100800, @@ -1075,8 +1066,8 @@ function _setupKey() 0x00020204, 0x00120204, 0x00020A04, 0x00120A04, 0x04020204, 0x04120204, 0x04020A04, 0x04120A04, 0x04020204, 0x04120204, 0x04020A04, 0x04120A04 - ); - static $pc2mapd3 = array( + ]; + static $pc2mapd3 = [ 0x00000000, 0x00010000, 0x02000000, 0x02010000, 0x00000020, 0x00010020, 0x02000020, 0x02010020, 0x00040000, 0x00050000, 0x02040000, 0x02050000, @@ -1141,8 +1132,8 @@ function _setupKey() 0x20002030, 0x20012030, 0x22002030, 0x22012030, 0x20042010, 0x20052010, 0x22042010, 0x22052010, 0x20042030, 0x20052030, 0x22042030, 0x22052030 - ); - static $pc2mapd4 = array( + ]; + static $pc2mapd4 = [ 0x00000000, 0x00000400, 0x01000000, 0x01000400, 0x00000000, 0x00000400, 0x01000000, 0x01000400, 0x00000100, 0x00000500, 0x01000100, 0x01000500, @@ -1207,33 +1198,33 @@ function _setupKey() 0x10081008, 0x10081408, 0x11081008, 0x11081408, 0x10081108, 0x10081508, 0x11081108, 0x11081508, 0x10081108, 0x10081508, 0x11081108, 0x11081508 - ); + ]; - $keys = array(); + $keys = []; for ($des_round = 0; $des_round < $this->des_rounds; ++$des_round) { // pad the key and remove extra characters as appropriate. $key = str_pad(substr($this->key, $des_round * 8, 8), 8, "\0"); // Perform the PC/1 transformation and compute C and D. $t = unpack('Nl/Nr', $key); - list($l, $r) = array($t['l'], $t['r']); - $key = ($this->shuffle[$pc1map[ $r & 0xFF]] & "\x80\x80\x80\x80\x80\x80\x80\x00") | - ($this->shuffle[$pc1map[($r >> 8) & 0xFF]] & "\x40\x40\x40\x40\x40\x40\x40\x00") | - ($this->shuffle[$pc1map[($r >> 16) & 0xFF]] & "\x20\x20\x20\x20\x20\x20\x20\x00") | - ($this->shuffle[$pc1map[($r >> 24) & 0xFF]] & "\x10\x10\x10\x10\x10\x10\x10\x00") | - ($this->shuffle[$pc1map[ $l & 0xFF]] & "\x08\x08\x08\x08\x08\x08\x08\x00") | - ($this->shuffle[$pc1map[($l >> 8) & 0xFF]] & "\x04\x04\x04\x04\x04\x04\x04\x00") | - ($this->shuffle[$pc1map[($l >> 16) & 0xFF]] & "\x02\x02\x02\x02\x02\x02\x02\x00") | - ($this->shuffle[$pc1map[($l >> 24) & 0xFF]] & "\x01\x01\x01\x01\x01\x01\x01\x00"); + list($l, $r) = [$t['l'], $t['r']]; + $key = (self::$shuffle[$pc1map[ $r & 0xFF]] & "\x80\x80\x80\x80\x80\x80\x80\x00") | + (self::$shuffle[$pc1map[($r >> 8) & 0xFF]] & "\x40\x40\x40\x40\x40\x40\x40\x00") | + (self::$shuffle[$pc1map[($r >> 16) & 0xFF]] & "\x20\x20\x20\x20\x20\x20\x20\x00") | + (self::$shuffle[$pc1map[($r >> 24) & 0xFF]] & "\x10\x10\x10\x10\x10\x10\x10\x00") | + (self::$shuffle[$pc1map[ $l & 0xFF]] & "\x08\x08\x08\x08\x08\x08\x08\x00") | + (self::$shuffle[$pc1map[($l >> 8) & 0xFF]] & "\x04\x04\x04\x04\x04\x04\x04\x00") | + (self::$shuffle[$pc1map[($l >> 16) & 0xFF]] & "\x02\x02\x02\x02\x02\x02\x02\x00") | + (self::$shuffle[$pc1map[($l >> 24) & 0xFF]] & "\x01\x01\x01\x01\x01\x01\x01\x00"); $key = unpack('Nc/Nd', $key); $c = ( $key['c'] >> 4) & 0x0FFFFFFF; $d = (($key['d'] >> 4) & 0x0FFFFFF0) | ($key['c'] & 0x0F); - $keys[$des_round] = array( - self::ENCRYPT => array(), + $keys[$des_round] = [ + self::ENCRYPT => [], self::DECRYPT => array_fill(0, 32, 0) - ); - for ($i = 0, $ki = 31; $i < 16; ++$i, $ki-= 2) { + ]; + for ($i = 0, $ki = 31; $i < 16; ++$i, $ki -= 2) { $c <<= $shifts[$i]; $c = ($c | ($c >> 28)) & 0x0FFFFFFF; $d <<= $shifts[$i]; @@ -1246,9 +1237,9 @@ function _setupKey() $pc2mapd3[($d >> 8) & 0xFF] | $pc2mapd4[ $d & 0xFF]; // Reorder: odd bytes/even bytes. Push the result in key schedule. - $val1 = ( $cp & 0xFF000000) | (($cp << 8) & 0x00FF0000) | + $val1 = ( $cp & intval(0xFF000000)) | (($cp << 8) & 0x00FF0000) | (($dp >> 16) & 0x0000FF00) | (($dp >> 8) & 0x000000FF); - $val2 = (($cp << 8) & 0xFF000000) | (($cp << 16) & 0x00FF0000) | + $val2 = (($cp << 8) & intval(0xFF000000)) | (($cp << 16) & 0x00FF0000) | (($dp >> 8) & 0x0000FF00) | ( $dp & 0x000000FF); $keys[$des_round][self::ENCRYPT][ ] = $val1; $keys[$des_round][self::DECRYPT][$ki - 1] = $val1; @@ -1259,7 +1250,7 @@ function _setupKey() switch ($this->des_rounds) { case 3: // 3DES keys - $this->keys = array( + $this->keys = [ self::ENCRYPT => array_merge( $keys[0][self::ENCRYPT], $keys[1][self::DECRYPT], @@ -1270,174 +1261,126 @@ function _setupKey() $keys[1][self::ENCRYPT], $keys[0][self::DECRYPT] ) - ); + ]; break; // case 1: // DES keys default: - $this->keys = array( + $this->keys = [ self::ENCRYPT => $keys[0][self::ENCRYPT], self::DECRYPT => $keys[0][self::DECRYPT] - ); + ]; } } /** * Setup the performance-optimized function for de/encrypt() * - * @see \phpseclib\Crypt\Base::_setupInlineCrypt() - * @access private + * @see \phpseclib3\Crypt\Common\SymmetricKey::setupInlineCrypt() */ - function _setupInlineCrypt() + protected function setupInlineCrypt() { - $lambda_functions =& self::_getLambdaFunctions(); - // Engine configuration for: // - DES ($des_rounds == 1) or // - 3DES ($des_rounds == 3) $des_rounds = $this->des_rounds; - // We create max. 10 hi-optimized code for memory reason. Means: For each $key one ultra fast inline-crypt function. - // (Currently, for DES, one generated $lambda_function cost on php5.5@32bit ~135kb unfreeable mem and ~230kb on php5.5@64bit) - // (Currently, for TripleDES, one generated $lambda_function cost on php5.5@32bit ~240kb unfreeable mem and ~340kb on php5.5@64bit) - // After that, we'll still create very fast optimized code but not the hi-ultimative code, for each $mode one - $gen_hi_opt_code = (bool)( count($lambda_functions) < 10 ); - - // Generation of a unique hash for our generated code - $code_hash = "Crypt_DES, $des_rounds, {$this->mode}"; - if ($gen_hi_opt_code) { - // For hi-optimized code, we create for each combination of - // $mode, $des_rounds and $this->key its own encrypt/decrypt function. - // After max 10 hi-optimized functions, we create generic - // (still very fast.. but not ultra) functions for each $mode/$des_rounds - // Currently 2 * 5 generic functions will be then max. possible. - $code_hash = str_pad($code_hash, 32) . $this->_hashInlineCryptFunction($this->key); - } - - // Is there a re-usable $lambda_functions in there? If not, we have to create it. - if (!isset($lambda_functions[$code_hash])) { - // Init code for both, encrypt and decrypt. - $init_crypt = 'static $sbox1, $sbox2, $sbox3, $sbox4, $sbox5, $sbox6, $sbox7, $sbox8, $shuffleip, $shuffleinvip; - if (!$sbox1) { - $sbox1 = array_map("intval", $self->sbox1); - $sbox2 = array_map("intval", $self->sbox2); - $sbox3 = array_map("intval", $self->sbox3); - $sbox4 = array_map("intval", $self->sbox4); - $sbox5 = array_map("intval", $self->sbox5); - $sbox6 = array_map("intval", $self->sbox6); - $sbox7 = array_map("intval", $self->sbox7); - $sbox8 = array_map("intval", $self->sbox8);' - /* Merge $shuffle with $[inv]ipmap */ . ' - for ($i = 0; $i < 256; ++$i) { - $shuffleip[] = $self->shuffle[$self->ipmap[$i]]; - $shuffleinvip[] = $self->shuffle[$self->invipmap[$i]]; - } + $init_crypt = 'static $sbox1, $sbox2, $sbox3, $sbox4, $sbox5, $sbox6, $sbox7, $sbox8, $shuffleip, $shuffleinvip; + if (!$sbox1) { + $sbox1 = array_map("intval", self::$sbox1); + $sbox2 = array_map("intval", self::$sbox2); + $sbox3 = array_map("intval", self::$sbox3); + $sbox4 = array_map("intval", self::$sbox4); + $sbox5 = array_map("intval", self::$sbox5); + $sbox6 = array_map("intval", self::$sbox6); + $sbox7 = array_map("intval", self::$sbox7); + $sbox8 = array_map("intval", self::$sbox8);' + /* Merge $shuffle with $[inv]ipmap */ . ' + for ($i = 0; $i < 256; ++$i) { + $shuffleip[] = self::$shuffle[self::$ipmap[$i]]; + $shuffleinvip[] = self::$shuffle[self::$invipmap[$i]]; } - '; - - switch (true) { - case $gen_hi_opt_code: - // In Hi-optimized code mode, we use our [3]DES key schedule as hardcoded integers. - // No futher initialisation of the $keys schedule is necessary. - // That is the extra performance boost. - $k = array( - self::ENCRYPT => $this->keys[self::ENCRYPT], - self::DECRYPT => $this->keys[self::DECRYPT] - ); - $init_encrypt = ''; - $init_decrypt = ''; - break; - default: - // In generic optimized code mode, we have to use, as the best compromise [currently], - // our key schedule as $ke/$kd arrays. (with hardcoded indexes...) - $k = array( - self::ENCRYPT => array(), - self::DECRYPT => array() - ); - for ($i = 0, $c = count($this->keys[self::ENCRYPT]); $i < $c; ++$i) { - $k[self::ENCRYPT][$i] = '$ke[' . $i . ']'; - $k[self::DECRYPT][$i] = '$kd[' . $i . ']'; - } - $init_encrypt = '$ke = $self->keys[$self::ENCRYPT];'; - $init_decrypt = '$kd = $self->keys[$self::DECRYPT];'; - break; } + '; + + $k = [ + self::ENCRYPT => $this->keys[self::ENCRYPT], + self::DECRYPT => $this->keys[self::DECRYPT] + ]; + $init_encrypt = ''; + $init_decrypt = ''; - // Creating code for en- and decryption. - $crypt_block = array(); - foreach (array(self::ENCRYPT, self::DECRYPT) as $c) { - /* Do the initial IP permutation. */ - $crypt_block[$c] = ' - $in = unpack("N*", $in); - $l = $in[1]; - $r = $in[2]; - $in = unpack("N*", - ($shuffleip[ $r & 0xFF] & "\x80\x80\x80\x80\x80\x80\x80\x80") | - ($shuffleip[($r >> 8) & 0xFF] & "\x40\x40\x40\x40\x40\x40\x40\x40") | - ($shuffleip[($r >> 16) & 0xFF] & "\x20\x20\x20\x20\x20\x20\x20\x20") | - ($shuffleip[($r >> 24) & 0xFF] & "\x10\x10\x10\x10\x10\x10\x10\x10") | - ($shuffleip[ $l & 0xFF] & "\x08\x08\x08\x08\x08\x08\x08\x08") | - ($shuffleip[($l >> 8) & 0xFF] & "\x04\x04\x04\x04\x04\x04\x04\x04") | - ($shuffleip[($l >> 16) & 0xFF] & "\x02\x02\x02\x02\x02\x02\x02\x02") | - ($shuffleip[($l >> 24) & 0xFF] & "\x01\x01\x01\x01\x01\x01\x01\x01") - ); - ' . /* Extract L0 and R0 */ ' - $l = $in[1]; - $r = $in[2]; - '; + // Creating code for en- and decryption. + $crypt_block = []; + foreach ([self::ENCRYPT, self::DECRYPT] as $c) { + /* Do the initial IP permutation. */ + $crypt_block[$c] = ' + $in = unpack("N*", $in); + $l = $in[1]; + $r = $in[2]; + $in = unpack("N*", + ($shuffleip[ $r & 0xFF] & "\x80\x80\x80\x80\x80\x80\x80\x80") | + ($shuffleip[($r >> 8) & 0xFF] & "\x40\x40\x40\x40\x40\x40\x40\x40") | + ($shuffleip[($r >> 16) & 0xFF] & "\x20\x20\x20\x20\x20\x20\x20\x20") | + ($shuffleip[($r >> 24) & 0xFF] & "\x10\x10\x10\x10\x10\x10\x10\x10") | + ($shuffleip[ $l & 0xFF] & "\x08\x08\x08\x08\x08\x08\x08\x08") | + ($shuffleip[($l >> 8) & 0xFF] & "\x04\x04\x04\x04\x04\x04\x04\x04") | + ($shuffleip[($l >> 16) & 0xFF] & "\x02\x02\x02\x02\x02\x02\x02\x02") | + ($shuffleip[($l >> 24) & 0xFF] & "\x01\x01\x01\x01\x01\x01\x01\x01") + ); + ' . /* Extract L0 and R0 */ ' + $l = $in[1]; + $r = $in[2]; + '; - $l = '$l'; - $r = '$r'; + $l = '$l'; + $r = '$r'; - // Perform DES or 3DES. - for ($ki = -1, $des_round = 0; $des_round < $des_rounds; ++$des_round) { - // Perform the 16 steps. - for ($i = 0; $i < 16; ++$i) { - // start of "the Feistel (F) function" - see the following URL: - // http://en.wikipedia.org/wiki/Image:Data_Encryption_Standard_InfoBox_Diagram.png - // Merge key schedule. - $crypt_block[$c].= ' - $b1 = ((' . $r . ' >> 3) & 0x1FFFFFFF) ^ (' . $r . ' << 29) ^ ' . $k[$c][++$ki] . '; - $b2 = ((' . $r . ' >> 31) & 0x00000001) ^ (' . $r . ' << 1) ^ ' . $k[$c][++$ki] . ';' . - /* S-box indexing. */ - $l . ' = $sbox1[($b1 >> 24) & 0x3F] ^ $sbox2[($b2 >> 24) & 0x3F] ^ - $sbox3[($b1 >> 16) & 0x3F] ^ $sbox4[($b2 >> 16) & 0x3F] ^ - $sbox5[($b1 >> 8) & 0x3F] ^ $sbox6[($b2 >> 8) & 0x3F] ^ - $sbox7[ $b1 & 0x3F] ^ $sbox8[ $b2 & 0x3F] ^ ' . $l . '; - '; - // end of "the Feistel (F) function" + // Perform DES or 3DES. + for ($ki = -1, $des_round = 0; $des_round < $des_rounds; ++$des_round) { + // Perform the 16 steps. + for ($i = 0; $i < 16; ++$i) { + // start of "the Feistel (F) function" - see the following URL: + // http://en.wikipedia.org/wiki/Image:Data_Encryption_Standard_InfoBox_Diagram.png + // Merge key schedule. + $crypt_block[$c] .= ' + $b1 = ((' . $r . ' >> 3) & 0x1FFFFFFF) ^ (' . $r . ' << 29) ^ ' . $k[$c][++$ki] . '; + $b2 = ((' . $r . ' >> 31) & 0x00000001) ^ (' . $r . ' << 1) ^ ' . $k[$c][++$ki] . ';' . + /* S-box indexing. */ + $l . ' = $sbox1[($b1 >> 24) & 0x3F] ^ $sbox2[($b2 >> 24) & 0x3F] ^ + $sbox3[($b1 >> 16) & 0x3F] ^ $sbox4[($b2 >> 16) & 0x3F] ^ + $sbox5[($b1 >> 8) & 0x3F] ^ $sbox6[($b2 >> 8) & 0x3F] ^ + $sbox7[ $b1 & 0x3F] ^ $sbox8[ $b2 & 0x3F] ^ ' . $l . '; + '; + // end of "the Feistel (F) function" - // swap L & R - list($l, $r) = array($r, $l); - } - list($l, $r) = array($r, $l); + // swap L & R + list($l, $r) = [$r, $l]; } - - // Perform the inverse IP permutation. - $crypt_block[$c].= '$in = - ($shuffleinvip[($l >> 24) & 0xFF] & "\x80\x80\x80\x80\x80\x80\x80\x80") | - ($shuffleinvip[($r >> 24) & 0xFF] & "\x40\x40\x40\x40\x40\x40\x40\x40") | - ($shuffleinvip[($l >> 16) & 0xFF] & "\x20\x20\x20\x20\x20\x20\x20\x20") | - ($shuffleinvip[($r >> 16) & 0xFF] & "\x10\x10\x10\x10\x10\x10\x10\x10") | - ($shuffleinvip[($l >> 8) & 0xFF] & "\x08\x08\x08\x08\x08\x08\x08\x08") | - ($shuffleinvip[($r >> 8) & 0xFF] & "\x04\x04\x04\x04\x04\x04\x04\x04") | - ($shuffleinvip[ $l & 0xFF] & "\x02\x02\x02\x02\x02\x02\x02\x02") | - ($shuffleinvip[ $r & 0xFF] & "\x01\x01\x01\x01\x01\x01\x01\x01"); - '; + list($l, $r) = [$r, $l]; } - // Creates the inline-crypt function - $lambda_functions[$code_hash] = $this->_createInlineCryptFunction( - array( - 'init_crypt' => $init_crypt, - 'init_encrypt' => $init_encrypt, - 'init_decrypt' => $init_decrypt, - 'encrypt_block' => $crypt_block[self::ENCRYPT], - 'decrypt_block' => $crypt_block[self::DECRYPT] - ) - ); + // Perform the inverse IP permutation. + $crypt_block[$c] .= '$in = + ($shuffleinvip[($l >> 24) & 0xFF] & "\x80\x80\x80\x80\x80\x80\x80\x80") | + ($shuffleinvip[($r >> 24) & 0xFF] & "\x40\x40\x40\x40\x40\x40\x40\x40") | + ($shuffleinvip[($l >> 16) & 0xFF] & "\x20\x20\x20\x20\x20\x20\x20\x20") | + ($shuffleinvip[($r >> 16) & 0xFF] & "\x10\x10\x10\x10\x10\x10\x10\x10") | + ($shuffleinvip[($l >> 8) & 0xFF] & "\x08\x08\x08\x08\x08\x08\x08\x08") | + ($shuffleinvip[($r >> 8) & 0xFF] & "\x04\x04\x04\x04\x04\x04\x04\x04") | + ($shuffleinvip[ $l & 0xFF] & "\x02\x02\x02\x02\x02\x02\x02\x02") | + ($shuffleinvip[ $r & 0xFF] & "\x01\x01\x01\x01\x01\x01\x01\x01"); + '; } - // Set the inline-crypt function as callback in: $this->inline_crypt - $this->inline_crypt = $lambda_functions[$code_hash]; + // Creates the inline-crypt function + $this->inline_crypt = $this->createInlineCryptFunction( + [ + 'init_crypt' => $init_crypt, + 'init_encrypt' => $init_encrypt, + 'init_decrypt' => $init_decrypt, + 'encrypt_block' => $crypt_block[self::ENCRYPT], + 'decrypt_block' => $crypt_block[self::DECRYPT] + ] + ); } } diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/DH.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/DH.php new file mode 100644 index 00000000..876cdbf8 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/DH.php @@ -0,0 +1,395 @@ + + * + * + * + * @author Jim Wigginton + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\Crypt; + +use phpseclib3\Crypt\Common\AsymmetricKey; +use phpseclib3\Crypt\DH\Parameters; +use phpseclib3\Crypt\DH\PrivateKey; +use phpseclib3\Crypt\DH\PublicKey; +use phpseclib3\Exception\NoKeyLoadedException; +use phpseclib3\Exception\UnsupportedOperationException; +use phpseclib3\Math\BigInteger; + +/** + * Pure-PHP (EC)DH implementation + * + * @author Jim Wigginton + */ +abstract class DH extends AsymmetricKey +{ + /** + * Algorithm Name + * + * @var string + */ + const ALGORITHM = 'DH'; + + /** + * DH prime + * + * @var \phpseclib3\Math\BigInteger + */ + protected $prime; + + /** + * DH Base + * + * Prime divisor of p-1 + * + * @var \phpseclib3\Math\BigInteger + */ + protected $base; + + /** + * Public Key + * + * @var \phpseclib3\Math\BigInteger + */ + protected $publicKey; + + /** + * Create DH parameters + * + * This method is a bit polymorphic. It can take any of the following: + * - two BigInteger's (prime and base) + * - an integer representing the size of the prime in bits (the base is assumed to be 2) + * - a string (eg. diffie-hellman-group14-sha1) + * + * @return Parameters + */ + public static function createParameters(...$args) + { + $params = new Parameters(); + if (count($args) == 2 && $args[0] instanceof BigInteger && $args[1] instanceof BigInteger) { + //if (!$args[0]->isPrime()) { + // throw new \InvalidArgumentException('The first parameter should be a prime number'); + //} + $params->prime = $args[0]; + $params->base = $args[1]; + return $params; + } elseif (count($args) == 1 && is_numeric($args[0])) { + $params->prime = BigInteger::randomPrime($args[0]); + $params->base = new BigInteger(2); + return $params; + } elseif (count($args) != 1 || !is_string($args[0])) { + throw new \InvalidArgumentException('Valid parameters are either: two BigInteger\'s (prime and base), a single integer (the length of the prime; base is assumed to be 2) or a string'); + } + switch ($args[0]) { + // see http://tools.ietf.org/html/rfc2409#section-6.2 and + // http://tools.ietf.org/html/rfc2412, appendex E + case 'diffie-hellman-group1-sha1': + $prime = 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' . + '020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' . + '4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' . + 'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF'; + break; + // see http://tools.ietf.org/html/rfc3526#section-3 + case 'diffie-hellman-group14-sha1': // 2048-bit MODP Group + case 'diffie-hellman-group14-sha256': + $prime = 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' . + '020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' . + '4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' . + 'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF05' . + '98DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB' . + '9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B' . + 'E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718' . + '3995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF'; + break; + // see https://tools.ietf.org/html/rfc3526#section-4 + case 'diffie-hellman-group15-sha512': // 3072-bit MODP Group + $prime = 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' . + '020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' . + '4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' . + 'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF05' . + '98DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB' . + '9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B' . + 'E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718' . + '3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33' . + 'A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7' . + 'ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864' . + 'D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E2' . + '08E24FA074E5AB3143DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF'; + break; + // see https://tools.ietf.org/html/rfc3526#section-5 + case 'diffie-hellman-group16-sha512': // 4096-bit MODP Group + $prime = 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' . + '020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' . + '4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' . + 'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF05' . + '98DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB' . + '9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B' . + 'E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718' . + '3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33' . + 'A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7' . + 'ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864' . + 'D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E2' . + '08E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D7' . + '88719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8' . + 'DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2' . + '233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA9' . + '93B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199FFFFFFFFFFFFFFFF'; + break; + // see https://tools.ietf.org/html/rfc3526#section-6 + case 'diffie-hellman-group17-sha512': // 6144-bit MODP Group + $prime = 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' . + '020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' . + '4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' . + 'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF05' . + '98DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB' . + '9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B' . + 'E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718' . + '3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33' . + 'A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7' . + 'ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864' . + 'D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E2' . + '08E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D7' . + '88719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8' . + 'DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2' . + '233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA9' . + '93B4EA988D8FDDC186FFB7DC90A6C08F4DF435C93402849236C3FAB4D27C7026' . + 'C1D4DCB2602646DEC9751E763DBA37BDF8FF9406AD9E530EE5DB382F413001AE' . + 'B06A53ED9027D831179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1B' . + 'DB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF5983CA01C64B92EC' . + 'F032EA15D1721D03F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E' . + '59E7C97FBEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA' . + 'CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58BB7C5DA76' . + 'F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632387FE8D76E3C0468' . + '043E8F663F4860EE12BF2D5B0B7474D6E694F91E6DCC4024FFFFFFFFFFFFFFFF'; + break; + // see https://tools.ietf.org/html/rfc3526#section-7 + case 'diffie-hellman-group18-sha512': // 8192-bit MODP Group + $prime = 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' . + '020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' . + '4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' . + 'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF05' . + '98DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB' . + '9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B' . + 'E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718' . + '3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33' . + 'A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7' . + 'ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864' . + 'D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E2' . + '08E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D7' . + '88719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8' . + 'DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2' . + '233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA9' . + '93B4EA988D8FDDC186FFB7DC90A6C08F4DF435C93402849236C3FAB4D27C7026' . + 'C1D4DCB2602646DEC9751E763DBA37BDF8FF9406AD9E530EE5DB382F413001AE' . + 'B06A53ED9027D831179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1B' . + 'DB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF5983CA01C64B92EC' . + 'F032EA15D1721D03F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E' . + '59E7C97FBEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA' . + 'CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58BB7C5DA76' . + 'F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632387FE8D76E3C0468' . + '043E8F663F4860EE12BF2D5B0B7474D6E694F91E6DBE115974A3926F12FEE5E4' . + '38777CB6A932DF8CD8BEC4D073B931BA3BC832B68D9DD300741FA7BF8AFC47ED' . + '2576F6936BA424663AAB639C5AE4F5683423B4742BF1C978238F16CBE39D652D' . + 'E3FDB8BEFC848AD922222E04A4037C0713EB57A81A23F0C73473FC646CEA306B' . + '4BCBC8862F8385DDFA9D4B7FA2C087E879683303ED5BDD3A062B3CF5B3A278A6' . + '6D2A13F83F44F82DDF310EE074AB6A364597E899A0255DC164F31CC50846851D' . + 'F9AB48195DED7EA1B1D510BD7EE74D73FAF36BC31ECFA268359046F4EB879F92' . + '4009438B481C6CD7889A002ED5EE382BC9190DA6FC026E479558E4475677E9AA' . + '9E3050E2765694DFC81F56E880B96E7160C980DD98EDD3DFFFFFFFFFFFFFFFFF'; + break; + default: + throw new \InvalidArgumentException('Invalid named prime provided'); + } + + $params->prime = new BigInteger($prime, 16); + $params->base = new BigInteger(2); + + return $params; + } + + /** + * Create public / private key pair. + * + * The rationale for the second parameter is described in http://tools.ietf.org/html/rfc4419#section-6.2 : + * + * "To increase the speed of the key exchange, both client and server may + * reduce the size of their private exponents. It should be at least + * twice as long as the key material that is generated from the shared + * secret. For more details, see the paper by van Oorschot and Wiener + * [VAN-OORSCHOT]." + * + * $length is in bits + * + * @param Parameters $params + * @param int $length optional + * @return DH\PrivateKey + */ + public static function createKey(Parameters $params, $length = 0) + { + $one = new BigInteger(1); + if ($length) { + $max = $one->bitwise_leftShift($length); + $max = $max->subtract($one); + } else { + $max = $params->prime->subtract($one); + } + + $key = new PrivateKey(); + $key->prime = $params->prime; + $key->base = $params->base; + $key->privateKey = BigInteger::randomRange($one, $max); + $key->publicKey = $key->base->powMod($key->privateKey, $key->prime); + return $key; + } + + /** + * Compute Shared Secret + * + * @param PrivateKey|EC $private + * @param PublicKey|BigInteger|string $public + * @return mixed + */ + public static function computeSecret($private, $public) + { + if ($private instanceof PrivateKey) { // DH\PrivateKey + switch (true) { + case $public instanceof PublicKey: + if (!$private->prime->equals($public->prime) || !$private->base->equals($public->base)) { + throw new \InvalidArgumentException('The public and private key do not share the same prime and / or base numbers'); + } + return $public->publicKey->powMod($private->privateKey, $private->prime)->toBytes(true); + case is_string($public): + $public = new BigInteger($public, -256); + // fall-through + case $public instanceof BigInteger: + return $public->powMod($private->privateKey, $private->prime)->toBytes(true); + default: + throw new \InvalidArgumentException('$public needs to be an instance of DH\PublicKey, a BigInteger or a string'); + } + } + + if ($private instanceof EC\PrivateKey) { + switch (true) { + case $public instanceof EC\PublicKey: + $public = $public->getEncodedCoordinates(); + // fall-through + case is_string($public): + $point = $private->multiply($public); + switch ($private->getCurve()) { + case 'Curve25519': + case 'Curve448': + $secret = $point; + break; + default: + // according to https://www.secg.org/sec1-v2.pdf#page=33 only X is returned + $secret = substr($point, 1, (strlen($point) - 1) >> 1); + } + /* + if (($secret[0] & "\x80") === "\x80") { + $secret = "\0$secret"; + } + */ + return $secret; + default: + throw new \InvalidArgumentException('$public needs to be an instance of EC\PublicKey or a string (an encoded coordinate)'); + } + } + } + + /** + * Load the key + * + * @param string $key + * @param string $password optional + * @return AsymmetricKey + */ + public static function load($key, $password = false) + { + try { + return EC::load($key, $password); + } catch (NoKeyLoadedException $e) { + } + + return parent::load($key, $password); + } + + /** + * OnLoad Handler + * + * @return bool + */ + protected static function onLoad(array $components) + { + if (!isset($components['privateKey']) && !isset($components['publicKey'])) { + $new = new Parameters(); + } else { + $new = isset($components['privateKey']) ? + new PrivateKey() : + new PublicKey(); + } + + $new->prime = $components['prime']; + $new->base = $components['base']; + + if (isset($components['privateKey'])) { + $new->privateKey = $components['privateKey']; + } + if (isset($components['publicKey'])) { + $new->publicKey = $components['publicKey']; + } + + return $new; + } + + /** + * Determines which hashing function should be used + * + * @param string $hash + */ + public function withHash($hash) + { + throw new UnsupportedOperationException('DH does not use a hash algorithm'); + } + + /** + * Returns the hash algorithm currently being used + * + */ + public function getHash() + { + throw new UnsupportedOperationException('DH does not use a hash algorithm'); + } + + /** + * Returns the parameters + * + * A public / private key is only returned if the currently loaded "key" contains an x or y + * value. + * + * @see self::getPublicKey() + * @return mixed + */ + public function getParameters() + { + $type = self::validatePlugin('Keys', 'PKCS1', 'saveParameters'); + + $key = $type::saveParameters($this->prime, $this->base); + return self::load($key, 'PKCS1'); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/DH/Formats/Keys/PKCS1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/DH/Formats/Keys/PKCS1.php new file mode 100644 index 00000000..65a0a5db --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/DH/Formats/Keys/PKCS1.php @@ -0,0 +1,77 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\Crypt\DH\Formats\Keys; + +use phpseclib3\Crypt\Common\Formats\Keys\PKCS1 as Progenitor; +use phpseclib3\File\ASN1; +use phpseclib3\File\ASN1\Maps; +use phpseclib3\Math\BigInteger; + +/** + * "PKCS1" Formatted DH Key Handler + * + * @author Jim Wigginton + */ +abstract class PKCS1 extends Progenitor +{ + /** + * Break a public or private key down into its constituent components + * + * @param string $key + * @param string $password optional + * @return array + */ + public static function load($key, $password = '') + { + $key = parent::load($key, $password); + + $decoded = ASN1::decodeBER($key); + if (!$decoded) { + throw new \RuntimeException('Unable to decode BER'); + } + + $components = ASN1::asn1map($decoded[0], Maps\DHParameter::MAP); + if (!is_array($components)) { + throw new \RuntimeException('Unable to perform ASN1 mapping on parameters'); + } + + return $components; + } + + /** + * Convert EC parameters to the appropriate format + * + * @return string + */ + public static function saveParameters(BigInteger $prime, BigInteger $base, array $options = []) + { + $params = [ + 'prime' => $prime, + 'base' => $base + ]; + $params = ASN1::encodeDER($params, Maps\DHParameter::MAP); + + return "-----BEGIN DH PARAMETERS-----\r\n" . + chunk_split(base64_encode($params), 64) . + "-----END DH PARAMETERS-----\r\n"; + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/DH/Formats/Keys/PKCS8.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/DH/Formats/Keys/PKCS8.php new file mode 100644 index 00000000..dc5375f2 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/DH/Formats/Keys/PKCS8.php @@ -0,0 +1,146 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\Crypt\DH\Formats\Keys; + +use phpseclib3\Common\Functions\Strings; +use phpseclib3\Crypt\Common\Formats\Keys\PKCS8 as Progenitor; +use phpseclib3\File\ASN1; +use phpseclib3\File\ASN1\Maps; +use phpseclib3\Math\BigInteger; + +/** + * PKCS#8 Formatted DH Key Handler + * + * @author Jim Wigginton + */ +abstract class PKCS8 extends Progenitor +{ + /** + * OID Name + * + * @var string + */ + const OID_NAME = 'dhKeyAgreement'; + + /** + * OID Value + * + * @var string + */ + const OID_VALUE = '1.2.840.113549.1.3.1'; + + /** + * Child OIDs loaded + * + * @var bool + */ + protected static $childOIDsLoaded = false; + + /** + * Break a public or private key down into its constituent components + * + * @param string $key + * @param string $password optional + * @return array + */ + public static function load($key, $password = '') + { + if (!Strings::is_stringable($key)) { + throw new \UnexpectedValueException('Key should be a string - not a ' . gettype($key)); + } + + $isPublic = strpos($key, 'PUBLIC') !== false; + + $key = parent::load($key, $password); + + $type = isset($key['privateKey']) ? 'privateKey' : 'publicKey'; + + switch (true) { + case !$isPublic && $type == 'publicKey': + throw new \UnexpectedValueException('Human readable string claims non-public key but DER encoded string claims public key'); + case $isPublic && $type == 'privateKey': + throw new \UnexpectedValueException('Human readable string claims public key but DER encoded string claims private key'); + } + + $decoded = ASN1::decodeBER($key[$type . 'Algorithm']['parameters']->element); + if (empty($decoded)) { + throw new \RuntimeException('Unable to decode BER of parameters'); + } + $components = ASN1::asn1map($decoded[0], Maps\DHParameter::MAP); + if (!is_array($components)) { + throw new \RuntimeException('Unable to perform ASN1 mapping on parameters'); + } + + $decoded = ASN1::decodeBER($key[$type]); + switch (true) { + case !isset($decoded): + case !isset($decoded[0]['content']): + case !$decoded[0]['content'] instanceof BigInteger: + throw new \RuntimeException('Unable to decode BER of parameters'); + } + $components[$type] = $decoded[0]['content']; + + return $components; + } + + /** + * Convert a private key to the appropriate format. + * + * @param \phpseclib3\Math\BigInteger $prime + * @param \phpseclib3\Math\BigInteger $base + * @param \phpseclib3\Math\BigInteger $privateKey + * @param \phpseclib3\Math\BigInteger $publicKey + * @param string $password optional + * @param array $options optional + * @return string + */ + public static function savePrivateKey(BigInteger $prime, BigInteger $base, BigInteger $privateKey, BigInteger $publicKey, $password = '', array $options = []) + { + $params = [ + 'prime' => $prime, + 'base' => $base + ]; + $params = ASN1::encodeDER($params, Maps\DHParameter::MAP); + $params = new ASN1\Element($params); + $key = ASN1::encodeDER($privateKey, ['type' => ASN1::TYPE_INTEGER]); + return self::wrapPrivateKey($key, [], $params, $password, null, '', $options); + } + + /** + * Convert a public key to the appropriate format + * + * @param \phpseclib3\Math\BigInteger $prime + * @param \phpseclib3\Math\BigInteger $base + * @param \phpseclib3\Math\BigInteger $publicKey + * @param array $options optional + * @return string + */ + public static function savePublicKey(BigInteger $prime, BigInteger $base, BigInteger $publicKey, array $options = []) + { + $params = [ + 'prime' => $prime, + 'base' => $base + ]; + $params = ASN1::encodeDER($params, Maps\DHParameter::MAP); + $params = new ASN1\Element($params); + $key = ASN1::encodeDER($publicKey, ['type' => ASN1::TYPE_INTEGER]); + return self::wrapPublicKey($key, $params); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/DH/Parameters.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/DH/Parameters.php new file mode 100644 index 00000000..e4c15576 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/DH/Parameters.php @@ -0,0 +1,36 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\Crypt\DH; + +use phpseclib3\Crypt\DH; + +/** + * DH Parameters + * + * @author Jim Wigginton + */ +class Parameters extends DH +{ + /** + * Returns the parameters + * + * @param string $type + * @param array $options optional + * @return string + */ + public function toString($type = 'PKCS1', array $options = []) + { + $type = self::validatePlugin('Keys', 'PKCS1', 'saveParameters'); + + return $type::saveParameters($this->prime, $this->base, $options); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/DH/PrivateKey.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/DH/PrivateKey.php new file mode 100644 index 00000000..fe03452c --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/DH/PrivateKey.php @@ -0,0 +1,75 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\Crypt\DH; + +use phpseclib3\Crypt\Common; +use phpseclib3\Crypt\DH; + +/** + * DH Private Key + * + * @author Jim Wigginton + */ +class PrivateKey extends DH +{ + use Common\Traits\PasswordProtected; + + /** + * Private Key + * + * @var \phpseclib3\Math\BigInteger + */ + protected $privateKey; + + /** + * Public Key + * + * @var \phpseclib3\Math\BigInteger + */ + protected $publicKey; + + /** + * Returns the public key + * + * @return DH\PublicKey + */ + public function getPublicKey() + { + $type = self::validatePlugin('Keys', 'PKCS8', 'savePublicKey'); + + if (!isset($this->publicKey)) { + $this->publicKey = $this->base->powMod($this->privateKey, $this->prime); + } + + $key = $type::savePublicKey($this->prime, $this->base, $this->publicKey); + + return DH::loadFormat('PKCS8', $key); + } + + /** + * Returns the private key + * + * @param string $type + * @param array $options optional + * @return string + */ + public function toString($type, array $options = []) + { + $type = self::validatePlugin('Keys', $type, 'savePrivateKey'); + + if (!isset($this->publicKey)) { + $this->publicKey = $this->base->powMod($this->privateKey, $this->prime); + } + + return $type::savePrivateKey($this->prime, $this->base, $this->privateKey, $this->publicKey, $this->password, $options); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/DH/PublicKey.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/DH/PublicKey.php new file mode 100644 index 00000000..3536360c --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/DH/PublicKey.php @@ -0,0 +1,49 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\Crypt\DH; + +use phpseclib3\Crypt\Common; +use phpseclib3\Crypt\DH; + +/** + * DH Public Key + * + * @author Jim Wigginton + */ +class PublicKey extends DH +{ + use Common\Traits\Fingerprint; + + /** + * Returns the public key + * + * @param string $type + * @param array $options optional + * @return string + */ + public function toString($type, array $options = []) + { + $type = self::validatePlugin('Keys', $type, 'savePublicKey'); + + return $type::savePublicKey($this->prime, $this->base, $this->publicKey, $options); + } + + /** + * Returns the public key as a BigInteger + * + * @return \phpseclib3\Math\BigInteger + */ + public function toBigInteger() + { + return $this->publicKey; + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA.php new file mode 100644 index 00000000..1d265860 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA.php @@ -0,0 +1,327 @@ + + * getPublicKey(); + * + * $plaintext = 'terrafrost'; + * + * $signature = $private->sign($plaintext); + * + * echo $public->verify($plaintext, $signature) ? 'verified' : 'unverified'; + * ?> + * + * + * @author Jim Wigginton + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\Crypt; + +use phpseclib3\Crypt\Common\AsymmetricKey; +use phpseclib3\Crypt\DSA\Parameters; +use phpseclib3\Crypt\DSA\PrivateKey; +use phpseclib3\Crypt\DSA\PublicKey; +use phpseclib3\Exception\InsufficientSetupException; +use phpseclib3\Math\BigInteger; + +/** + * Pure-PHP FIPS 186-4 compliant implementation of DSA. + * + * @author Jim Wigginton + */ +abstract class DSA extends AsymmetricKey +{ + /** + * Algorithm Name + * + * @var string + */ + const ALGORITHM = 'DSA'; + + /** + * DSA Prime P + * + * @var \phpseclib3\Math\BigInteger + */ + protected $p; + + /** + * DSA Group Order q + * + * Prime divisor of p-1 + * + * @var \phpseclib3\Math\BigInteger + */ + protected $q; + + /** + * DSA Group Generator G + * + * @var \phpseclib3\Math\BigInteger + */ + protected $g; + + /** + * DSA public key value y + * + * @var \phpseclib3\Math\BigInteger + */ + protected $y; + + /** + * Signature Format + * + * @var string + */ + protected $sigFormat; + + /** + * Signature Format (Short) + * + * @var string + */ + protected $shortFormat; + + /** + * Create DSA parameters + * + * @param int $L + * @param int $N + * @return \phpseclib3\Crypt\DSA|bool + */ + public static function createParameters($L = 2048, $N = 224) + { + self::initialize_static_variables(); + + if (!isset(self::$engines['PHP'])) { + self::useBestEngine(); + } + + switch (true) { + case $N == 160: + /* + in FIPS 186-1 and 186-2 N was fixed at 160 whereas K had an upper bound of 1024. + RFC 4253 (SSH Transport Layer Protocol) references FIPS 186-2 and as such most + SSH DSA implementations only support keys with an N of 160. + puttygen let's you set the size of L (but not the size of N) and uses 2048 as the + default L value. that's not really compliant with any of the FIPS standards, however, + for the purposes of maintaining compatibility with puttygen, we'll support it + */ + //case ($L >= 512 || $L <= 1024) && (($L & 0x3F) == 0) && $N == 160: + // FIPS 186-3 changed this as follows: + //case $L == 1024 && $N == 160: + case $L == 2048 && $N == 224: + case $L == 2048 && $N == 256: + case $L == 3072 && $N == 256: + break; + default: + throw new \InvalidArgumentException('Invalid values for N and L'); + } + + $two = new BigInteger(2); + + $q = BigInteger::randomPrime($N); + $divisor = $q->multiply($two); + + do { + $x = BigInteger::random($L); + list(, $c) = $x->divide($divisor); + $p = $x->subtract($c->subtract(self::$one)); + } while ($p->getLength() != $L || !$p->isPrime()); + + $p_1 = $p->subtract(self::$one); + list($e) = $p_1->divide($q); + + // quoting http://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf#page=50 , + // "h could be obtained from a random number generator or from a counter that + // changes after each use". PuTTY (sshdssg.c) starts h off at 1 and increments + // it on each loop. wikipedia says "commonly h = 2 is used" so we'll just do that + $h = clone $two; + while (true) { + $g = $h->powMod($e, $p); + if (!$g->equals(self::$one)) { + break; + } + $h = $h->add(self::$one); + } + + $dsa = new Parameters(); + $dsa->p = $p; + $dsa->q = $q; + $dsa->g = $g; + + return $dsa; + } + + /** + * Create public / private key pair. + * + * This method is a bit polymorphic. It can take a DSA/Parameters object, L / N as two distinct parameters or + * no parameters (at which point L and N will be generated with this method) + * + * Returns the private key, from which the publickey can be extracted + * + * @param int[] ...$args + * @return DSA\PrivateKey + */ + public static function createKey(...$args) + { + self::initialize_static_variables(); + + if (!isset(self::$engines['PHP'])) { + self::useBestEngine(); + } + + if (count($args) == 2 && is_int($args[0]) && is_int($args[1])) { + $params = self::createParameters($args[0], $args[1]); + } elseif (count($args) == 1 && $args[0] instanceof Parameters) { + $params = $args[0]; + } elseif (!count($args)) { + $params = self::createParameters(); + } else { + throw new InsufficientSetupException('Valid parameters are either two integers (L and N), a single DSA object or no parameters at all.'); + } + + $private = new PrivateKey(); + $private->p = $params->p; + $private->q = $params->q; + $private->g = $params->g; + + $private->x = BigInteger::randomRange(self::$one, $private->q->subtract(self::$one)); + $private->y = $private->g->powMod($private->x, $private->p); + + //$public = clone $private; + //unset($public->x); + + return $private + ->withHash($params->hash->getHash()) + ->withSignatureFormat($params->shortFormat); + } + + /** + * OnLoad Handler + * + * @return bool + */ + protected static function onLoad(array $components) + { + if (!isset(self::$engines['PHP'])) { + self::useBestEngine(); + } + + if (!isset($components['x']) && !isset($components['y'])) { + $new = new Parameters(); + } elseif (isset($components['x'])) { + $new = new PrivateKey(); + $new->x = $components['x']; + } else { + $new = new PublicKey(); + } + + $new->p = $components['p']; + $new->q = $components['q']; + $new->g = $components['g']; + + if (isset($components['y'])) { + $new->y = $components['y']; + } + + return $new; + } + + /** + * Constructor + * + * PublicKey and PrivateKey objects can only be created from abstract RSA class + */ + protected function __construct() + { + $this->sigFormat = self::validatePlugin('Signature', 'ASN1'); + $this->shortFormat = 'ASN1'; + + parent::__construct(); + } + + /** + * Returns the key size + * + * More specifically, this L (the length of DSA Prime P) and N (the length of DSA Group Order q) + * + * @return array + */ + public function getLength() + { + return ['L' => $this->p->getLength(), 'N' => $this->q->getLength()]; + } + + /** + * Returns the current engine being used + * + * @see self::useInternalEngine() + * @see self::useBestEngine() + * @return string + */ + public function getEngine() + { + if (!isset(self::$engines['PHP'])) { + self::useBestEngine(); + } + return self::$engines['OpenSSL'] && in_array($this->hash->getHash(), openssl_get_md_methods()) ? + 'OpenSSL' : 'PHP'; + } + + /** + * Returns the parameters + * + * A public / private key is only returned if the currently loaded "key" contains an x or y + * value. + * + * @see self::getPublicKey() + * @return mixed + */ + public function getParameters() + { + $type = self::validatePlugin('Keys', 'PKCS1', 'saveParameters'); + + $key = $type::saveParameters($this->p, $this->q, $this->g); + return DSA::load($key, 'PKCS1') + ->withHash($this->hash->getHash()) + ->withSignatureFormat($this->shortFormat); + } + + /** + * Determines the signature padding mode + * + * Valid values are: ASN1, SSH2, Raw + * + * @param string $format + */ + public function withSignatureFormat($format) + { + $new = clone $this; + $new->shortFormat = $format; + $new->sigFormat = self::validatePlugin('Signature', $format); + return $new; + } + + /** + * Returns the signature format currently being used + * + */ + public function getSignatureFormat() + { + return $this->shortFormat; + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Keys/OpenSSH.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Keys/OpenSSH.php new file mode 100644 index 00000000..cc204fa9 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Keys/OpenSSH.php @@ -0,0 +1,118 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\Crypt\DSA\Formats\Keys; + +use phpseclib3\Common\Functions\Strings; +use phpseclib3\Crypt\Common\Formats\Keys\OpenSSH as Progenitor; +use phpseclib3\Math\BigInteger; + +/** + * OpenSSH Formatted DSA Key Handler + * + * @author Jim Wigginton + */ +abstract class OpenSSH extends Progenitor +{ + /** + * Supported Key Types + * + * @var array + */ + protected static $types = ['ssh-dss']; + + /** + * Break a public or private key down into its constituent components + * + * @param string $key + * @param string $password optional + * @return array + */ + public static function load($key, $password = '') + { + $parsed = parent::load($key, $password); + + if (isset($parsed['paddedKey'])) { + list($type) = Strings::unpackSSH2('s', $parsed['paddedKey']); + if ($type != $parsed['type']) { + throw new \RuntimeException("The public and private keys are not of the same type ($type vs $parsed[type])"); + } + + list($p, $q, $g, $y, $x, $comment) = Strings::unpackSSH2('i5s', $parsed['paddedKey']); + + return compact('p', 'q', 'g', 'y', 'x', 'comment'); + } + + list($p, $q, $g, $y) = Strings::unpackSSH2('iiii', $parsed['publicKey']); + + $comment = $parsed['comment']; + + return compact('p', 'q', 'g', 'y', 'comment'); + } + + /** + * Convert a public key to the appropriate format + * + * @param \phpseclib3\Math\BigInteger $p + * @param \phpseclib3\Math\BigInteger $q + * @param \phpseclib3\Math\BigInteger $g + * @param \phpseclib3\Math\BigInteger $y + * @param array $options optional + * @return string + */ + public static function savePublicKey(BigInteger $p, BigInteger $q, BigInteger $g, BigInteger $y, array $options = []) + { + if ($q->getLength() != 160) { + throw new \InvalidArgumentException('SSH only supports keys with an N (length of Group Order q) of 160'); + } + + // from : + // string "ssh-dss" + // mpint p + // mpint q + // mpint g + // mpint y + $DSAPublicKey = Strings::packSSH2('siiii', 'ssh-dss', $p, $q, $g, $y); + + if (isset($options['binary']) ? $options['binary'] : self::$binary) { + return $DSAPublicKey; + } + + $comment = isset($options['comment']) ? $options['comment'] : self::$comment; + $DSAPublicKey = 'ssh-dss ' . base64_encode($DSAPublicKey) . ' ' . $comment; + + return $DSAPublicKey; + } + + /** + * Convert a private key to the appropriate format. + * + * @param \phpseclib3\Math\BigInteger $p + * @param \phpseclib3\Math\BigInteger $q + * @param \phpseclib3\Math\BigInteger $g + * @param \phpseclib3\Math\BigInteger $y + * @param \phpseclib3\Math\BigInteger $x + * @param string $password optional + * @param array $options optional + * @return string + */ + public static function savePrivateKey(BigInteger $p, BigInteger $q, BigInteger $g, BigInteger $y, BigInteger $x, $password = '', array $options = []) + { + $publicKey = self::savePublicKey($p, $q, $g, $y, ['binary' => true]); + $privateKey = Strings::packSSH2('si5', 'ssh-dss', $p, $q, $g, $y, $x); + + return self::wrapPrivateKey($publicKey, $privateKey, $password, $options); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Keys/PKCS1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Keys/PKCS1.php new file mode 100644 index 00000000..52a04992 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Keys/PKCS1.php @@ -0,0 +1,143 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\Crypt\DSA\Formats\Keys; + +use phpseclib3\Common\Functions\Strings; +use phpseclib3\Crypt\Common\Formats\Keys\PKCS1 as Progenitor; +use phpseclib3\File\ASN1; +use phpseclib3\File\ASN1\Maps; +use phpseclib3\Math\BigInteger; + +/** + * PKCS#1 Formatted DSA Key Handler + * + * @author Jim Wigginton + */ +abstract class PKCS1 extends Progenitor +{ + /** + * Break a public or private key down into its constituent components + * + * @param string $key + * @param string $password optional + * @return array + */ + public static function load($key, $password = '') + { + $key = parent::load($key, $password); + + $decoded = ASN1::decodeBER($key); + if (!$decoded) { + throw new \RuntimeException('Unable to decode BER'); + } + + $key = ASN1::asn1map($decoded[0], Maps\DSAParams::MAP); + if (is_array($key)) { + return $key; + } + + $key = ASN1::asn1map($decoded[0], Maps\DSAPrivateKey::MAP); + if (is_array($key)) { + return $key; + } + + $key = ASN1::asn1map($decoded[0], Maps\DSAPublicKey::MAP); + if (is_array($key)) { + return $key; + } + + throw new \RuntimeException('Unable to perform ASN1 mapping'); + } + + /** + * Convert DSA parameters to the appropriate format + * + * @param \phpseclib3\Math\BigInteger $p + * @param \phpseclib3\Math\BigInteger $q + * @param \phpseclib3\Math\BigInteger $g + * @return string + */ + public static function saveParameters(BigInteger $p, BigInteger $q, BigInteger $g) + { + $key = [ + 'p' => $p, + 'q' => $q, + 'g' => $g + ]; + + $key = ASN1::encodeDER($key, Maps\DSAParams::MAP); + + return "-----BEGIN DSA PARAMETERS-----\r\n" . + chunk_split(Strings::base64_encode($key), 64) . + "-----END DSA PARAMETERS-----\r\n"; + } + + /** + * Convert a private key to the appropriate format. + * + * @param \phpseclib3\Math\BigInteger $p + * @param \phpseclib3\Math\BigInteger $q + * @param \phpseclib3\Math\BigInteger $g + * @param \phpseclib3\Math\BigInteger $y + * @param \phpseclib3\Math\BigInteger $x + * @param string $password optional + * @param array $options optional + * @return string + */ + public static function savePrivateKey(BigInteger $p, BigInteger $q, BigInteger $g, BigInteger $y, BigInteger $x, $password = '', array $options = []) + { + $key = [ + 'version' => 0, + 'p' => $p, + 'q' => $q, + 'g' => $g, + 'y' => $y, + 'x' => $x + ]; + + $key = ASN1::encodeDER($key, Maps\DSAPrivateKey::MAP); + + return self::wrapPrivateKey($key, 'DSA', $password, $options); + } + + /** + * Convert a public key to the appropriate format + * + * @param \phpseclib3\Math\BigInteger $p + * @param \phpseclib3\Math\BigInteger $q + * @param \phpseclib3\Math\BigInteger $g + * @param \phpseclib3\Math\BigInteger $y + * @return string + */ + public static function savePublicKey(BigInteger $p, BigInteger $q, BigInteger $g, BigInteger $y) + { + $key = ASN1::encodeDER($y, Maps\DSAPublicKey::MAP); + + return self::wrapPublicKey($key, 'DSA'); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Keys/PKCS8.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Keys/PKCS8.php new file mode 100644 index 00000000..a5858b7e --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Keys/PKCS8.php @@ -0,0 +1,160 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\Crypt\DSA\Formats\Keys; + +use phpseclib3\Common\Functions\Strings; +use phpseclib3\Crypt\Common\Formats\Keys\PKCS8 as Progenitor; +use phpseclib3\File\ASN1; +use phpseclib3\File\ASN1\Maps; +use phpseclib3\Math\BigInteger; + +/** + * PKCS#8 Formatted DSA Key Handler + * + * @author Jim Wigginton + */ +abstract class PKCS8 extends Progenitor +{ + /** + * OID Name + * + * @var string + */ + const OID_NAME = 'id-dsa'; + + /** + * OID Value + * + * @var string + */ + const OID_VALUE = '1.2.840.10040.4.1'; + + /** + * Child OIDs loaded + * + * @var bool + */ + protected static $childOIDsLoaded = false; + + /** + * Break a public or private key down into its constituent components + * + * @param string $key + * @param string $password optional + * @return array + */ + public static function load($key, $password = '') + { + if (!Strings::is_stringable($key)) { + throw new \UnexpectedValueException('Key should be a string - not a ' . gettype($key)); + } + + $isPublic = strpos($key, 'PUBLIC') !== false; + + $key = parent::load($key, $password); + + $type = isset($key['privateKey']) ? 'privateKey' : 'publicKey'; + + switch (true) { + case !$isPublic && $type == 'publicKey': + throw new \UnexpectedValueException('Human readable string claims non-public key but DER encoded string claims public key'); + case $isPublic && $type == 'privateKey': + throw new \UnexpectedValueException('Human readable string claims public key but DER encoded string claims private key'); + } + + $decoded = ASN1::decodeBER($key[$type . 'Algorithm']['parameters']->element); + if (!$decoded) { + throw new \RuntimeException('Unable to decode BER of parameters'); + } + $components = ASN1::asn1map($decoded[0], Maps\DSAParams::MAP); + if (!is_array($components)) { + throw new \RuntimeException('Unable to perform ASN1 mapping on parameters'); + } + + $decoded = ASN1::decodeBER($key[$type]); + if (empty($decoded)) { + throw new \RuntimeException('Unable to decode BER'); + } + + $var = $type == 'privateKey' ? 'x' : 'y'; + $components[$var] = ASN1::asn1map($decoded[0], Maps\DSAPublicKey::MAP); + if (!$components[$var] instanceof BigInteger) { + throw new \RuntimeException('Unable to perform ASN1 mapping'); + } + + if (isset($key['meta'])) { + $components['meta'] = $key['meta']; + } + + return $components; + } + + /** + * Convert a private key to the appropriate format. + * + * @param \phpseclib3\Math\BigInteger $p + * @param \phpseclib3\Math\BigInteger $q + * @param \phpseclib3\Math\BigInteger $g + * @param \phpseclib3\Math\BigInteger $y + * @param \phpseclib3\Math\BigInteger $x + * @param string $password optional + * @param array $options optional + * @return string + */ + public static function savePrivateKey(BigInteger $p, BigInteger $q, BigInteger $g, BigInteger $y, BigInteger $x, $password = '', array $options = []) + { + $params = [ + 'p' => $p, + 'q' => $q, + 'g' => $g + ]; + $params = ASN1::encodeDER($params, Maps\DSAParams::MAP); + $params = new ASN1\Element($params); + $key = ASN1::encodeDER($x, Maps\DSAPublicKey::MAP); + return self::wrapPrivateKey($key, [], $params, $password, null, '', $options); + } + + /** + * Convert a public key to the appropriate format + * + * @param \phpseclib3\Math\BigInteger $p + * @param \phpseclib3\Math\BigInteger $q + * @param \phpseclib3\Math\BigInteger $g + * @param \phpseclib3\Math\BigInteger $y + * @param array $options optional + * @return string + */ + public static function savePublicKey(BigInteger $p, BigInteger $q, BigInteger $g, BigInteger $y, array $options = []) + { + $params = [ + 'p' => $p, + 'q' => $q, + 'g' => $g + ]; + $params = ASN1::encodeDER($params, Maps\DSAParams::MAP); + $params = new ASN1\Element($params); + $key = ASN1::encodeDER($y, Maps\DSAPublicKey::MAP); + return self::wrapPublicKey($key, $params); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Keys/PuTTY.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Keys/PuTTY.php new file mode 100644 index 00000000..ff7f4c95 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Keys/PuTTY.php @@ -0,0 +1,109 @@ + 160 kinda useless, hence this handlers not supporting such keys. + * + * PHP version 5 + * + * @author Jim Wigginton + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\Crypt\DSA\Formats\Keys; + +use phpseclib3\Common\Functions\Strings; +use phpseclib3\Crypt\Common\Formats\Keys\PuTTY as Progenitor; +use phpseclib3\Math\BigInteger; + +/** + * PuTTY Formatted DSA Key Handler + * + * @author Jim Wigginton + */ +abstract class PuTTY extends Progenitor +{ + /** + * Public Handler + * + * @var string + */ + const PUBLIC_HANDLER = 'phpseclib3\Crypt\DSA\Formats\Keys\OpenSSH'; + + /** + * Algorithm Identifier + * + * @var array + */ + protected static $types = ['ssh-dss']; + + /** + * Break a public or private key down into its constituent components + * + * @param string $key + * @param string $password optional + * @return array + */ + public static function load($key, $password = '') + { + $components = parent::load($key, $password); + if (!isset($components['private'])) { + return $components; + } + extract($components); + unset($components['public'], $components['private']); + + list($p, $q, $g, $y) = Strings::unpackSSH2('iiii', $public); + list($x) = Strings::unpackSSH2('i', $private); + + return compact('p', 'q', 'g', 'y', 'x', 'comment'); + } + + /** + * Convert a private key to the appropriate format. + * + * @param \phpseclib3\Math\BigInteger $p + * @param \phpseclib3\Math\BigInteger $q + * @param \phpseclib3\Math\BigInteger $g + * @param \phpseclib3\Math\BigInteger $y + * @param \phpseclib3\Math\BigInteger $x + * @param string $password optional + * @param array $options optional + * @return string + */ + public static function savePrivateKey(BigInteger $p, BigInteger $q, BigInteger $g, BigInteger $y, BigInteger $x, $password = false, array $options = []) + { + if ($q->getLength() != 160) { + throw new \InvalidArgumentException('SSH only supports keys with an N (length of Group Order q) of 160'); + } + + $public = Strings::packSSH2('iiii', $p, $q, $g, $y); + $private = Strings::packSSH2('i', $x); + + return self::wrapPrivateKey($public, $private, 'ssh-dsa', $password, $options); + } + + /** + * Convert a public key to the appropriate format + * + * @param \phpseclib3\Math\BigInteger $p + * @param \phpseclib3\Math\BigInteger $q + * @param \phpseclib3\Math\BigInteger $g + * @param \phpseclib3\Math\BigInteger $y + * @return string + */ + public static function savePublicKey(BigInteger $p, BigInteger $q, BigInteger $g, BigInteger $y) + { + if ($q->getLength() != 160) { + throw new \InvalidArgumentException('SSH only supports keys with an N (length of Group Order q) of 160'); + } + + return self::wrapPublicKey(Strings::packSSH2('iiii', $p, $q, $g, $y), 'ssh-dsa'); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Keys/Raw.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Keys/Raw.php new file mode 100644 index 00000000..201aa6f9 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Keys/Raw.php @@ -0,0 +1,85 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\Crypt\DSA\Formats\Keys; + +use phpseclib3\Math\BigInteger; + +/** + * Raw DSA Key Handler + * + * @author Jim Wigginton + */ +abstract class Raw +{ + /** + * Break a public or private key down into its constituent components + * + * @param array $key + * @param string $password optional + * @return array + */ + public static function load($key, $password = '') + { + if (!is_array($key)) { + throw new \UnexpectedValueException('Key should be a array - not a ' . gettype($key)); + } + + switch (true) { + case !isset($key['p']) || !isset($key['q']) || !isset($key['g']): + case !$key['p'] instanceof BigInteger: + case !$key['q'] instanceof BigInteger: + case !$key['g'] instanceof BigInteger: + case !isset($key['x']) && !isset($key['y']): + case isset($key['x']) && !$key['x'] instanceof BigInteger: + case isset($key['y']) && !$key['y'] instanceof BigInteger: + throw new \UnexpectedValueException('Key appears to be malformed'); + } + + $options = ['p' => 1, 'q' => 1, 'g' => 1, 'x' => 1, 'y' => 1]; + + return array_intersect_key($key, $options); + } + + /** + * Convert a private key to the appropriate format. + * + * @param \phpseclib3\Math\BigInteger $p + * @param \phpseclib3\Math\BigInteger $q + * @param \phpseclib3\Math\BigInteger $g + * @param \phpseclib3\Math\BigInteger $y + * @param \phpseclib3\Math\BigInteger $x + * @param string $password optional + * @return string + */ + public static function savePrivateKey(BigInteger $p, BigInteger $q, BigInteger $g, BigInteger $y, BigInteger $x, $password = '') + { + return compact('p', 'q', 'g', 'y', 'x'); + } + + /** + * Convert a public key to the appropriate format + * + * @param \phpseclib3\Math\BigInteger $p + * @param \phpseclib3\Math\BigInteger $q + * @param \phpseclib3\Math\BigInteger $g + * @param \phpseclib3\Math\BigInteger $y + * @return string + */ + public static function savePublicKey(BigInteger $p, BigInteger $q, BigInteger $g, BigInteger $y) + { + return compact('p', 'q', 'g', 'y'); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Keys/XML.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Keys/XML.php new file mode 100644 index 00000000..fc363677 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Keys/XML.php @@ -0,0 +1,132 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\Crypt\DSA\Formats\Keys; + +use phpseclib3\Common\Functions\Strings; +use phpseclib3\Exception\BadConfigurationException; +use phpseclib3\Math\BigInteger; + +/** + * XML Formatted DSA Key Handler + * + * @author Jim Wigginton + */ +abstract class XML +{ + /** + * Break a public or private key down into its constituent components + * + * @param string $key + * @param string $password optional + * @return array + */ + public static function load($key, $password = '') + { + if (!Strings::is_stringable($key)) { + throw new \UnexpectedValueException('Key should be a string - not a ' . gettype($key)); + } + + if (!class_exists('DOMDocument')) { + throw new BadConfigurationException('The dom extension is not setup correctly on this system'); + } + + $use_errors = libxml_use_internal_errors(true); + + $dom = new \DOMDocument(); + if (substr($key, 0, 5) != '' . $key . ''; + } + if (!$dom->loadXML($key)) { + libxml_use_internal_errors($use_errors); + throw new \UnexpectedValueException('Key does not appear to contain XML'); + } + $xpath = new \DOMXPath($dom); + $keys = ['p', 'q', 'g', 'y', 'j', 'seed', 'pgencounter']; + foreach ($keys as $key) { + // $dom->getElementsByTagName($key) is case-sensitive + $temp = $xpath->query("//*[translate(local-name(), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz')='$key']"); + if (!$temp->length) { + continue; + } + $value = new BigInteger(Strings::base64_decode($temp->item(0)->nodeValue), 256); + switch ($key) { + case 'p': // a prime modulus meeting the [DSS] requirements + // Parameters P, Q, and G can be public and common to a group of users. They might be known + // from application context. As such, they are optional but P and Q must either both appear + // or both be absent + $components['p'] = $value; + break; + case 'q': // an integer in the range 2**159 < Q < 2**160 which is a prime divisor of P-1 + $components['q'] = $value; + break; + case 'g': // an integer with certain properties with respect to P and Q + $components['g'] = $value; + break; + case 'y': // G**X mod P (where X is part of the private key and not made public) + $components['y'] = $value; + // the remaining options do not do anything + case 'j': // (P - 1) / Q + // Parameter J is available for inclusion solely for efficiency as it is calculatable from + // P and Q + case 'seed': // a DSA prime generation seed + // Parameters seed and pgenCounter are used in the DSA prime number generation algorithm + // specified in [DSS]. As such, they are optional but must either both be present or both + // be absent + case 'pgencounter': // a DSA prime generation counter + } + } + + libxml_use_internal_errors($use_errors); + + if (!isset($components['y'])) { + throw new \UnexpectedValueException('Key is missing y component'); + } + + switch (true) { + case !isset($components['p']): + case !isset($components['q']): + case !isset($components['g']): + return ['y' => $components['y']]; + } + + return $components; + } + + /** + * Convert a public key to the appropriate format + * + * See https://www.w3.org/TR/xmldsig-core/#sec-DSAKeyValue + * + * @param \phpseclib3\Math\BigInteger $p + * @param \phpseclib3\Math\BigInteger $q + * @param \phpseclib3\Math\BigInteger $g + * @param \phpseclib3\Math\BigInteger $y + * @return string + */ + public static function savePublicKey(BigInteger $p, BigInteger $q, BigInteger $g, BigInteger $y) + { + return "\r\n" . + '

' . Strings::base64_encode($p->toBytes()) . "

\r\n" . + ' ' . Strings::base64_encode($q->toBytes()) . "\r\n" . + ' ' . Strings::base64_encode($g->toBytes()) . "\r\n" . + ' ' . Strings::base64_encode($y->toBytes()) . "\r\n" . + '
'; + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Signature/ASN1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Signature/ASN1.php new file mode 100644 index 00000000..df52beed --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Signature/ASN1.php @@ -0,0 +1,62 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\Crypt\DSA\Formats\Signature; + +use phpseclib3\File\ASN1 as Encoder; +use phpseclib3\File\ASN1\Maps; +use phpseclib3\Math\BigInteger; + +/** + * ASN1 Signature Handler + * + * @author Jim Wigginton + */ +abstract class ASN1 +{ + /** + * Loads a signature + * + * @param string $sig + * @return array|bool + */ + public static function load($sig) + { + if (!is_string($sig)) { + return false; + } + + $decoded = Encoder::decodeBER($sig); + if (empty($decoded)) { + return false; + } + $components = Encoder::asn1map($decoded[0], Maps\DssSigValue::MAP); + + return $components; + } + + /** + * Returns a signature in the appropriate format + * + * @param \phpseclib3\Math\BigInteger $r + * @param \phpseclib3\Math\BigInteger $s + * @return string + */ + public static function save(BigInteger $r, BigInteger $s) + { + return Encoder::encodeDER(compact('r', 's'), Maps\DssSigValue::MAP); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Signature/Raw.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Signature/Raw.php new file mode 100644 index 00000000..2657a2a8 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Signature/Raw.php @@ -0,0 +1,25 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\Crypt\DSA\Formats\Signature; + +use phpseclib3\Crypt\Common\Formats\Signature\Raw as Progenitor; + +/** + * Raw DSA Signature Handler + * + * @author Jim Wigginton + */ +abstract class Raw extends Progenitor +{ +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Signature/SSH2.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Signature/SSH2.php new file mode 100644 index 00000000..dbfceabb --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Signature/SSH2.php @@ -0,0 +1,74 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\Crypt\DSA\Formats\Signature; + +use phpseclib3\Common\Functions\Strings; +use phpseclib3\Math\BigInteger; + +/** + * SSH2 Signature Handler + * + * @author Jim Wigginton + */ +abstract class SSH2 +{ + /** + * Loads a signature + * + * @param string $sig + * @return mixed + */ + public static function load($sig) + { + if (!is_string($sig)) { + return false; + } + + $result = Strings::unpackSSH2('ss', $sig); + if ($result === false) { + return false; + } + list($type, $blob) = $result; + if ($type != 'ssh-dss' || strlen($blob) != 40) { + return false; + } + + return [ + 'r' => new BigInteger(substr($blob, 0, 20), 256), + 's' => new BigInteger(substr($blob, 20), 256) + ]; + } + + /** + * Returns a signature in the appropriate format + * + * @param \phpseclib3\Math\BigInteger $r + * @param \phpseclib3\Math\BigInteger $s + * @return string + */ + public static function save(BigInteger $r, BigInteger $s) + { + if ($r->getLength() > 160 || $s->getLength() > 160) { + return false; + } + return Strings::packSSH2( + 'ss', + 'ssh-dss', + str_pad($r->toBytes(), 20, "\0", STR_PAD_LEFT) . + str_pad($s->toBytes(), 20, "\0", STR_PAD_LEFT) + ); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/Parameters.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/Parameters.php new file mode 100644 index 00000000..6bcb152d --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/Parameters.php @@ -0,0 +1,36 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\Crypt\DSA; + +use phpseclib3\Crypt\DSA; + +/** + * DSA Parameters + * + * @author Jim Wigginton + */ +class Parameters extends DSA +{ + /** + * Returns the parameters + * + * @param string $type + * @param array $options optional + * @return string + */ + public function toString($type = 'PKCS1', array $options = []) + { + $type = self::validatePlugin('Keys', 'PKCS1', 'saveParameters'); + + return $type::saveParameters($this->p, $this->q, $this->g, $options); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/PrivateKey.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/PrivateKey.php new file mode 100644 index 00000000..7039941b --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/PrivateKey.php @@ -0,0 +1,152 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\Crypt\DSA; + +use phpseclib3\Crypt\Common; +use phpseclib3\Crypt\DSA; +use phpseclib3\Crypt\DSA\Formats\Signature\ASN1 as ASN1Signature; +use phpseclib3\Math\BigInteger; + +/** + * DSA Private Key + * + * @author Jim Wigginton + */ +class PrivateKey extends DSA implements Common\PrivateKey +{ + use Common\Traits\PasswordProtected; + + /** + * DSA secret exponent x + * + * @var \phpseclib3\Math\BigInteger + */ + protected $x; + + /** + * Returns the public key + * + * If you do "openssl rsa -in private.rsa -pubout -outform PEM" you get a PKCS8 formatted key + * that contains a publicKeyAlgorithm AlgorithmIdentifier and a publicKey BIT STRING. + * An AlgorithmIdentifier contains an OID and a parameters field. With RSA public keys this + * parameters field is NULL. With DSA PKCS8 public keys it is not - it contains the p, q and g + * variables. The publicKey BIT STRING contains, simply, the y variable. This can be verified + * by getting a DSA PKCS8 public key: + * + * "openssl dsa -in private.dsa -pubout -outform PEM" + * + * ie. just swap out rsa with dsa in the rsa command above. + * + * A PKCS1 public key corresponds to the publicKey portion of the PKCS8 key. In the case of RSA + * the publicKey portion /is/ the key. In the case of DSA it is not. You cannot verify a signature + * without the parameters and the PKCS1 DSA public key format does not include the parameters. + * + * @see self::getPrivateKey() + * @return mixed + */ + public function getPublicKey() + { + $type = self::validatePlugin('Keys', 'PKCS8', 'savePublicKey'); + + if (!isset($this->y)) { + $this->y = $this->g->powMod($this->x, $this->p); + } + + $key = $type::savePublicKey($this->p, $this->q, $this->g, $this->y); + + return DSA::loadFormat('PKCS8', $key) + ->withHash($this->hash->getHash()) + ->withSignatureFormat($this->shortFormat); + } + + /** + * Create a signature + * + * @see self::verify() + * @param string $message + * @return mixed + */ + public function sign($message) + { + $format = $this->sigFormat; + + if (self::$engines['OpenSSL'] && in_array($this->hash->getHash(), openssl_get_md_methods())) { + $signature = ''; + $result = openssl_sign($message, $signature, $this->toString('PKCS8'), $this->hash->getHash()); + + if ($result) { + if ($this->shortFormat == 'ASN1') { + return $signature; + } + + extract(ASN1Signature::load($signature)); + + return $format::save($r, $s); + } + } + + $h = $this->hash->hash($message); + $h = $this->bits2int($h); + + while (true) { + $k = BigInteger::randomRange(self::$one, $this->q->subtract(self::$one)); + $r = $this->g->powMod($k, $this->p); + list(, $r) = $r->divide($this->q); + if ($r->equals(self::$zero)) { + continue; + } + $kinv = $k->modInverse($this->q); + $temp = $h->add($this->x->multiply($r)); + $temp = $kinv->multiply($temp); + list(, $s) = $temp->divide($this->q); + if (!$s->equals(self::$zero)) { + break; + } + } + + // the following is an RFC6979 compliant implementation of deterministic DSA + // it's unused because it's mainly intended for use when a good CSPRNG isn't + // available. if phpseclib's CSPRNG isn't good then even key generation is + // suspect + /* + $h1 = $this->hash->hash($message); + $k = $this->computek($h1); + $r = $this->g->powMod($k, $this->p); + list(, $r) = $r->divide($this->q); + $kinv = $k->modInverse($this->q); + $h1 = $this->bits2int($h1); + $temp = $h1->add($this->x->multiply($r)); + $temp = $kinv->multiply($temp); + list(, $s) = $temp->divide($this->q); + */ + + return $format::save($r, $s); + } + + /** + * Returns the private key + * + * @param string $type + * @param array $options optional + * @return string + */ + public function toString($type, array $options = []) + { + $type = self::validatePlugin('Keys', $type, 'savePrivateKey'); + + if (!isset($this->y)) { + $this->y = $this->g->powMod($this->x, $this->p); + } + + return $type::savePrivateKey($this->p, $this->q, $this->g, $this->y, $this->x, $this->password, $options); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/PublicKey.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/PublicKey.php new file mode 100644 index 00000000..7e00e24a --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/PublicKey.php @@ -0,0 +1,86 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\Crypt\DSA; + +use phpseclib3\Crypt\Common; +use phpseclib3\Crypt\DSA; +use phpseclib3\Crypt\DSA\Formats\Signature\ASN1 as ASN1Signature; + +/** + * DSA Public Key + * + * @author Jim Wigginton + */ +class PublicKey extends DSA implements Common\PublicKey +{ + use Common\Traits\Fingerprint; + + /** + * Verify a signature + * + * @see self::verify() + * @param string $message + * @param string $signature + * @return mixed + */ + public function verify($message, $signature) + { + $format = $this->sigFormat; + + $params = $format::load($signature); + if ($params === false || count($params) != 2) { + return false; + } + extract($params); + + if (self::$engines['OpenSSL'] && in_array($this->hash->getHash(), openssl_get_md_methods())) { + $sig = $format != 'ASN1' ? ASN1Signature::save($r, $s) : $signature; + + $result = openssl_verify($message, $sig, $this->toString('PKCS8'), $this->hash->getHash()); + + if ($result != -1) { + return (bool) $result; + } + } + + $q_1 = $this->q->subtract(self::$one); + if (!$r->between(self::$one, $q_1) || !$s->between(self::$one, $q_1)) { + return false; + } + + $w = $s->modInverse($this->q); + $h = $this->hash->hash($message); + $h = $this->bits2int($h); + list(, $u1) = $h->multiply($w)->divide($this->q); + list(, $u2) = $r->multiply($w)->divide($this->q); + $v1 = $this->g->powMod($u1, $this->p); + $v2 = $this->y->powMod($u2, $this->p); + list(, $v) = $v1->multiply($v2)->divide($this->p); + list(, $v) = $v->divide($this->q); + + return $v->equals($r); + } + + /** + * Returns the public key + * + * @param string $type + * @param array $options optional + * @return string + */ + public function toString($type, array $options = []) + { + $type = self::validatePlugin('Keys', $type, 'savePublicKey'); + + return $type::savePublicKey($this->p, $this->q, $this->g, $this->y, $options); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC.php new file mode 100644 index 00000000..e61f0092 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC.php @@ -0,0 +1,475 @@ + + * getPublicKey(); + * + * $plaintext = 'terrafrost'; + * + * $signature = $private->sign($plaintext); + * + * echo $public->verify($plaintext, $signature) ? 'verified' : 'unverified'; + * ?> + * + * + * @author Jim Wigginton + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\Crypt; + +use phpseclib3\Crypt\Common\AsymmetricKey; +use phpseclib3\Crypt\EC\BaseCurves\Montgomery as MontgomeryCurve; +use phpseclib3\Crypt\EC\BaseCurves\TwistedEdwards as TwistedEdwardsCurve; +use phpseclib3\Crypt\EC\Curves\Curve25519; +use phpseclib3\Crypt\EC\Curves\Ed25519; +use phpseclib3\Crypt\EC\Curves\Ed448; +use phpseclib3\Crypt\EC\Formats\Keys\PKCS1; +use phpseclib3\Crypt\EC\Parameters; +use phpseclib3\Crypt\EC\PrivateKey; +use phpseclib3\Crypt\EC\PublicKey; +use phpseclib3\Exception\UnsupportedAlgorithmException; +use phpseclib3\Exception\UnsupportedCurveException; +use phpseclib3\Exception\UnsupportedOperationException; +use phpseclib3\File\ASN1; +use phpseclib3\File\ASN1\Maps\ECParameters; +use phpseclib3\Math\BigInteger; + +/** + * Pure-PHP implementation of EC. + * + * @author Jim Wigginton + */ +abstract class EC extends AsymmetricKey +{ + /** + * Algorithm Name + * + * @var string + */ + const ALGORITHM = 'EC'; + + /** + * Public Key QA + * + * @var object[] + */ + protected $QA; + + /** + * Curve + * + * @var \phpseclib3\Crypt\EC\BaseCurves\Base + */ + protected $curve; + + /** + * Signature Format + * + * @var string + */ + protected $format; + + /** + * Signature Format (Short) + * + * @var string + */ + protected $shortFormat; + + /** + * Curve Name + * + * @var string + */ + private $curveName; + + /** + * Curve Order + * + * Used for deterministic ECDSA + * + * @var \phpseclib3\Math\BigInteger + */ + protected $q; + + /** + * Alias for the private key + * + * Used for deterministic ECDSA. AsymmetricKey expects $x. I don't like x because + * with x you have x * the base point yielding an (x, y)-coordinate that is the + * public key. But the x is different depending on which side of the equal sign + * you're on. It's less ambiguous if you do dA * base point = (x, y)-coordinate. + * + * @var \phpseclib3\Math\BigInteger + */ + protected $x; + + /** + * Context + * + * @var string + */ + protected $context; + + /** + * Signature Format + * + * @var string + */ + protected $sigFormat; + + /** + * Create public / private key pair. + * + * @param string $curve + * @return \phpseclib3\Crypt\EC\PrivateKey + */ + public static function createKey($curve) + { + self::initialize_static_variables(); + + if (!isset(self::$engines['PHP'])) { + self::useBestEngine(); + } + + $curve = strtolower($curve); + if (self::$engines['libsodium'] && $curve == 'ed25519' && function_exists('sodium_crypto_sign_keypair')) { + $kp = sodium_crypto_sign_keypair(); + + $privatekey = EC::loadFormat('libsodium', sodium_crypto_sign_secretkey($kp)); + //$publickey = EC::loadFormat('libsodium', sodium_crypto_sign_publickey($kp)); + + $privatekey->curveName = 'Ed25519'; + //$publickey->curveName = $curve; + + return $privatekey; + } + + $privatekey = new PrivateKey(); + + $curveName = $curve; + if (preg_match('#(?:^curve|^ed)\d+$#', $curveName)) { + $curveName = ucfirst($curveName); + } elseif (substr($curveName, 0, 10) == 'brainpoolp') { + $curveName = 'brainpoolP' . substr($curveName, 10); + } + $curve = '\phpseclib3\Crypt\EC\Curves\\' . $curveName; + + if (!class_exists($curve)) { + throw new UnsupportedCurveException('Named Curve of ' . $curveName . ' is not supported'); + } + + $reflect = new \ReflectionClass($curve); + $curveName = $reflect->isFinal() ? + $reflect->getParentClass()->getShortName() : + $reflect->getShortName(); + + $curve = new $curve(); + if ($curve instanceof TwistedEdwardsCurve) { + $arr = $curve->extractSecret(Random::string($curve instanceof Ed448 ? 57 : 32)); + $privatekey->dA = $dA = $arr['dA']; + $privatekey->secret = $arr['secret']; + } else { + $privatekey->dA = $dA = $curve->createRandomMultiplier(); + } + if ($curve instanceof Curve25519 && self::$engines['libsodium']) { + //$r = pack('H*', '0900000000000000000000000000000000000000000000000000000000000000'); + //$QA = sodium_crypto_scalarmult($dA->toBytes(), $r); + $QA = sodium_crypto_box_publickey_from_secretkey($dA->toBytes()); + $privatekey->QA = [$curve->convertInteger(new BigInteger(strrev($QA), 256))]; + } else { + $privatekey->QA = $curve->multiplyPoint($curve->getBasePoint(), $dA); + } + $privatekey->curve = $curve; + + //$publickey = clone $privatekey; + //unset($publickey->dA); + //unset($publickey->x); + + $privatekey->curveName = $curveName; + //$publickey->curveName = $curveName; + + if ($privatekey->curve instanceof TwistedEdwardsCurve) { + return $privatekey->withHash($curve::HASH); + } + + return $privatekey; + } + + /** + * OnLoad Handler + * + * @return bool + */ + protected static function onLoad(array $components) + { + if (!isset(self::$engines['PHP'])) { + self::useBestEngine(); + } + + if (!isset($components['dA']) && !isset($components['QA'])) { + $new = new Parameters(); + $new->curve = $components['curve']; + return $new; + } + + $new = isset($components['dA']) ? + new PrivateKey() : + new PublicKey(); + $new->curve = $components['curve']; + $new->QA = $components['QA']; + + if (isset($components['dA'])) { + $new->dA = $components['dA']; + $new->secret = $components['secret']; + } + + if ($new->curve instanceof TwistedEdwardsCurve) { + return $new->withHash($components['curve']::HASH); + } + + return $new; + } + + /** + * Constructor + * + * PublicKey and PrivateKey objects can only be created from abstract RSA class + */ + protected function __construct() + { + $this->sigFormat = self::validatePlugin('Signature', 'ASN1'); + $this->shortFormat = 'ASN1'; + + parent::__construct(); + } + + /** + * Returns the curve + * + * Returns a string if it's a named curve, an array if not + * + * @return string|array + */ + public function getCurve() + { + if ($this->curveName) { + return $this->curveName; + } + + if ($this->curve instanceof MontgomeryCurve) { + $this->curveName = $this->curve instanceof Curve25519 ? 'Curve25519' : 'Curve448'; + return $this->curveName; + } + + if ($this->curve instanceof TwistedEdwardsCurve) { + $this->curveName = $this->curve instanceof Ed25519 ? 'Ed25519' : 'Ed448'; + return $this->curveName; + } + + $params = $this->getParameters()->toString('PKCS8', ['namedCurve' => true]); + $decoded = ASN1::extractBER($params); + $decoded = ASN1::decodeBER($decoded); + $decoded = ASN1::asn1map($decoded[0], ECParameters::MAP); + if (isset($decoded['namedCurve'])) { + $this->curveName = $decoded['namedCurve']; + return $decoded['namedCurve']; + } + + if (!$namedCurves) { + PKCS1::useSpecifiedCurve(); + } + + return $decoded; + } + + /** + * Returns the key size + * + * Quoting https://tools.ietf.org/html/rfc5656#section-2, + * + * "The size of a set of elliptic curve domain parameters on a prime + * curve is defined as the number of bits in the binary representation + * of the field order, commonly denoted by p. Size on a + * characteristic-2 curve is defined as the number of bits in the binary + * representation of the field, commonly denoted by m. A set of + * elliptic curve domain parameters defines a group of order n generated + * by a base point P" + * + * @return int + */ + public function getLength() + { + return $this->curve->getLength(); + } + + /** + * Returns the current engine being used + * + * @see self::useInternalEngine() + * @see self::useBestEngine() + * @return string + */ + public function getEngine() + { + if (!isset(self::$engines['PHP'])) { + self::useBestEngine(); + } + if ($this->curve instanceof TwistedEdwardsCurve) { + return $this->curve instanceof Ed25519 && self::$engines['libsodium'] && !isset($this->context) ? + 'libsodium' : 'PHP'; + } + + return self::$engines['OpenSSL'] && in_array($this->hash->getHash(), openssl_get_md_methods()) ? + 'OpenSSL' : 'PHP'; + } + + /** + * Returns the public key coordinates as a string + * + * Used by ECDH + * + * @return string + */ + public function getEncodedCoordinates() + { + if ($this->curve instanceof MontgomeryCurve) { + return strrev($this->QA[0]->toBytes(true)); + } + if ($this->curve instanceof TwistedEdwardsCurve) { + return $this->curve->encodePoint($this->QA); + } + return "\4" . $this->QA[0]->toBytes(true) . $this->QA[1]->toBytes(true); + } + + /** + * Returns the parameters + * + * @see self::getPublicKey() + * @param string $type optional + * @return mixed + */ + public function getParameters($type = 'PKCS1') + { + $type = self::validatePlugin('Keys', $type, 'saveParameters'); + + $key = $type::saveParameters($this->curve); + + return EC::load($key, 'PKCS1') + ->withHash($this->hash->getHash()) + ->withSignatureFormat($this->shortFormat); + } + + /** + * Determines the signature padding mode + * + * Valid values are: ASN1, SSH2, Raw + * + * @param string $format + */ + public function withSignatureFormat($format) + { + if ($this->curve instanceof MontgomeryCurve) { + throw new UnsupportedOperationException('Montgomery Curves cannot be used to create signatures'); + } + + $new = clone $this; + $new->shortFormat = $format; + $new->sigFormat = self::validatePlugin('Signature', $format); + return $new; + } + + /** + * Returns the signature format currently being used + * + */ + public function getSignatureFormat() + { + return $this->shortFormat; + } + + /** + * Sets the context + * + * Used by Ed25519 / Ed448. + * + * @see self::sign() + * @see self::verify() + * @param string $context optional + */ + public function withContext($context = null) + { + if (!$this->curve instanceof TwistedEdwardsCurve) { + throw new UnsupportedCurveException('Only Ed25519 and Ed448 support contexts'); + } + + $new = clone $this; + if (!isset($context)) { + $new->context = null; + return $new; + } + if (!is_string($context)) { + throw new \InvalidArgumentException('setContext expects a string'); + } + if (strlen($context) > 255) { + throw new \LengthException('The context is supposed to be, at most, 255 bytes long'); + } + $new->context = $context; + return $new; + } + + /** + * Returns the signature format currently being used + * + */ + public function getContext() + { + return $this->context; + } + + /** + * Determines which hashing function should be used + * + * @param string $hash + */ + public function withHash($hash) + { + if ($this->curve instanceof MontgomeryCurve) { + throw new UnsupportedOperationException('Montgomery Curves cannot be used to create signatures'); + } + if ($this->curve instanceof Ed25519 && $hash != 'sha512') { + throw new UnsupportedAlgorithmException('Ed25519 only supports sha512 as a hash'); + } + if ($this->curve instanceof Ed448 && $hash != 'shake256-912') { + throw new UnsupportedAlgorithmException('Ed448 only supports shake256 with a length of 114 bytes'); + } + + return parent::withHash($hash); + } + + /** + * __toString() magic method + * + * @return string + */ + public function __toString() + { + if ($this->curve instanceof MontgomeryCurve) { + return ''; + } + + return parent::__toString(); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/BaseCurves/Base.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/BaseCurves/Base.php new file mode 100644 index 00000000..dbc914be --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/BaseCurves/Base.php @@ -0,0 +1,218 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Crypt\EC\BaseCurves; + +use phpseclib3\Math\BigInteger; + +/** + * Base + * + * @author Jim Wigginton + */ +abstract class Base +{ + /** + * The Order + * + * @var BigInteger + */ + protected $order; + + /** + * Finite Field Integer factory + * + * @var \phpseclib3\Math\FiniteField\Integer + */ + protected $factory; + + /** + * Returns a random integer + * + * @return object + */ + public function randomInteger() + { + return $this->factory->randomInteger(); + } + + /** + * Converts a BigInteger to a \phpseclib3\Math\FiniteField\Integer integer + * + * @return object + */ + public function convertInteger(BigInteger $x) + { + return $this->factory->newInteger($x); + } + + /** + * Returns the length, in bytes, of the modulo + * + * @return integer + */ + public function getLengthInBytes() + { + return $this->factory->getLengthInBytes(); + } + + /** + * Returns the length, in bits, of the modulo + * + * @return integer + */ + public function getLength() + { + return $this->factory->getLength(); + } + + /** + * Multiply a point on the curve by a scalar + * + * Uses the montgomery ladder technique as described here: + * + * https://en.wikipedia.org/wiki/Elliptic_curve_point_multiplication#Montgomery_ladder + * https://github.com/phpecc/phpecc/issues/16#issuecomment-59176772 + * + * @return array + */ + public function multiplyPoint(array $p, BigInteger $d) + { + $alreadyInternal = isset($p[2]); + $r = $alreadyInternal ? + [[], $p] : + [[], $this->convertToInternal($p)]; + + $d = $d->toBits(); + for ($i = 0; $i < strlen($d); $i++) { + $d_i = (int) $d[$i]; + $r[1 - $d_i] = $this->addPoint($r[0], $r[1]); + $r[$d_i] = $this->doublePoint($r[$d_i]); + } + + return $alreadyInternal ? $r[0] : $this->convertToAffine($r[0]); + } + + /** + * Creates a random scalar multiplier + * + * @return BigInteger + */ + public function createRandomMultiplier() + { + static $one; + if (!isset($one)) { + $one = new BigInteger(1); + } + + return BigInteger::randomRange($one, $this->order->subtract($one)); + } + + /** + * Performs range check + */ + public function rangeCheck(BigInteger $x) + { + static $zero; + if (!isset($zero)) { + $zero = new BigInteger(); + } + + if (!isset($this->order)) { + throw new \RuntimeException('setOrder needs to be called before this method'); + } + if ($x->compare($this->order) > 0 || $x->compare($zero) <= 0) { + throw new \RangeException('x must be between 1 and the order of the curve'); + } + } + + /** + * Sets the Order + */ + public function setOrder(BigInteger $order) + { + $this->order = $order; + } + + /** + * Returns the Order + * + * @return \phpseclib3\Math\BigInteger + */ + public function getOrder() + { + return $this->order; + } + + /** + * Use a custom defined modular reduction function + * + * @return object + */ + public function setReduction(callable $func) + { + $this->factory->setReduction($func); + } + + /** + * Returns the affine point + * + * @return object[] + */ + public function convertToAffine(array $p) + { + return $p; + } + + /** + * Converts an affine point to a jacobian coordinate + * + * @return object[] + */ + public function convertToInternal(array $p) + { + return $p; + } + + /** + * Negates a point + * + * @return object[] + */ + public function negatePoint(array $p) + { + $temp = [ + $p[0], + $p[1]->negate() + ]; + if (isset($p[2])) { + $temp[] = $p[2]; + } + return $temp; + } + + /** + * Multiply and Add Points + * + * @return int[] + */ + public function multiplyAddPoints(array $points, array $scalars) + { + $p1 = $this->convertToInternal($points[0]); + $p2 = $this->convertToInternal($points[1]); + $p1 = $this->multiplyPoint($p1, $scalars[0]); + $p2 = $this->multiplyPoint($p2, $scalars[1]); + $r = $this->addPoint($p1, $p2); + return $this->convertToAffine($r); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/BaseCurves/Binary.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/BaseCurves/Binary.php new file mode 100644 index 00000000..4fc6c70c --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/BaseCurves/Binary.php @@ -0,0 +1,373 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Crypt\EC\BaseCurves; + +use phpseclib3\Math\BigInteger; +use phpseclib3\Math\BinaryField; +use phpseclib3\Math\BinaryField\Integer as BinaryInteger; + +/** + * Curves over y^2 + x*y = x^3 + a*x^2 + b + * + * @author Jim Wigginton + */ +class Binary extends Base +{ + /** + * Binary Field Integer factory + * + * @var \phpseclib3\Math\BinaryField + */ + protected $factory; + + /** + * Cofficient for x^1 + * + * @var object + */ + protected $a; + + /** + * Cofficient for x^0 + * + * @var object + */ + protected $b; + + /** + * Base Point + * + * @var object + */ + protected $p; + + /** + * The number one over the specified finite field + * + * @var object + */ + protected $one; + + /** + * The modulo + * + * @var BigInteger + */ + protected $modulo; + + /** + * The Order + * + * @var BigInteger + */ + protected $order; + + /** + * Sets the modulo + */ + public function setModulo(...$modulo) + { + $this->modulo = $modulo; + $this->factory = new BinaryField(...$modulo); + + $this->one = $this->factory->newInteger("\1"); + } + + /** + * Set coefficients a and b + * + * @param string $a + * @param string $b + */ + public function setCoefficients($a, $b) + { + if (!isset($this->factory)) { + throw new \RuntimeException('setModulo needs to be called before this method'); + } + $this->a = $this->factory->newInteger(pack('H*', $a)); + $this->b = $this->factory->newInteger(pack('H*', $b)); + } + + /** + * Set x and y coordinates for the base point + * + * @param string|BinaryInteger $x + * @param string|BinaryInteger $y + */ + public function setBasePoint($x, $y) + { + switch (true) { + case !is_string($x) && !$x instanceof BinaryInteger: + throw new \UnexpectedValueException('Argument 1 passed to Binary::setBasePoint() must be a string or an instance of BinaryField\Integer'); + case !is_string($y) && !$y instanceof BinaryInteger: + throw new \UnexpectedValueException('Argument 2 passed to Binary::setBasePoint() must be a string or an instance of BinaryField\Integer'); + } + if (!isset($this->factory)) { + throw new \RuntimeException('setModulo needs to be called before this method'); + } + $this->p = [ + is_string($x) ? $this->factory->newInteger(pack('H*', $x)) : $x, + is_string($y) ? $this->factory->newInteger(pack('H*', $y)) : $y + ]; + } + + /** + * Retrieve the base point as an array + * + * @return array + */ + public function getBasePoint() + { + if (!isset($this->factory)) { + throw new \RuntimeException('setModulo needs to be called before this method'); + } + /* + if (!isset($this->p)) { + throw new \RuntimeException('setBasePoint needs to be called before this method'); + } + */ + return $this->p; + } + + /** + * Adds two points on the curve + * + * @return FiniteField[] + */ + public function addPoint(array $p, array $q) + { + if (!isset($this->factory)) { + throw new \RuntimeException('setModulo needs to be called before this method'); + } + + if (!count($p) || !count($q)) { + if (count($q)) { + return $q; + } + if (count($p)) { + return $p; + } + return []; + } + + if (!isset($p[2]) || !isset($q[2])) { + throw new \RuntimeException('Affine coordinates need to be manually converted to "Jacobi" coordinates or vice versa'); + } + + if ($p[0]->equals($q[0])) { + return !$p[1]->equals($q[1]) ? [] : $this->doublePoint($p); + } + + // formulas from http://hyperelliptic.org/EFD/g12o/auto-shortw-jacobian.html + + list($x1, $y1, $z1) = $p; + list($x2, $y2, $z2) = $q; + + $o1 = $z1->multiply($z1); + $b = $x2->multiply($o1); + + if ($z2->equals($this->one)) { + $d = $y2->multiply($o1)->multiply($z1); + $e = $x1->add($b); + $f = $y1->add($d); + $z3 = $e->multiply($z1); + $h = $f->multiply($x2)->add($z3->multiply($y2)); + $i = $f->add($z3); + $g = $z3->multiply($z3); + $p1 = $this->a->multiply($g); + $p2 = $f->multiply($i); + $p3 = $e->multiply($e)->multiply($e); + $x3 = $p1->add($p2)->add($p3); + $y3 = $i->multiply($x3)->add($g->multiply($h)); + + return [$x3, $y3, $z3]; + } + + $o2 = $z2->multiply($z2); + $a = $x1->multiply($o2); + $c = $y1->multiply($o2)->multiply($z2); + $d = $y2->multiply($o1)->multiply($z1); + $e = $a->add($b); + $f = $c->add($d); + $g = $e->multiply($z1); + $h = $f->multiply($x2)->add($g->multiply($y2)); + $z3 = $g->multiply($z2); + $i = $f->add($z3); + $p1 = $this->a->multiply($z3->multiply($z3)); + $p2 = $f->multiply($i); + $p3 = $e->multiply($e)->multiply($e); + $x3 = $p1->add($p2)->add($p3); + $y3 = $i->multiply($x3)->add($g->multiply($g)->multiply($h)); + + return [$x3, $y3, $z3]; + } + + /** + * Doubles a point on a curve + * + * @return FiniteField[] + */ + public function doublePoint(array $p) + { + if (!isset($this->factory)) { + throw new \RuntimeException('setModulo needs to be called before this method'); + } + + if (!count($p)) { + return []; + } + + if (!isset($p[2])) { + throw new \RuntimeException('Affine coordinates need to be manually converted to "Jacobi" coordinates or vice versa'); + } + + // formulas from http://hyperelliptic.org/EFD/g12o/auto-shortw-jacobian.html + + list($x1, $y1, $z1) = $p; + + $a = $x1->multiply($x1); + $b = $a->multiply($a); + + if ($z1->equals($this->one)) { + $x3 = $b->add($this->b); + $z3 = clone $x1; + $p1 = $a->add($y1)->add($z3)->multiply($this->b); + $p2 = $a->add($y1)->multiply($b); + $y3 = $p1->add($p2); + + return [$x3, $y3, $z3]; + } + + $c = $z1->multiply($z1); + $d = $c->multiply($c); + $x3 = $b->add($this->b->multiply($d->multiply($d))); + $z3 = $x1->multiply($c); + $p1 = $b->multiply($z3); + $p2 = $a->add($y1->multiply($z1))->add($z3)->multiply($x3); + $y3 = $p1->add($p2); + + return [$x3, $y3, $z3]; + } + + /** + * Returns the X coordinate and the derived Y coordinate + * + * Not supported because it is covered by patents. + * Quoting https://www.openssl.org/docs/man1.1.0/apps/ecparam.html , + * + * "Due to patent issues the compressed option is disabled by default for binary curves + * and can be enabled by defining the preprocessor macro OPENSSL_EC_BIN_PT_COMP at + * compile time." + * + * @return array + */ + public function derivePoint($m) + { + throw new \RuntimeException('Point compression on binary finite field elliptic curves is not supported'); + } + + /** + * Tests whether or not the x / y values satisfy the equation + * + * @return boolean + */ + public function verifyPoint(array $p) + { + list($x, $y) = $p; + $lhs = $y->multiply($y); + $lhs = $lhs->add($x->multiply($y)); + $x2 = $x->multiply($x); + $x3 = $x2->multiply($x); + $rhs = $x3->add($this->a->multiply($x2))->add($this->b); + + return $lhs->equals($rhs); + } + + /** + * Returns the modulo + * + * @return \phpseclib3\Math\BigInteger + */ + public function getModulo() + { + return $this->modulo; + } + + /** + * Returns the a coefficient + * + * @return \phpseclib3\Math\PrimeField\Integer + */ + public function getA() + { + return $this->a; + } + + /** + * Returns the a coefficient + * + * @return \phpseclib3\Math\PrimeField\Integer + */ + public function getB() + { + return $this->b; + } + + /** + * Returns the affine point + * + * A Jacobian Coordinate is of the form (x, y, z). + * To convert a Jacobian Coordinate to an Affine Point + * you do (x / z^2, y / z^3) + * + * @return \phpseclib3\Math\PrimeField\Integer[] + */ + public function convertToAffine(array $p) + { + if (!isset($p[2])) { + return $p; + } + list($x, $y, $z) = $p; + $z = $this->one->divide($z); + $z2 = $z->multiply($z); + return [ + $x->multiply($z2), + $y->multiply($z2)->multiply($z) + ]; + } + + /** + * Converts an affine point to a jacobian coordinate + * + * @return \phpseclib3\Math\PrimeField\Integer[] + */ + public function convertToInternal(array $p) + { + if (isset($p[2])) { + return $p; + } + + $p[2] = clone $this->one; + $p['fresh'] = true; + return $p; + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/BaseCurves/KoblitzPrime.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/BaseCurves/KoblitzPrime.php new file mode 100644 index 00000000..d8492ebc --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/BaseCurves/KoblitzPrime.php @@ -0,0 +1,335 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Crypt\EC\BaseCurves; + +use phpseclib3\Math\BigInteger; +use phpseclib3\Math\PrimeField; + +/** + * Curves over y^2 = x^3 + b + * + * @author Jim Wigginton + */ +class KoblitzPrime extends Prime +{ + /** + * Basis + * + * @var list + */ + protected $basis; + + /** + * Beta + * + * @var PrimeField\Integer + */ + protected $beta; + + // don't overwrite setCoefficients() with one that only accepts one parameter so that + // one might be able to switch between KoblitzPrime and Prime more easily (for benchmarking + // purposes). + + /** + * Multiply and Add Points + * + * Uses a efficiently computable endomorphism to achieve a slight speedup + * + * Adapted from: + * https://github.com/indutny/elliptic/blob/725bd91/lib/elliptic/curve/short.js#L219 + * + * @return int[] + */ + public function multiplyAddPoints(array $points, array $scalars) + { + static $zero, $one, $two; + if (!isset($two)) { + $two = new BigInteger(2); + $one = new BigInteger(1); + } + + if (!isset($this->beta)) { + // get roots + $inv = $this->one->divide($this->two)->negate(); + $s = $this->three->negate()->squareRoot()->multiply($inv); + $betas = [ + $inv->add($s), + $inv->subtract($s) + ]; + $this->beta = $betas[0]->compare($betas[1]) < 0 ? $betas[0] : $betas[1]; + //echo strtoupper($this->beta->toHex(true)) . "\n"; exit; + } + + if (!isset($this->basis)) { + $factory = new PrimeField($this->order); + $tempOne = $factory->newInteger($one); + $tempTwo = $factory->newInteger($two); + $tempThree = $factory->newInteger(new BigInteger(3)); + + $inv = $tempOne->divide($tempTwo)->negate(); + $s = $tempThree->negate()->squareRoot()->multiply($inv); + + $lambdas = [ + $inv->add($s), + $inv->subtract($s) + ]; + + $lhs = $this->multiplyPoint($this->p, $lambdas[0])[0]; + $rhs = $this->p[0]->multiply($this->beta); + $lambda = $lhs->equals($rhs) ? $lambdas[0] : $lambdas[1]; + + $this->basis = static::extendedGCD($lambda->toBigInteger(), $this->order); + ///* + foreach ($this->basis as $basis) { + echo strtoupper($basis['a']->toHex(true)) . "\n"; + echo strtoupper($basis['b']->toHex(true)) . "\n\n"; + } + exit; + //*/ + } + + $npoints = $nscalars = []; + for ($i = 0; $i < count($points); $i++) { + $p = $points[$i]; + $k = $scalars[$i]->toBigInteger(); + + // begin split + list($v1, $v2) = $this->basis; + + $c1 = $v2['b']->multiply($k); + list($c1, $r) = $c1->divide($this->order); + if ($this->order->compare($r->multiply($two)) <= 0) { + $c1 = $c1->add($one); + } + + $c2 = $v1['b']->negate()->multiply($k); + list($c2, $r) = $c2->divide($this->order); + if ($this->order->compare($r->multiply($two)) <= 0) { + $c2 = $c2->add($one); + } + + $p1 = $c1->multiply($v1['a']); + $p2 = $c2->multiply($v2['a']); + $q1 = $c1->multiply($v1['b']); + $q2 = $c2->multiply($v2['b']); + + $k1 = $k->subtract($p1)->subtract($p2); + $k2 = $q1->add($q2)->negate(); + // end split + + $beta = [ + $p[0]->multiply($this->beta), + $p[1], + clone $this->one + ]; + + if (isset($p['naf'])) { + $beta['naf'] = array_map(function ($p) { + return [ + $p[0]->multiply($this->beta), + $p[1], + clone $this->one + ]; + }, $p['naf']); + $beta['nafwidth'] = $p['nafwidth']; + } + + if ($k1->isNegative()) { + $k1 = $k1->negate(); + $p = $this->negatePoint($p); + } + + if ($k2->isNegative()) { + $k2 = $k2->negate(); + $beta = $this->negatePoint($beta); + } + + $pos = 2 * $i; + $npoints[$pos] = $p; + $nscalars[$pos] = $this->factory->newInteger($k1); + + $pos++; + $npoints[$pos] = $beta; + $nscalars[$pos] = $this->factory->newInteger($k2); + } + + return parent::multiplyAddPoints($npoints, $nscalars); + } + + /** + * Returns the numerator and denominator of the slope + * + * @return FiniteField[] + */ + protected function doublePointHelper(array $p) + { + $numerator = $this->three->multiply($p[0])->multiply($p[0]); + $denominator = $this->two->multiply($p[1]); + return [$numerator, $denominator]; + } + + /** + * Doubles a jacobian coordinate on the curve + * + * See http://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#doubling-dbl-2009-l + * + * @return FiniteField[] + */ + protected function jacobianDoublePoint(array $p) + { + list($x1, $y1, $z1) = $p; + $a = $x1->multiply($x1); + $b = $y1->multiply($y1); + $c = $b->multiply($b); + $d = $x1->add($b); + $d = $d->multiply($d)->subtract($a)->subtract($c)->multiply($this->two); + $e = $this->three->multiply($a); + $f = $e->multiply($e); + $x3 = $f->subtract($this->two->multiply($d)); + $y3 = $e->multiply($d->subtract($x3))->subtract( + $this->eight->multiply($c) + ); + $z3 = $this->two->multiply($y1)->multiply($z1); + return [$x3, $y3, $z3]; + } + + /** + * Doubles a "fresh" jacobian coordinate on the curve + * + * See http://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#doubling-mdbl-2007-bl + * + * @return FiniteField[] + */ + protected function jacobianDoublePointMixed(array $p) + { + list($x1, $y1) = $p; + $xx = $x1->multiply($x1); + $yy = $y1->multiply($y1); + $yyyy = $yy->multiply($yy); + $s = $x1->add($yy); + $s = $s->multiply($s)->subtract($xx)->subtract($yyyy)->multiply($this->two); + $m = $this->three->multiply($xx); + $t = $m->multiply($m)->subtract($this->two->multiply($s)); + $x3 = $t; + $y3 = $s->subtract($t); + $y3 = $m->multiply($y3)->subtract($this->eight->multiply($yyyy)); + $z3 = $this->two->multiply($y1); + return [$x3, $y3, $z3]; + } + + /** + * Tests whether or not the x / y values satisfy the equation + * + * @return boolean + */ + public function verifyPoint(array $p) + { + list($x, $y) = $p; + $lhs = $y->multiply($y); + $temp = $x->multiply($x)->multiply($x); + $rhs = $temp->add($this->b); + + return $lhs->equals($rhs); + } + + /** + * Calculates the parameters needed from the Euclidean algorithm as discussed at + * http://diamond.boisestate.edu/~liljanab/MATH308/GuideToECC.pdf#page=148 + * + * @param BigInteger $u + * @param BigInteger $v + * @return BigInteger[] + */ + protected static function extendedGCD(BigInteger $u, BigInteger $v) + { + $one = new BigInteger(1); + $zero = new BigInteger(); + + $a = clone $one; + $b = clone $zero; + $c = clone $zero; + $d = clone $one; + + $stop = $v->bitwise_rightShift($v->getLength() >> 1); + + $a1 = clone $zero; + $b1 = clone $zero; + $a2 = clone $zero; + $b2 = clone $zero; + + $postGreatestIndex = 0; + + while (!$v->equals($zero)) { + list($q) = $u->divide($v); + + $temp = $u; + $u = $v; + $v = $temp->subtract($v->multiply($q)); + + $temp = $a; + $a = $c; + $c = $temp->subtract($a->multiply($q)); + + $temp = $b; + $b = $d; + $d = $temp->subtract($b->multiply($q)); + + if ($v->compare($stop) > 0) { + $a0 = $v; + $b0 = $c; + } else { + $postGreatestIndex++; + } + + if ($postGreatestIndex == 1) { + $a1 = $v; + $b1 = $c->negate(); + } + + if ($postGreatestIndex == 2) { + $rhs = $a0->multiply($a0)->add($b0->multiply($b0)); + $lhs = $v->multiply($v)->add($b->multiply($b)); + if ($lhs->compare($rhs) <= 0) { + $a2 = $a0; + $b2 = $b0->negate(); + } else { + $a2 = $v; + $b2 = $c->negate(); + } + + break; + } + } + + return [ + ['a' => $a1, 'b' => $b1], + ['a' => $a2, 'b' => $b2] + ]; + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/BaseCurves/Montgomery.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/BaseCurves/Montgomery.php new file mode 100644 index 00000000..e3fa50b3 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/BaseCurves/Montgomery.php @@ -0,0 +1,279 @@ + + * @copyright 2019 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Crypt\EC\BaseCurves; + +use phpseclib3\Crypt\EC\Curves\Curve25519; +use phpseclib3\Math\BigInteger; +use phpseclib3\Math\PrimeField; +use phpseclib3\Math\PrimeField\Integer as PrimeInteger; + +/** + * Curves over y^2 = x^3 + a*x + x + * + * @author Jim Wigginton + */ +class Montgomery extends Base +{ + /** + * Prime Field Integer factory + * + * @var \phpseclib3\Math\PrimeField + */ + protected $factory; + + /** + * Cofficient for x + * + * @var object + */ + protected $a; + + /** + * Constant used for point doubling + * + * @var object + */ + protected $a24; + + /** + * The Number Zero + * + * @var object + */ + protected $zero; + + /** + * The Number One + * + * @var object + */ + protected $one; + + /** + * Base Point + * + * @var object + */ + protected $p; + + /** + * The modulo + * + * @var BigInteger + */ + protected $modulo; + + /** + * The Order + * + * @var BigInteger + */ + protected $order; + + /** + * Sets the modulo + */ + public function setModulo(BigInteger $modulo) + { + $this->modulo = $modulo; + $this->factory = new PrimeField($modulo); + $this->zero = $this->factory->newInteger(new BigInteger()); + $this->one = $this->factory->newInteger(new BigInteger(1)); + } + + /** + * Set coefficients a + */ + public function setCoefficients(BigInteger $a) + { + if (!isset($this->factory)) { + throw new \RuntimeException('setModulo needs to be called before this method'); + } + $this->a = $this->factory->newInteger($a); + $two = $this->factory->newInteger(new BigInteger(2)); + $four = $this->factory->newInteger(new BigInteger(4)); + $this->a24 = $this->a->subtract($two)->divide($four); + } + + /** + * Set x and y coordinates for the base point + * + * @param BigInteger|PrimeInteger $x + * @param BigInteger|PrimeInteger $y + * @return PrimeInteger[] + */ + public function setBasePoint($x, $y) + { + switch (true) { + case !$x instanceof BigInteger && !$x instanceof PrimeInteger: + throw new \UnexpectedValueException('Argument 1 passed to Prime::setBasePoint() must be an instance of either BigInteger or PrimeField\Integer'); + case !$y instanceof BigInteger && !$y instanceof PrimeInteger: + throw new \UnexpectedValueException('Argument 2 passed to Prime::setBasePoint() must be an instance of either BigInteger or PrimeField\Integer'); + } + if (!isset($this->factory)) { + throw new \RuntimeException('setModulo needs to be called before this method'); + } + $this->p = [ + $x instanceof BigInteger ? $this->factory->newInteger($x) : $x, + $y instanceof BigInteger ? $this->factory->newInteger($y) : $y + ]; + } + + /** + * Retrieve the base point as an array + * + * @return array + */ + public function getBasePoint() + { + if (!isset($this->factory)) { + throw new \RuntimeException('setModulo needs to be called before this method'); + } + /* + if (!isset($this->p)) { + throw new \RuntimeException('setBasePoint needs to be called before this method'); + } + */ + return $this->p; + } + + /** + * Doubles and adds a point on a curve + * + * See https://tools.ietf.org/html/draft-ietf-tls-curve25519-01#appendix-A.1.3 + * + * @return FiniteField[][] + */ + private function doubleAndAddPoint(array $p, array $q, PrimeInteger $x1) + { + if (!isset($this->factory)) { + throw new \RuntimeException('setModulo needs to be called before this method'); + } + + if (!count($p) || !count($q)) { + return []; + } + + if (!isset($p[1])) { + throw new \RuntimeException('Affine coordinates need to be manually converted to XZ coordinates'); + } + + list($x2, $z2) = $p; + list($x3, $z3) = $q; + + $a = $x2->add($z2); + $aa = $a->multiply($a); + $b = $x2->subtract($z2); + $bb = $b->multiply($b); + $e = $aa->subtract($bb); + $c = $x3->add($z3); + $d = $x3->subtract($z3); + $da = $d->multiply($a); + $cb = $c->multiply($b); + $temp = $da->add($cb); + $x5 = $temp->multiply($temp); + $temp = $da->subtract($cb); + $z5 = $x1->multiply($temp->multiply($temp)); + $x4 = $aa->multiply($bb); + $temp = static::class == Curve25519::class ? $bb : $aa; + $z4 = $e->multiply($temp->add($this->a24->multiply($e))); + + return [ + [$x4, $z4], + [$x5, $z5] + ]; + } + + /** + * Multiply a point on the curve by a scalar + * + * Uses the montgomery ladder technique as described here: + * + * https://en.wikipedia.org/wiki/Elliptic_curve_point_multiplication#Montgomery_ladder + * https://github.com/phpecc/phpecc/issues/16#issuecomment-59176772 + * + * @return array + */ + public function multiplyPoint(array $p, BigInteger $d) + { + $p1 = [$this->one, $this->zero]; + $alreadyInternal = isset($x[1]); + $p2 = $this->convertToInternal($p); + $x = $p[0]; + + $b = $d->toBits(); + $b = str_pad($b, 256, '0', STR_PAD_LEFT); + for ($i = 0; $i < strlen($b); $i++) { + $b_i = (int) $b[$i]; + if ($b_i) { + list($p2, $p1) = $this->doubleAndAddPoint($p2, $p1, $x); + } else { + list($p1, $p2) = $this->doubleAndAddPoint($p1, $p2, $x); + } + } + + return $alreadyInternal ? $p1 : $this->convertToAffine($p1); + } + + /** + * Converts an affine point to an XZ coordinate + * + * From https://hyperelliptic.org/EFD/g1p/auto-montgom-xz.html + * + * XZ coordinates represent x y as X Z satsfying the following equations: + * + * x=X/Z + * + * @return \phpseclib3\Math\PrimeField\Integer[] + */ + public function convertToInternal(array $p) + { + if (empty($p)) { + return [clone $this->zero, clone $this->one]; + } + + if (isset($p[1])) { + return $p; + } + + $p[1] = clone $this->one; + + return $p; + } + + /** + * Returns the affine point + * + * @return \phpseclib3\Math\PrimeField\Integer[] + */ + public function convertToAffine(array $p) + { + if (!isset($p[1])) { + return $p; + } + list($x, $z) = $p; + return [$x->divide($z)]; + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/BaseCurves/Prime.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/BaseCurves/Prime.php new file mode 100644 index 00000000..6250dfb0 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/BaseCurves/Prime.php @@ -0,0 +1,785 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Crypt\EC\BaseCurves; + +use phpseclib3\Common\Functions\Strings; +use phpseclib3\Math\BigInteger; +use phpseclib3\Math\Common\FiniteField\Integer; +use phpseclib3\Math\PrimeField; +use phpseclib3\Math\PrimeField\Integer as PrimeInteger; + +/** + * Curves over y^2 = x^3 + a*x + b + * + * @author Jim Wigginton + */ +class Prime extends Base +{ + /** + * Prime Field Integer factory + * + * @var \phpseclib3\Math\PrimeFields + */ + protected $factory; + + /** + * Cofficient for x^1 + * + * @var object + */ + protected $a; + + /** + * Cofficient for x^0 + * + * @var object + */ + protected $b; + + /** + * Base Point + * + * @var object + */ + protected $p; + + /** + * The number one over the specified finite field + * + * @var object + */ + protected $one; + + /** + * The number two over the specified finite field + * + * @var object + */ + protected $two; + + /** + * The number three over the specified finite field + * + * @var object + */ + protected $three; + + /** + * The number four over the specified finite field + * + * @var object + */ + protected $four; + + /** + * The number eight over the specified finite field + * + * @var object + */ + protected $eight; + + /** + * The modulo + * + * @var BigInteger + */ + protected $modulo; + + /** + * The Order + * + * @var BigInteger + */ + protected $order; + + /** + * Sets the modulo + */ + public function setModulo(BigInteger $modulo) + { + $this->modulo = $modulo; + $this->factory = new PrimeField($modulo); + $this->two = $this->factory->newInteger(new BigInteger(2)); + $this->three = $this->factory->newInteger(new BigInteger(3)); + // used by jacobian coordinates + $this->one = $this->factory->newInteger(new BigInteger(1)); + $this->four = $this->factory->newInteger(new BigInteger(4)); + $this->eight = $this->factory->newInteger(new BigInteger(8)); + } + + /** + * Set coefficients a and b + */ + public function setCoefficients(BigInteger $a, BigInteger $b) + { + if (!isset($this->factory)) { + throw new \RuntimeException('setModulo needs to be called before this method'); + } + $this->a = $this->factory->newInteger($a); + $this->b = $this->factory->newInteger($b); + } + + /** + * Set x and y coordinates for the base point + * + * @param BigInteger|PrimeInteger $x + * @param BigInteger|PrimeInteger $y + * @return PrimeInteger[] + */ + public function setBasePoint($x, $y) + { + switch (true) { + case !$x instanceof BigInteger && !$x instanceof PrimeInteger: + throw new \UnexpectedValueException('Argument 1 passed to Prime::setBasePoint() must be an instance of either BigInteger or PrimeField\Integer'); + case !$y instanceof BigInteger && !$y instanceof PrimeInteger: + throw new \UnexpectedValueException('Argument 2 passed to Prime::setBasePoint() must be an instance of either BigInteger or PrimeField\Integer'); + } + if (!isset($this->factory)) { + throw new \RuntimeException('setModulo needs to be called before this method'); + } + $this->p = [ + $x instanceof BigInteger ? $this->factory->newInteger($x) : $x, + $y instanceof BigInteger ? $this->factory->newInteger($y) : $y + ]; + } + + /** + * Retrieve the base point as an array + * + * @return array + */ + public function getBasePoint() + { + if (!isset($this->factory)) { + throw new \RuntimeException('setModulo needs to be called before this method'); + } + /* + if (!isset($this->p)) { + throw new \RuntimeException('setBasePoint needs to be called before this method'); + } + */ + return $this->p; + } + + /** + * Adds two "fresh" jacobian form on the curve + * + * @return FiniteField[] + */ + protected function jacobianAddPointMixedXY(array $p, array $q) + { + list($u1, $s1) = $p; + list($u2, $s2) = $q; + if ($u1->equals($u2)) { + if (!$s1->equals($s2)) { + return []; + } else { + return $this->doublePoint($p); + } + } + $h = $u2->subtract($u1); + $r = $s2->subtract($s1); + $h2 = $h->multiply($h); + $h3 = $h2->multiply($h); + $v = $u1->multiply($h2); + $x3 = $r->multiply($r)->subtract($h3)->subtract($v->multiply($this->two)); + $y3 = $r->multiply( + $v->subtract($x3) + )->subtract( + $s1->multiply($h3) + ); + return [$x3, $y3, $h]; + } + + /** + * Adds one "fresh" jacobian form on the curve + * + * The second parameter should be the "fresh" one + * + * @return FiniteField[] + */ + protected function jacobianAddPointMixedX(array $p, array $q) + { + list($u1, $s1, $z1) = $p; + list($x2, $y2) = $q; + + $z12 = $z1->multiply($z1); + + $u2 = $x2->multiply($z12); + $s2 = $y2->multiply($z12->multiply($z1)); + if ($u1->equals($u2)) { + if (!$s1->equals($s2)) { + return []; + } else { + return $this->doublePoint($p); + } + } + $h = $u2->subtract($u1); + $r = $s2->subtract($s1); + $h2 = $h->multiply($h); + $h3 = $h2->multiply($h); + $v = $u1->multiply($h2); + $x3 = $r->multiply($r)->subtract($h3)->subtract($v->multiply($this->two)); + $y3 = $r->multiply( + $v->subtract($x3) + )->subtract( + $s1->multiply($h3) + ); + $z3 = $h->multiply($z1); + return [$x3, $y3, $z3]; + } + + /** + * Adds two jacobian coordinates on the curve + * + * @return FiniteField[] + */ + protected function jacobianAddPoint(array $p, array $q) + { + list($x1, $y1, $z1) = $p; + list($x2, $y2, $z2) = $q; + + $z12 = $z1->multiply($z1); + $z22 = $z2->multiply($z2); + + $u1 = $x1->multiply($z22); + $u2 = $x2->multiply($z12); + $s1 = $y1->multiply($z22->multiply($z2)); + $s2 = $y2->multiply($z12->multiply($z1)); + if ($u1->equals($u2)) { + if (!$s1->equals($s2)) { + return []; + } else { + return $this->doublePoint($p); + } + } + $h = $u2->subtract($u1); + $r = $s2->subtract($s1); + $h2 = $h->multiply($h); + $h3 = $h2->multiply($h); + $v = $u1->multiply($h2); + $x3 = $r->multiply($r)->subtract($h3)->subtract($v->multiply($this->two)); + $y3 = $r->multiply( + $v->subtract($x3) + )->subtract( + $s1->multiply($h3) + ); + $z3 = $h->multiply($z1)->multiply($z2); + return [$x3, $y3, $z3]; + } + + /** + * Adds two points on the curve + * + * @return FiniteField[] + */ + public function addPoint(array $p, array $q) + { + if (!isset($this->factory)) { + throw new \RuntimeException('setModulo needs to be called before this method'); + } + + if (!count($p) || !count($q)) { + if (count($q)) { + return $q; + } + if (count($p)) { + return $p; + } + return []; + } + + // use jacobian coordinates + if (isset($p[2]) && isset($q[2])) { + if (isset($p['fresh']) && isset($q['fresh'])) { + return $this->jacobianAddPointMixedXY($p, $q); + } + if (isset($p['fresh'])) { + return $this->jacobianAddPointMixedX($q, $p); + } + if (isset($q['fresh'])) { + return $this->jacobianAddPointMixedX($p, $q); + } + return $this->jacobianAddPoint($p, $q); + } + + if (isset($p[2]) || isset($q[2])) { + throw new \RuntimeException('Affine coordinates need to be manually converted to Jacobi coordinates or vice versa'); + } + + if ($p[0]->equals($q[0])) { + if (!$p[1]->equals($q[1])) { + return []; + } else { // eg. doublePoint + list($numerator, $denominator) = $this->doublePointHelper($p); + } + } else { + $numerator = $q[1]->subtract($p[1]); + $denominator = $q[0]->subtract($p[0]); + } + $slope = $numerator->divide($denominator); + $x = $slope->multiply($slope)->subtract($p[0])->subtract($q[0]); + $y = $slope->multiply($p[0]->subtract($x))->subtract($p[1]); + + return [$x, $y]; + } + + /** + * Returns the numerator and denominator of the slope + * + * @return FiniteField[] + */ + protected function doublePointHelper(array $p) + { + $numerator = $this->three->multiply($p[0])->multiply($p[0])->add($this->a); + $denominator = $this->two->multiply($p[1]); + return [$numerator, $denominator]; + } + + /** + * Doubles a jacobian coordinate on the curve + * + * @return FiniteField[] + */ + protected function jacobianDoublePoint(array $p) + { + list($x, $y, $z) = $p; + $x2 = $x->multiply($x); + $y2 = $y->multiply($y); + $z2 = $z->multiply($z); + $s = $this->four->multiply($x)->multiply($y2); + $m1 = $this->three->multiply($x2); + $m2 = $this->a->multiply($z2->multiply($z2)); + $m = $m1->add($m2); + $x1 = $m->multiply($m)->subtract($this->two->multiply($s)); + $y1 = $m->multiply($s->subtract($x1))->subtract( + $this->eight->multiply($y2->multiply($y2)) + ); + $z1 = $this->two->multiply($y)->multiply($z); + return [$x1, $y1, $z1]; + } + + /** + * Doubles a "fresh" jacobian coordinate on the curve + * + * @return FiniteField[] + */ + protected function jacobianDoublePointMixed(array $p) + { + list($x, $y) = $p; + $x2 = $x->multiply($x); + $y2 = $y->multiply($y); + $s = $this->four->multiply($x)->multiply($y2); + $m1 = $this->three->multiply($x2); + $m = $m1->add($this->a); + $x1 = $m->multiply($m)->subtract($this->two->multiply($s)); + $y1 = $m->multiply($s->subtract($x1))->subtract( + $this->eight->multiply($y2->multiply($y2)) + ); + $z1 = $this->two->multiply($y); + return [$x1, $y1, $z1]; + } + + /** + * Doubles a point on a curve + * + * @return FiniteField[] + */ + public function doublePoint(array $p) + { + if (!isset($this->factory)) { + throw new \RuntimeException('setModulo needs to be called before this method'); + } + + if (!count($p)) { + return []; + } + + // use jacobian coordinates + if (isset($p[2])) { + if (isset($p['fresh'])) { + return $this->jacobianDoublePointMixed($p); + } + return $this->jacobianDoublePoint($p); + } + + list($numerator, $denominator) = $this->doublePointHelper($p); + + $slope = $numerator->divide($denominator); + + $x = $slope->multiply($slope)->subtract($p[0])->subtract($p[0]); + $y = $slope->multiply($p[0]->subtract($x))->subtract($p[1]); + + return [$x, $y]; + } + + /** + * Returns the X coordinate and the derived Y coordinate + * + * @return array + */ + public function derivePoint($m) + { + $y = ord(Strings::shift($m)); + $x = new BigInteger($m, 256); + $xp = $this->convertInteger($x); + switch ($y) { + case 2: + $ypn = false; + break; + case 3: + $ypn = true; + break; + default: + throw new \RuntimeException('Coordinate not in recognized format'); + } + $temp = $xp->multiply($this->a); + $temp = $xp->multiply($xp)->multiply($xp)->add($temp); + $temp = $temp->add($this->b); + $b = $temp->squareRoot(); + if (!$b) { + throw new \RuntimeException('Unable to derive Y coordinate'); + } + $bn = $b->isOdd(); + $yp = $ypn == $bn ? $b : $b->negate(); + return [$xp, $yp]; + } + + /** + * Tests whether or not the x / y values satisfy the equation + * + * @return boolean + */ + public function verifyPoint(array $p) + { + list($x, $y) = $p; + $lhs = $y->multiply($y); + $temp = $x->multiply($this->a); + $temp = $x->multiply($x)->multiply($x)->add($temp); + $rhs = $temp->add($this->b); + + return $lhs->equals($rhs); + } + + /** + * Returns the modulo + * + * @return \phpseclib3\Math\BigInteger + */ + public function getModulo() + { + return $this->modulo; + } + + /** + * Returns the a coefficient + * + * @return \phpseclib3\Math\PrimeField\Integer + */ + public function getA() + { + return $this->a; + } + + /** + * Returns the a coefficient + * + * @return \phpseclib3\Math\PrimeField\Integer + */ + public function getB() + { + return $this->b; + } + + /** + * Multiply and Add Points + * + * Adapted from: + * https://github.com/indutny/elliptic/blob/725bd91/lib/elliptic/curve/base.js#L125 + * + * @return int[] + */ + public function multiplyAddPoints(array $points, array $scalars) + { + $length = count($points); + + foreach ($points as &$point) { + $point = $this->convertToInternal($point); + } + + $wnd = [$this->getNAFPoints($points[0], 7)]; + $wndWidth = [isset($points[0]['nafwidth']) ? $points[0]['nafwidth'] : 7]; + for ($i = 1; $i < $length; $i++) { + $wnd[] = $this->getNAFPoints($points[$i], 1); + $wndWidth[] = isset($points[$i]['nafwidth']) ? $points[$i]['nafwidth'] : 1; + } + + $naf = []; + + // comb all window NAFs + + $max = 0; + for ($i = $length - 1; $i >= 1; $i -= 2) { + $a = $i - 1; + $b = $i; + if ($wndWidth[$a] != 1 || $wndWidth[$b] != 1) { + $naf[$a] = $scalars[$a]->getNAF($wndWidth[$a]); + $naf[$b] = $scalars[$b]->getNAF($wndWidth[$b]); + $max = max(count($naf[$a]), count($naf[$b]), $max); + continue; + } + + $comb = [ + $points[$a], // 1 + null, // 3 + null, // 5 + $points[$b] // 7 + ]; + + $comb[1] = $this->addPoint($points[$a], $points[$b]); + $comb[2] = $this->addPoint($points[$a], $this->negatePoint($points[$b])); + + $index = [ + -3, /* -1 -1 */ + -1, /* -1 0 */ + -5, /* -1 1 */ + -7, /* 0 -1 */ + 0, /* 0 -1 */ + 7, /* 0 1 */ + 5, /* 1 -1 */ + 1, /* 1 0 */ + 3 /* 1 1 */ + ]; + + $jsf = self::getJSFPoints($scalars[$a], $scalars[$b]); + + $max = max(count($jsf[0]), $max); + if ($max > 0) { + $naf[$a] = array_fill(0, $max, 0); + $naf[$b] = array_fill(0, $max, 0); + } else { + $naf[$a] = []; + $naf[$b] = []; + } + + for ($j = 0; $j < $max; $j++) { + $ja = isset($jsf[0][$j]) ? $jsf[0][$j] : 0; + $jb = isset($jsf[1][$j]) ? $jsf[1][$j] : 0; + + $naf[$a][$j] = $index[3 * ($ja + 1) + $jb + 1]; + $naf[$b][$j] = 0; + $wnd[$a] = $comb; + } + } + + $acc = []; + $temp = [0, 0, 0, 0]; + for ($i = $max; $i >= 0; $i--) { + $k = 0; + while ($i >= 0) { + $zero = true; + for ($j = 0; $j < $length; $j++) { + $temp[$j] = isset($naf[$j][$i]) ? $naf[$j][$i] : 0; + if ($temp[$j] != 0) { + $zero = false; + } + } + if (!$zero) { + break; + } + $k++; + $i--; + } + + if ($i >= 0) { + $k++; + } + while ($k--) { + $acc = $this->doublePoint($acc); + } + + if ($i < 0) { + break; + } + + for ($j = 0; $j < $length; $j++) { + $z = $temp[$j]; + $p = null; + if ($z == 0) { + continue; + } + $p = $z > 0 ? + $wnd[$j][($z - 1) >> 1] : + $this->negatePoint($wnd[$j][(-$z - 1) >> 1]); + $acc = $this->addPoint($acc, $p); + } + } + + return $this->convertToAffine($acc); + } + + /** + * Precomputes NAF points + * + * Adapted from: + * https://github.com/indutny/elliptic/blob/725bd91/lib/elliptic/curve/base.js#L351 + * + * @return int[] + */ + private function getNAFPoints(array $point, $wnd) + { + if (isset($point['naf'])) { + return $point['naf']; + } + + $res = [$point]; + $max = (1 << $wnd) - 1; + $dbl = $max == 1 ? null : $this->doublePoint($point); + for ($i = 1; $i < $max; $i++) { + $res[] = $this->addPoint($res[$i - 1], $dbl); + } + + $point['naf'] = $res; + + /* + $str = ''; + foreach ($res as $re) { + $re[0] = bin2hex($re[0]->toBytes()); + $re[1] = bin2hex($re[1]->toBytes()); + $str.= " ['$re[0]', '$re[1]'],\r\n"; + } + file_put_contents('temp.txt', $str); + exit; + */ + + return $res; + } + + /** + * Precomputes points in Joint Sparse Form + * + * Adapted from: + * https://github.com/indutny/elliptic/blob/725bd91/lib/elliptic/utils.js#L96 + * + * @return int[] + */ + private static function getJSFPoints(Integer $k1, Integer $k2) + { + static $three; + if (!isset($three)) { + $three = new BigInteger(3); + } + + $jsf = [[], []]; + $k1 = $k1->toBigInteger(); + $k2 = $k2->toBigInteger(); + $d1 = 0; + $d2 = 0; + + while ($k1->compare(new BigInteger(-$d1)) > 0 || $k2->compare(new BigInteger(-$d2)) > 0) { + // first phase + $m14 = $k1->testBit(0) + 2 * $k1->testBit(1); + $m14 += $d1; + $m14 &= 3; + + $m24 = $k2->testBit(0) + 2 * $k2->testBit(1); + $m24 += $d2; + $m24 &= 3; + + if ($m14 == 3) { + $m14 = -1; + } + if ($m24 == 3) { + $m24 = -1; + } + + $u1 = 0; + if ($m14 & 1) { // if $m14 is odd + $m8 = $k1->testBit(0) + 2 * $k1->testBit(1) + 4 * $k1->testBit(2); + $m8 += $d1; + $m8 &= 7; + $u1 = ($m8 == 3 || $m8 == 5) && $m24 == 2 ? -$m14 : $m14; + } + $jsf[0][] = $u1; + + $u2 = 0; + if ($m24 & 1) { // if $m24 is odd + $m8 = $k2->testBit(0) + 2 * $k2->testBit(1) + 4 * $k2->testBit(2); + $m8 += $d2; + $m8 &= 7; + $u2 = ($m8 == 3 || $m8 == 5) && $m14 == 2 ? -$m24 : $m24; + } + $jsf[1][] = $u2; + + // second phase + if (2 * $d1 == $u1 + 1) { + $d1 = 1 - $d1; + } + if (2 * $d2 == $u2 + 1) { + $d2 = 1 - $d2; + } + $k1 = $k1->bitwise_rightShift(1); + $k2 = $k2->bitwise_rightShift(1); + } + + return $jsf; + } + + /** + * Returns the affine point + * + * A Jacobian Coordinate is of the form (x, y, z). + * To convert a Jacobian Coordinate to an Affine Point + * you do (x / z^2, y / z^3) + * + * @return \phpseclib3\Math\PrimeField\Integer[] + */ + public function convertToAffine(array $p) + { + if (!isset($p[2])) { + return $p; + } + list($x, $y, $z) = $p; + $z = $this->one->divide($z); + $z2 = $z->multiply($z); + return [ + $x->multiply($z2), + $y->multiply($z2)->multiply($z) + ]; + } + + /** + * Converts an affine point to a jacobian coordinate + * + * @return \phpseclib3\Math\PrimeField\Integer[] + */ + public function convertToInternal(array $p) + { + if (isset($p[2])) { + return $p; + } + + $p[2] = clone $this->one; + $p['fresh'] = true; + return $p; + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/BaseCurves/TwistedEdwards.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/BaseCurves/TwistedEdwards.php new file mode 100644 index 00000000..2521a4c0 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/BaseCurves/TwistedEdwards.php @@ -0,0 +1,215 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Crypt\EC\BaseCurves; + +use phpseclib3\Math\BigInteger; +use phpseclib3\Math\PrimeField; +use phpseclib3\Math\PrimeField\Integer as PrimeInteger; + +/** + * Curves over a*x^2 + y^2 = 1 + d*x^2*y^2 + * + * @author Jim Wigginton + */ +class TwistedEdwards extends Base +{ + /** + * The modulo + * + * @var BigInteger + */ + protected $modulo; + + /** + * Cofficient for x^2 + * + * @var object + */ + protected $a; + + /** + * Cofficient for x^2*y^2 + * + * @var object + */ + protected $d; + + /** + * Base Point + * + * @var object[] + */ + protected $p; + + /** + * The number zero over the specified finite field + * + * @var object + */ + protected $zero; + + /** + * The number one over the specified finite field + * + * @var object + */ + protected $one; + + /** + * The number two over the specified finite field + * + * @var object + */ + protected $two; + + /** + * Sets the modulo + */ + public function setModulo(BigInteger $modulo) + { + $this->modulo = $modulo; + $this->factory = new PrimeField($modulo); + $this->zero = $this->factory->newInteger(new BigInteger(0)); + $this->one = $this->factory->newInteger(new BigInteger(1)); + $this->two = $this->factory->newInteger(new BigInteger(2)); + } + + /** + * Set coefficients a and b + */ + public function setCoefficients(BigInteger $a, BigInteger $d) + { + if (!isset($this->factory)) { + throw new \RuntimeException('setModulo needs to be called before this method'); + } + $this->a = $this->factory->newInteger($a); + $this->d = $this->factory->newInteger($d); + } + + /** + * Set x and y coordinates for the base point + */ + public function setBasePoint($x, $y) + { + switch (true) { + case !$x instanceof BigInteger && !$x instanceof PrimeInteger: + throw new \UnexpectedValueException('Argument 1 passed to Prime::setBasePoint() must be an instance of either BigInteger or PrimeField\Integer'); + case !$y instanceof BigInteger && !$y instanceof PrimeInteger: + throw new \UnexpectedValueException('Argument 2 passed to Prime::setBasePoint() must be an instance of either BigInteger or PrimeField\Integer'); + } + if (!isset($this->factory)) { + throw new \RuntimeException('setModulo needs to be called before this method'); + } + $this->p = [ + $x instanceof BigInteger ? $this->factory->newInteger($x) : $x, + $y instanceof BigInteger ? $this->factory->newInteger($y) : $y + ]; + } + + /** + * Returns the a coefficient + * + * @return \phpseclib3\Math\PrimeField\Integer + */ + public function getA() + { + return $this->a; + } + + /** + * Returns the a coefficient + * + * @return \phpseclib3\Math\PrimeField\Integer + */ + public function getD() + { + return $this->d; + } + + /** + * Retrieve the base point as an array + * + * @return array + */ + public function getBasePoint() + { + if (!isset($this->factory)) { + throw new \RuntimeException('setModulo needs to be called before this method'); + } + /* + if (!isset($this->p)) { + throw new \RuntimeException('setBasePoint needs to be called before this method'); + } + */ + return $this->p; + } + + /** + * Returns the affine point + * + * @return \phpseclib3\Math\PrimeField\Integer[] + */ + public function convertToAffine(array $p) + { + if (!isset($p[2])) { + return $p; + } + list($x, $y, $z) = $p; + $z = $this->one->divide($z); + return [ + $x->multiply($z), + $y->multiply($z) + ]; + } + + /** + * Returns the modulo + * + * @return \phpseclib3\Math\BigInteger + */ + public function getModulo() + { + return $this->modulo; + } + + /** + * Tests whether or not the x / y values satisfy the equation + * + * @return boolean + */ + public function verifyPoint(array $p) + { + list($x, $y) = $p; + $x2 = $x->multiply($x); + $y2 = $y->multiply($y); + + $lhs = $this->a->multiply($x2)->add($y2); + $rhs = $this->d->multiply($x2)->multiply($y2)->add($this->one); + + return $lhs->equals($rhs); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/Curve25519.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/Curve25519.php new file mode 100644 index 00000000..0f3f4d82 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/Curve25519.php @@ -0,0 +1,81 @@ + + * @copyright 2019 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Crypt\EC\Curves; + +use phpseclib3\Crypt\EC\BaseCurves\Montgomery; +use phpseclib3\Math\BigInteger; + +class Curve25519 extends Montgomery +{ + public function __construct() + { + // 2^255 - 19 + $this->setModulo(new BigInteger('7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFED', 16)); + $this->a24 = $this->factory->newInteger(new BigInteger('121666')); + $this->p = [$this->factory->newInteger(new BigInteger(9))]; + // 2^252 + 0x14def9dea2f79cd65812631a5cf5d3ed + $this->setOrder(new BigInteger('1000000000000000000000000000000014DEF9DEA2F79CD65812631A5CF5D3ED', 16)); + + /* + $this->setCoefficients( + new BigInteger('486662'), // a + ); + $this->setBasePoint( + new BigInteger(9), + new BigInteger('14781619447589544791020593568409986887264606134616475288964881837755586237401') + ); + */ + } + + /** + * Multiply a point on the curve by a scalar + * + * Modifies the scalar as described at https://tools.ietf.org/html/rfc7748#page-8 + * + * @return array + */ + public function multiplyPoint(array $p, BigInteger $d) + { + //$r = strrev(sodium_crypto_scalarmult($d->toBytes(), strrev($p[0]->toBytes()))); + //return [$this->factory->newInteger(new BigInteger($r, 256))]; + + $d = $d->toBytes(); + $d &= "\xF8" . str_repeat("\xFF", 30) . "\x7F"; + $d = strrev($d); + $d |= "\x40"; + $d = new BigInteger($d, -256); + + return parent::multiplyPoint($p, $d); + } + + /** + * Creates a random scalar multiplier + * + * @return BigInteger + */ + public function createRandomMultiplier() + { + return BigInteger::random(256); + } + + /** + * Performs range check + */ + public function rangeCheck(BigInteger $x) + { + if ($x->getLength() > 256 || $x->isNegative()) { + throw new \RangeException('x must be a positive integer less than 256 bytes in length'); + } + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/Curve448.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/Curve448.php new file mode 100644 index 00000000..f4a44231 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/Curve448.php @@ -0,0 +1,92 @@ + + * @copyright 2019 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Crypt\EC\Curves; + +use phpseclib3\Crypt\EC\BaseCurves\Montgomery; +use phpseclib3\Math\BigInteger; + +class Curve448 extends Montgomery +{ + public function __construct() + { + // 2^448 - 2^224 - 1 + $this->setModulo(new BigInteger( + 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE' . + 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', + 16 + )); + $this->a24 = $this->factory->newInteger(new BigInteger('39081')); + $this->p = [$this->factory->newInteger(new BigInteger(5))]; + // 2^446 - 0x8335dc163bb124b65129c96fde933d8d723a70aadc873d6d54a7bb0d + $this->setOrder(new BigInteger( + '3FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF' . + '7CCA23E9C44EDB49AED63690216CC2728DC58F552378C292AB5844F3', + 16 + )); + + /* + $this->setCoefficients( + new BigInteger('156326'), // a + ); + $this->setBasePoint( + new BigInteger(5), + new BigInteger( + '355293926785568175264127502063783334808976399387714271831880898' . + '435169088786967410002932673765864550910142774147268105838985595290' . + '606362') + ); + */ + } + + /** + * Multiply a point on the curve by a scalar + * + * Modifies the scalar as described at https://tools.ietf.org/html/rfc7748#page-8 + * + * @return array + */ + public function multiplyPoint(array $p, BigInteger $d) + { + //$r = strrev(sodium_crypto_scalarmult($d->toBytes(), strrev($p[0]->toBytes()))); + //return [$this->factory->newInteger(new BigInteger($r, 256))]; + + $d = $d->toBytes(); + $d[0] = $d[0] & "\xFC"; + $d = strrev($d); + $d |= "\x80"; + $d = new BigInteger($d, 256); + + return parent::multiplyPoint($p, $d); + } + + /** + * Creates a random scalar multiplier + * + * @return BigInteger + */ + public function createRandomMultiplier() + { + return BigInteger::random(446); + } + + /** + * Performs range check + */ + public function rangeCheck(BigInteger $x) + { + if ($x->getLength() > 448 || $x->isNegative()) { + throw new \RangeException('x must be a positive integer less than 446 bytes in length'); + } + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/Ed25519.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/Ed25519.php new file mode 100644 index 00000000..9d3de684 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/Ed25519.php @@ -0,0 +1,333 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + */ + +namespace phpseclib3\Crypt\EC\Curves; + +use phpseclib3\Crypt\EC\BaseCurves\TwistedEdwards; +use phpseclib3\Crypt\Hash; +use phpseclib3\Crypt\Random; +use phpseclib3\Math\BigInteger; + +class Ed25519 extends TwistedEdwards +{ + const HASH = 'sha512'; + /* + Per https://tools.ietf.org/html/rfc8032#page-6 EdDSA has several parameters, one of which is b: + + 2. An integer b with 2^(b-1) > p. EdDSA public keys have exactly b + bits, and EdDSA signatures have exactly 2*b bits. b is + recommended to be a multiple of 8, so public key and signature + lengths are an integral number of octets. + + SIZE corresponds to b + */ + const SIZE = 32; + + public function __construct() + { + // 2^255 - 19 + $this->setModulo(new BigInteger('7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFED', 16)); + $this->setCoefficients( + // -1 + new BigInteger('7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEC', 16), // a + // -121665/121666 + new BigInteger('52036CEE2B6FFE738CC740797779E89800700A4D4141D8AB75EB4DCA135978A3', 16) // d + ); + $this->setBasePoint( + new BigInteger('216936D3CD6E53FEC0A4E231FDD6DC5C692CC7609525A7B2C9562D608F25D51A', 16), + new BigInteger('6666666666666666666666666666666666666666666666666666666666666658', 16) + ); + $this->setOrder(new BigInteger('1000000000000000000000000000000014DEF9DEA2F79CD65812631A5CF5D3ED', 16)); + // algorithm 14.47 from http://cacr.uwaterloo.ca/hac/about/chap14.pdf#page=16 + /* + $this->setReduction(function($x) { + $parts = $x->bitwise_split(255); + $className = $this->className; + + if (count($parts) > 2) { + list(, $r) = $x->divide($className::$modulo); + return $r; + } + + $zero = new BigInteger(); + $c = new BigInteger(19); + + switch (count($parts)) { + case 2: + list($qi, $ri) = $parts; + break; + case 1: + $qi = $zero; + list($ri) = $parts; + break; + case 0: + return $zero; + } + $r = $ri; + + while ($qi->compare($zero) > 0) { + $temp = $qi->multiply($c)->bitwise_split(255); + if (count($temp) == 2) { + list($qi, $ri) = $temp; + } else { + $qi = $zero; + list($ri) = $temp; + } + $r = $r->add($ri); + } + + while ($r->compare($className::$modulo) > 0) { + $r = $r->subtract($className::$modulo); + } + return $r; + }); + */ + } + + /** + * Recover X from Y + * + * Implements steps 2-4 at https://tools.ietf.org/html/rfc8032#section-5.1.3 + * + * Used by EC\Keys\Common.php + * + * @param BigInteger $y + * @param boolean $sign + * @return object[] + */ + public function recoverX(BigInteger $y, $sign) + { + $y = $this->factory->newInteger($y); + + $y2 = $y->multiply($y); + $u = $y2->subtract($this->one); + $v = $this->d->multiply($y2)->add($this->one); + $x2 = $u->divide($v); + if ($x2->equals($this->zero)) { + if ($sign) { + throw new \RuntimeException('Unable to recover X coordinate (x2 = 0)'); + } + return clone $this->zero; + } + // find the square root + /* we don't do $x2->squareRoot() because, quoting from + https://tools.ietf.org/html/rfc8032#section-5.1.1: + + "For point decoding or "decompression", square roots modulo p are + needed. They can be computed using the Tonelli-Shanks algorithm or + the special case for p = 5 (mod 8). To find a square root of a, + first compute the candidate root x = a^((p+3)/8) (mod p)." + */ + $exp = $this->getModulo()->add(new BigInteger(3)); + $exp = $exp->bitwise_rightShift(3); + $x = $x2->pow($exp); + + // If v x^2 = -u (mod p), set x <-- x * 2^((p-1)/4), which is a square root. + if (!$x->multiply($x)->subtract($x2)->equals($this->zero)) { + $temp = $this->getModulo()->subtract(new BigInteger(1)); + $temp = $temp->bitwise_rightShift(2); + $temp = $this->two->pow($temp); + $x = $x->multiply($temp); + if (!$x->multiply($x)->subtract($x2)->equals($this->zero)) { + throw new \RuntimeException('Unable to recover X coordinate'); + } + } + if ($x->isOdd() != $sign) { + $x = $x->negate(); + } + + return [$x, $y]; + } + + /** + * Extract Secret Scalar + * + * Implements steps 1-3 at https://tools.ietf.org/html/rfc8032#section-5.1.5 + * + * Used by the various key handlers + * + * @param string $str + * @return array + */ + public function extractSecret($str) + { + if (strlen($str) != 32) { + throw new \LengthException('Private Key should be 32-bytes long'); + } + // 1. Hash the 32-byte private key using SHA-512, storing the digest in + // a 64-octet large buffer, denoted h. Only the lower 32 bytes are + // used for generating the public key. + $hash = new Hash('sha512'); + $h = $hash->hash($str); + $h = substr($h, 0, 32); + // 2. Prune the buffer: The lowest three bits of the first octet are + // cleared, the highest bit of the last octet is cleared, and the + // second highest bit of the last octet is set. + $h[0] = $h[0] & chr(0xF8); + $h = strrev($h); + $h[0] = ($h[0] & chr(0x3F)) | chr(0x40); + // 3. Interpret the buffer as the little-endian integer, forming a + // secret scalar s. + $dA = new BigInteger($h, 256); + + return [ + 'dA' => $dA, + 'secret' => $str + ]; + } + + /** + * Encode a point as a string + * + * @param array $point + * @return string + */ + public function encodePoint($point) + { + list($x, $y) = $point; + $y = $y->toBytes(); + $y[0] = $y[0] & chr(0x7F); + if ($x->isOdd()) { + $y[0] = $y[0] | chr(0x80); + } + $y = strrev($y); + + return $y; + } + + /** + * Creates a random scalar multiplier + * + * @return \phpseclib3\Math\PrimeField\Integer + */ + public function createRandomMultiplier() + { + return $this->extractSecret(Random::string(32))['dA']; + } + + /** + * Converts an affine point to an extended homogeneous coordinate + * + * From https://tools.ietf.org/html/rfc8032#section-5.1.4 : + * + * A point (x,y) is represented in extended homogeneous coordinates (X, Y, Z, T), + * with x = X/Z, y = Y/Z, x * y = T/Z. + * + * @return \phpseclib3\Math\PrimeField\Integer[] + */ + public function convertToInternal(array $p) + { + if (empty($p)) { + return [clone $this->zero, clone $this->one, clone $this->one, clone $this->zero]; + } + + if (isset($p[2])) { + return $p; + } + + $p[2] = clone $this->one; + $p[3] = $p[0]->multiply($p[1]); + + return $p; + } + + /** + * Doubles a point on a curve + * + * @return FiniteField[] + */ + public function doublePoint(array $p) + { + if (!isset($this->factory)) { + throw new \RuntimeException('setModulo needs to be called before this method'); + } + + if (!count($p)) { + return []; + } + + if (!isset($p[2])) { + throw new \RuntimeException('Affine coordinates need to be manually converted to "Jacobi" coordinates or vice versa'); + } + + // from https://tools.ietf.org/html/rfc8032#page-12 + + list($x1, $y1, $z1, $t1) = $p; + + $a = $x1->multiply($x1); + $b = $y1->multiply($y1); + $c = $this->two->multiply($z1)->multiply($z1); + $h = $a->add($b); + $temp = $x1->add($y1); + $e = $h->subtract($temp->multiply($temp)); + $g = $a->subtract($b); + $f = $c->add($g); + + $x3 = $e->multiply($f); + $y3 = $g->multiply($h); + $t3 = $e->multiply($h); + $z3 = $f->multiply($g); + + return [$x3, $y3, $z3, $t3]; + } + + /** + * Adds two points on the curve + * + * @return FiniteField[] + */ + public function addPoint(array $p, array $q) + { + if (!isset($this->factory)) { + throw new \RuntimeException('setModulo needs to be called before this method'); + } + + if (!count($p) || !count($q)) { + if (count($q)) { + return $q; + } + if (count($p)) { + return $p; + } + return []; + } + + if (!isset($p[2]) || !isset($q[2])) { + throw new \RuntimeException('Affine coordinates need to be manually converted to "Jacobi" coordinates or vice versa'); + } + + if ($p[0]->equals($q[0])) { + return !$p[1]->equals($q[1]) ? [] : $this->doublePoint($p); + } + + // from https://tools.ietf.org/html/rfc8032#page-12 + + list($x1, $y1, $z1, $t1) = $p; + list($x2, $y2, $z2, $t2) = $q; + + $a = $y1->subtract($x1)->multiply($y2->subtract($x2)); + $b = $y1->add($x1)->multiply($y2->add($x2)); + $c = $t1->multiply($this->two)->multiply($this->d)->multiply($t2); + $d = $z1->multiply($this->two)->multiply($z2); + $e = $b->subtract($a); + $f = $d->subtract($c); + $g = $d->add($c); + $h = $b->add($a); + + $x3 = $e->multiply($f); + $y3 = $g->multiply($h); + $t3 = $e->multiply($h); + $z3 = $f->multiply($g); + + return [$x3, $y3, $z3, $t3]; + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/Ed448.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/Ed448.php new file mode 100644 index 00000000..5451f909 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/Ed448.php @@ -0,0 +1,273 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + */ + +namespace phpseclib3\Crypt\EC\Curves; + +use phpseclib3\Crypt\EC\BaseCurves\TwistedEdwards; +use phpseclib3\Crypt\Hash; +use phpseclib3\Crypt\Random; +use phpseclib3\Math\BigInteger; + +class Ed448 extends TwistedEdwards +{ + const HASH = 'shake256-912'; + const SIZE = 57; + + public function __construct() + { + // 2^448 - 2^224 - 1 + $this->setModulo(new BigInteger( + 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE' . + 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', + 16 + )); + $this->setCoefficients( + new BigInteger(1), + // -39081 + new BigInteger('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE' . + 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6756', 16) + ); + $this->setBasePoint( + new BigInteger('4F1970C66BED0DED221D15A622BF36DA9E146570470F1767EA6DE324' . + 'A3D3A46412AE1AF72AB66511433B80E18B00938E2626A82BC70CC05E', 16), + new BigInteger('693F46716EB6BC248876203756C9C7624BEA73736CA3984087789C1E' . + '05A0C2D73AD3FF1CE67C39C4FDBD132C4ED7C8AD9808795BF230FA14', 16) + ); + $this->setOrder(new BigInteger( + '3FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF' . + '7CCA23E9C44EDB49AED63690216CC2728DC58F552378C292AB5844F3', + 16 + )); + } + + /** + * Recover X from Y + * + * Implements steps 2-4 at https://tools.ietf.org/html/rfc8032#section-5.2.3 + * + * Used by EC\Keys\Common.php + * + * @param BigInteger $y + * @param boolean $sign + * @return object[] + */ + public function recoverX(BigInteger $y, $sign) + { + $y = $this->factory->newInteger($y); + + $y2 = $y->multiply($y); + $u = $y2->subtract($this->one); + $v = $this->d->multiply($y2)->subtract($this->one); + $x2 = $u->divide($v); + if ($x2->equals($this->zero)) { + if ($sign) { + throw new \RuntimeException('Unable to recover X coordinate (x2 = 0)'); + } + return clone $this->zero; + } + // find the square root + $exp = $this->getModulo()->add(new BigInteger(1)); + $exp = $exp->bitwise_rightShift(2); + $x = $x2->pow($exp); + + if (!$x->multiply($x)->subtract($x2)->equals($this->zero)) { + throw new \RuntimeException('Unable to recover X coordinate'); + } + if ($x->isOdd() != $sign) { + $x = $x->negate(); + } + + return [$x, $y]; + } + + /** + * Extract Secret Scalar + * + * Implements steps 1-3 at https://tools.ietf.org/html/rfc8032#section-5.2.5 + * + * Used by the various key handlers + * + * @param string $str + * @return array + */ + public function extractSecret($str) + { + if (strlen($str) != 57) { + throw new \LengthException('Private Key should be 57-bytes long'); + } + // 1. Hash the 57-byte private key using SHAKE256(x, 114), storing the + // digest in a 114-octet large buffer, denoted h. Only the lower 57 + // bytes are used for generating the public key. + $hash = new Hash('shake256-912'); + $h = $hash->hash($str); + $h = substr($h, 0, 57); + // 2. Prune the buffer: The two least significant bits of the first + // octet are cleared, all eight bits the last octet are cleared, and + // the highest bit of the second to last octet is set. + $h[0] = $h[0] & chr(0xFC); + $h = strrev($h); + $h[0] = "\0"; + $h[1] = $h[1] | chr(0x80); + // 3. Interpret the buffer as the little-endian integer, forming a + // secret scalar s. + $dA = new BigInteger($h, 256); + + return [ + 'dA' => $dA, + 'secret' => $str + ]; + + $dA->secret = $str; + return $dA; + } + + /** + * Encode a point as a string + * + * @param array $point + * @return string + */ + public function encodePoint($point) + { + list($x, $y) = $point; + $y = "\0" . $y->toBytes(); + if ($x->isOdd()) { + $y[0] = $y[0] | chr(0x80); + } + $y = strrev($y); + + return $y; + } + + /** + * Creates a random scalar multiplier + * + * @return \phpseclib3\Math\PrimeField\Integer + */ + public function createRandomMultiplier() + { + return $this->extractSecret(Random::string(57))['dA']; + } + + /** + * Converts an affine point to an extended homogeneous coordinate + * + * From https://tools.ietf.org/html/rfc8032#section-5.2.4 : + * + * A point (x,y) is represented in extended homogeneous coordinates (X, Y, Z, T), + * with x = X/Z, y = Y/Z, x * y = T/Z. + * + * @return \phpseclib3\Math\PrimeField\Integer[] + */ + public function convertToInternal(array $p) + { + if (empty($p)) { + return [clone $this->zero, clone $this->one, clone $this->one]; + } + + if (isset($p[2])) { + return $p; + } + + $p[2] = clone $this->one; + + return $p; + } + + /** + * Doubles a point on a curve + * + * @return FiniteField[] + */ + public function doublePoint(array $p) + { + if (!isset($this->factory)) { + throw new \RuntimeException('setModulo needs to be called before this method'); + } + + if (!count($p)) { + return []; + } + + if (!isset($p[2])) { + throw new \RuntimeException('Affine coordinates need to be manually converted to "Jacobi" coordinates or vice versa'); + } + + // from https://tools.ietf.org/html/rfc8032#page-18 + + list($x1, $y1, $z1) = $p; + + $b = $x1->add($y1); + $b = $b->multiply($b); + $c = $x1->multiply($x1); + $d = $y1->multiply($y1); + $e = $c->add($d); + $h = $z1->multiply($z1); + $j = $e->subtract($this->two->multiply($h)); + + $x3 = $b->subtract($e)->multiply($j); + $y3 = $c->subtract($d)->multiply($e); + $z3 = $e->multiply($j); + + return [$x3, $y3, $z3]; + } + + /** + * Adds two points on the curve + * + * @return FiniteField[] + */ + public function addPoint(array $p, array $q) + { + if (!isset($this->factory)) { + throw new \RuntimeException('setModulo needs to be called before this method'); + } + + if (!count($p) || !count($q)) { + if (count($q)) { + return $q; + } + if (count($p)) { + return $p; + } + return []; + } + + if (!isset($p[2]) || !isset($q[2])) { + throw new \RuntimeException('Affine coordinates need to be manually converted to "Jacobi" coordinates or vice versa'); + } + + if ($p[0]->equals($q[0])) { + return !$p[1]->equals($q[1]) ? [] : $this->doublePoint($p); + } + + // from https://tools.ietf.org/html/rfc8032#page-17 + + list($x1, $y1, $z1) = $p; + list($x2, $y2, $z2) = $q; + + $a = $z1->multiply($z2); + $b = $a->multiply($a); + $c = $x1->multiply($x2); + $d = $y1->multiply($y2); + $e = $this->d->multiply($c)->multiply($d); + $f = $b->subtract($e); + $g = $b->add($e); + $h = $x1->add($y1)->multiply($x2->add($y2)); + + $x3 = $a->multiply($f)->multiply($h->subtract($c)->subtract($d)); + $y3 = $a->multiply($g)->multiply($d->subtract($c)); + $z3 = $f->multiply($g); + + return [$x3, $y3, $z3]; + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP160r1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP160r1.php new file mode 100644 index 00000000..7bc2272a --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP160r1.php @@ -0,0 +1,34 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Crypt\EC\Curves; + +use phpseclib3\Crypt\EC\BaseCurves\Prime; +use phpseclib3\Math\BigInteger; + +class brainpoolP160r1 extends Prime +{ + public function __construct() + { + $this->setModulo(new BigInteger('E95E4A5F737059DC60DFC7AD95B3D8139515620F', 16)); + $this->setCoefficients( + new BigInteger('340E7BE2A280EB74E2BE61BADA745D97E8F7C300', 16), + new BigInteger('1E589A8595423412134FAA2DBDEC95C8D8675E58', 16) + ); + $this->setBasePoint( + new BigInteger('BED5AF16EA3F6A4F62938C4631EB5AF7BDBCDBC3', 16), + new BigInteger('1667CB477A1A8EC338F94741669C976316DA6321', 16) + ); + $this->setOrder(new BigInteger('E95E4A5F737059DC60DF5991D45029409E60FC09', 16)); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP160t1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP160t1.php new file mode 100644 index 00000000..ebfb29ae --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP160t1.php @@ -0,0 +1,47 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Crypt\EC\Curves; + +use phpseclib3\Crypt\EC\BaseCurves\Prime; +use phpseclib3\Math\BigInteger; + +class brainpoolP160t1 extends Prime +{ + public function __construct() + { + $this->setModulo(new BigInteger('E95E4A5F737059DC60DFC7AD95B3D8139515620F', 16)); + $this->setCoefficients( + new BigInteger('E95E4A5F737059DC60DFC7AD95B3D8139515620C', 16), // eg. -3 + new BigInteger('7A556B6DAE535B7B51ED2C4D7DAA7A0B5C55F380', 16) + ); + $this->setBasePoint( + new BigInteger('B199B13B9B34EFC1397E64BAEB05ACC265FF2378', 16), + new BigInteger('ADD6718B7C7C1961F0991B842443772152C9E0AD', 16) + ); + $this->setOrder(new BigInteger('E95E4A5F737059DC60DF5991D45029409E60FC09', 16)); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP192r1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP192r1.php new file mode 100644 index 00000000..6ec848bc --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP192r1.php @@ -0,0 +1,34 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Crypt\EC\Curves; + +use phpseclib3\Crypt\EC\BaseCurves\Prime; +use phpseclib3\Math\BigInteger; + +class brainpoolP192r1 extends Prime +{ + public function __construct() + { + $this->setModulo(new BigInteger('C302F41D932A36CDA7A3463093D18DB78FCE476DE1A86297', 16)); + $this->setCoefficients( + new BigInteger('6A91174076B1E0E19C39C031FE8685C1CAE040E5C69A28EF', 16), + new BigInteger('469A28EF7C28CCA3DC721D044F4496BCCA7EF4146FBF25C9', 16) + ); + $this->setBasePoint( + new BigInteger('C0A0647EAAB6A48753B033C56CB0F0900A2F5C4853375FD6', 16), + new BigInteger('14B690866ABD5BB88B5F4828C1490002E6773FA2FA299B8F', 16) + ); + $this->setOrder(new BigInteger('C302F41D932A36CDA7A3462F9E9E916B5BE8F1029AC4ACC1', 16)); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP192t1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP192t1.php new file mode 100644 index 00000000..e6a86bbd --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP192t1.php @@ -0,0 +1,34 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Crypt\EC\Curves; + +use phpseclib3\Crypt\EC\BaseCurves\Prime; +use phpseclib3\Math\BigInteger; + +class brainpoolP192t1 extends Prime +{ + public function __construct() + { + $this->setModulo(new BigInteger('C302F41D932A36CDA7A3463093D18DB78FCE476DE1A86297', 16)); + $this->setCoefficients( + new BigInteger('C302F41D932A36CDA7A3463093D18DB78FCE476DE1A86294', 16), // eg. -3 + new BigInteger('13D56FFAEC78681E68F9DEB43B35BEC2FB68542E27897B79', 16) + ); + $this->setBasePoint( + new BigInteger('3AE9E58C82F63C30282E1FE7BBF43FA72C446AF6F4618129', 16), + new BigInteger('097E2C5667C2223A902AB5CA449D0084B7E5B3DE7CCC01C9', 16) + ); + $this->setOrder(new BigInteger('C302F41D932A36CDA7A3462F9E9E916B5BE8F1029AC4ACC1', 16)); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP224r1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP224r1.php new file mode 100644 index 00000000..3d7d8726 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP224r1.php @@ -0,0 +1,34 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Crypt\EC\Curves; + +use phpseclib3\Crypt\EC\BaseCurves\Prime; +use phpseclib3\Math\BigInteger; + +class brainpoolP224r1 extends Prime +{ + public function __construct() + { + $this->setModulo(new BigInteger('D7C134AA264366862A18302575D1D787B09F075797DA89F57EC8C0FF', 16)); + $this->setCoefficients( + new BigInteger('68A5E62CA9CE6C1C299803A6C1530B514E182AD8B0042A59CAD29F43', 16), + new BigInteger('2580F63CCFE44138870713B1A92369E33E2135D266DBB372386C400B', 16) + ); + $this->setBasePoint( + new BigInteger('0D9029AD2C7E5CF4340823B2A87DC68C9E4CE3174C1E6EFDEE12C07D', 16), + new BigInteger('58AA56F772C0726F24C6B89E4ECDAC24354B9E99CAA3F6D3761402CD', 16) + ); + $this->setOrder(new BigInteger('D7C134AA264366862A18302575D0FB98D116BC4B6DDEBCA3A5A7939F', 16)); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP224t1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP224t1.php new file mode 100644 index 00000000..3d4f9289 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP224t1.php @@ -0,0 +1,34 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Crypt\EC\Curves; + +use phpseclib3\Crypt\EC\BaseCurves\Prime; +use phpseclib3\Math\BigInteger; + +class brainpoolP224t1 extends Prime +{ + public function __construct() + { + $this->setModulo(new BigInteger('D7C134AA264366862A18302575D1D787B09F075797DA89F57EC8C0FF', 16)); + $this->setCoefficients( + new BigInteger('D7C134AA264366862A18302575D1D787B09F075797DA89F57EC8C0FC', 16), // eg. -3 + new BigInteger('4B337D934104CD7BEF271BF60CED1ED20DA14C08B3BB64F18A60888D', 16) + ); + $this->setBasePoint( + new BigInteger('6AB1E344CE25FF3896424E7FFE14762ECB49F8928AC0C76029B4D580', 16), + new BigInteger('0374E9F5143E568CD23F3F4D7C0D4B1E41C8CC0D1C6ABD5F1A46DB4C', 16) + ); + $this->setOrder(new BigInteger('D7C134AA264366862A18302575D0FB98D116BC4B6DDEBCA3A5A7939F', 16)); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP256r1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP256r1.php new file mode 100644 index 00000000..5780da76 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP256r1.php @@ -0,0 +1,34 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Crypt\EC\Curves; + +use phpseclib3\Crypt\EC\BaseCurves\Prime; +use phpseclib3\Math\BigInteger; + +class brainpoolP256r1 extends Prime +{ + public function __construct() + { + $this->setModulo(new BigInteger('A9FB57DBA1EEA9BC3E660A909D838D726E3BF623D52620282013481D1F6E5377', 16)); + $this->setCoefficients( + new BigInteger('7D5A0975FC2C3057EEF67530417AFFE7FB8055C126DC5C6CE94A4B44F330B5D9', 16), + new BigInteger('26DC5C6CE94A4B44F330B5D9BBD77CBF958416295CF7E1CE6BCCDC18FF8C07B6', 16) + ); + $this->setBasePoint( + new BigInteger('8BD2AEB9CB7E57CB2C4B482FFC81B7AFB9DE27E1E3BD23C23A4453BD9ACE3262', 16), + new BigInteger('547EF835C3DAC4FD97F8461A14611DC9C27745132DED8E545C1D54C72F046997', 16) + ); + $this->setOrder(new BigInteger('A9FB57DBA1EEA9BC3E660A909D838D718C397AA3B561A6F7901E0E82974856A7', 16)); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP256t1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP256t1.php new file mode 100644 index 00000000..724d8b8f --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP256t1.php @@ -0,0 +1,34 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Crypt\EC\Curves; + +use phpseclib3\Crypt\EC\BaseCurves\Prime; +use phpseclib3\Math\BigInteger; + +class brainpoolP256t1 extends Prime +{ + public function __construct() + { + $this->setModulo(new BigInteger('A9FB57DBA1EEA9BC3E660A909D838D726E3BF623D52620282013481D1F6E5377', 16)); + $this->setCoefficients( + new BigInteger('A9FB57DBA1EEA9BC3E660A909D838D726E3BF623D52620282013481D1F6E5374', 16), // eg. -3 + new BigInteger('662C61C430D84EA4FE66A7733D0B76B7BF93EBC4AF2F49256AE58101FEE92B04', 16) + ); + $this->setBasePoint( + new BigInteger('A3E8EB3CC1CFE7B7732213B23A656149AFA142C47AAFBC2B79A191562E1305F4', 16), + new BigInteger('2D996C823439C56D7F7B22E14644417E69BCB6DE39D027001DABE8F35B25C9BE', 16) + ); + $this->setOrder(new BigInteger('A9FB57DBA1EEA9BC3E660A909D838D718C397AA3B561A6F7901E0E82974856A7', 16)); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP320r1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP320r1.php new file mode 100644 index 00000000..182e6227 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP320r1.php @@ -0,0 +1,40 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Crypt\EC\Curves; + +use phpseclib3\Crypt\EC\BaseCurves\Prime; +use phpseclib3\Math\BigInteger; + +class brainpoolP320r1 extends Prime +{ + public function __construct() + { + $this->setModulo(new BigInteger('D35E472036BC4FB7E13C785ED201E065F98FCFA6F6F40DEF4F9' . + '2B9EC7893EC28FCD412B1F1B32E27', 16)); + $this->setCoefficients( + new BigInteger('3EE30B568FBAB0F883CCEBD46D3F3BB8A2A73513F5EB79DA66190EB085FFA9F4' . + '92F375A97D860EB4', 16), + new BigInteger('520883949DFDBC42D3AD198640688A6FE13F41349554B49ACC31DCCD88453981' . + '6F5EB4AC8FB1F1A6', 16) + ); + $this->setBasePoint( + new BigInteger('43BD7E9AFB53D8B85289BCC48EE5BFE6F20137D10A087EB6E7871E2A10A599C7' . + '10AF8D0D39E20611', 16), + new BigInteger('14FDD05545EC1CC8AB4093247F77275E0743FFED117182EAA9C77877AAAC6AC7' . + 'D35245D1692E8EE1', 16) + ); + $this->setOrder(new BigInteger('D35E472036BC4FB7E13C785ED201E065F98FCFA5B68F12A32D4' . + '82EC7EE8658E98691555B44C59311', 16)); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP320t1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP320t1.php new file mode 100644 index 00000000..d5a620d8 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP320t1.php @@ -0,0 +1,40 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Crypt\EC\Curves; + +use phpseclib3\Crypt\EC\BaseCurves\Prime; +use phpseclib3\Math\BigInteger; + +class brainpoolP320t1 extends Prime +{ + public function __construct() + { + $this->setModulo(new BigInteger('D35E472036BC4FB7E13C785ED201E065F98FCFA6F6F40DEF4F9' . + '2B9EC7893EC28FCD412B1F1B32E27', 16)); + $this->setCoefficients( + new BigInteger('D35E472036BC4FB7E13C785ED201E065F98FCFA6F6F40DEF4F92B9EC7893EC28' . + 'FCD412B1F1B32E24', 16), // eg. -3 + new BigInteger('A7F561E038EB1ED560B3D147DB782013064C19F27ED27C6780AAF77FB8A547CE' . + 'B5B4FEF422340353', 16) + ); + $this->setBasePoint( + new BigInteger('925BE9FB01AFC6FB4D3E7D4990010F813408AB106C4F09CB7EE07868CC136FFF' . + '3357F624A21BED52', 16), + new BigInteger('63BA3A7A27483EBF6671DBEF7ABB30EBEE084E58A0B077AD42A5A0989D1EE71B' . + '1B9BC0455FB0D2C3', 16) + ); + $this->setOrder(new BigInteger('D35E472036BC4FB7E13C785ED201E065F98FCFA5B68F12A32D4' . + '82EC7EE8658E98691555B44C59311', 16)); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP384r1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP384r1.php new file mode 100644 index 00000000..a20b4b44 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP384r1.php @@ -0,0 +1,58 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Crypt\EC\Curves; + +use phpseclib3\Crypt\EC\BaseCurves\Prime; +use phpseclib3\Math\BigInteger; + +class brainpoolP384r1 extends Prime +{ + public function __construct() + { + $this->setModulo(new BigInteger( + '8CB91E82A3386D280F5D6F7E50E641DF152F7109ED5456B412B1DA197FB71123ACD3A729901D1A7' . + '1874700133107EC53', + 16 + )); + $this->setCoefficients( + new BigInteger( + '7BC382C63D8C150C3C72080ACE05AFA0C2BEA28E4FB22787139165EFBA91F90F8AA5814A503' . + 'AD4EB04A8C7DD22CE2826', + 16 + ), + new BigInteger( + '4A8C7DD22CE28268B39B55416F0447C2FB77DE107DCD2A62E880EA53EEB62D57CB4390295DB' . + 'C9943AB78696FA504C11', + 16 + ) + ); + $this->setBasePoint( + new BigInteger( + '1D1C64F068CF45FFA2A63A81B7C13F6B8847A3E77EF14FE3DB7FCAFE0CBD10E8E826E03436D' . + '646AAEF87B2E247D4AF1E', + 16 + ), + new BigInteger( + '8ABE1D7520F9C2A45CB1EB8E95CFD55262B70B29FEEC5864E19C054FF99129280E464621779' . + '1811142820341263C5315', + 16 + ) + ); + $this->setOrder(new BigInteger( + '8CB91E82A3386D280F5D6F7E50E641DF152F7109ED5456B31F166E6CAC0425A7CF3AB6AF6B7FC31' . + '03B883202E9046565', + 16 + )); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP384t1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP384t1.php new file mode 100644 index 00000000..366660e6 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP384t1.php @@ -0,0 +1,58 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Crypt\EC\Curves; + +use phpseclib3\Crypt\EC\BaseCurves\Prime; +use phpseclib3\Math\BigInteger; + +class brainpoolP384t1 extends Prime +{ + public function __construct() + { + $this->setModulo(new BigInteger( + '8CB91E82A3386D280F5D6F7E50E641DF152F7109ED5456B412B1DA197FB71123ACD3A729901D1A7' . + '1874700133107EC53', + 16 + )); + $this->setCoefficients( + new BigInteger( + '8CB91E82A3386D280F5D6F7E50E641DF152F7109ED5456B412B1DA197FB71123ACD3A729901' . + 'D1A71874700133107EC50', + 16 + ), // eg. -3 + new BigInteger( + '7F519EADA7BDA81BD826DBA647910F8C4B9346ED8CCDC64E4B1ABD11756DCE1D2074AA263B8' . + '8805CED70355A33B471EE', + 16 + ) + ); + $this->setBasePoint( + new BigInteger( + '18DE98B02DB9A306F2AFCD7235F72A819B80AB12EBD653172476FECD462AABFFC4FF191B946' . + 'A5F54D8D0AA2F418808CC', + 16 + ), + new BigInteger( + '25AB056962D30651A114AFD2755AD336747F93475B7A1FCA3B88F2B6A208CCFE469408584DC' . + '2B2912675BF5B9E582928', + 16 + ) + ); + $this->setOrder(new BigInteger( + '8CB91E82A3386D280F5D6F7E50E641DF152F7109ED5456B31F166E6CAC0425A7CF3AB6AF6B7FC31' . + '03B883202E9046565', + 16 + )); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP512r1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP512r1.php new file mode 100644 index 00000000..5efe5e1a --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP512r1.php @@ -0,0 +1,58 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Crypt\EC\Curves; + +use phpseclib3\Crypt\EC\BaseCurves\Prime; +use phpseclib3\Math\BigInteger; + +class brainpoolP512r1 extends Prime +{ + public function __construct() + { + $this->setModulo(new BigInteger( + 'AADD9DB8DBE9C48B3FD4E6AE33C9FC07CB308DB3B3C9D20ED6639CCA703308717D4D9B009BC' . + '66842AECDA12AE6A380E62881FF2F2D82C68528AA6056583A48F3', + 16 + )); + $this->setCoefficients( + new BigInteger( + '7830A3318B603B89E2327145AC234CC594CBDD8D3DF91610A83441CAEA9863BC2DED5D5AA82' . + '53AA10A2EF1C98B9AC8B57F1117A72BF2C7B9E7C1AC4D77FC94CA', + 16 + ), + new BigInteger( + '3DF91610A83441CAEA9863BC2DED5D5AA8253AA10A2EF1C98B9AC8B57F1117A72BF2C7B9E7C' . + '1AC4D77FC94CADC083E67984050B75EBAE5DD2809BD638016F723', + 16 + ) + ); + $this->setBasePoint( + new BigInteger( + '81AEE4BDD82ED9645A21322E9C4C6A9385ED9F70B5D916C1B43B62EEF4D0098EFF3B1F78E2D' . + '0D48D50D1687B93B97D5F7C6D5047406A5E688B352209BCB9F822', + 16 + ), + new BigInteger( + '7DDE385D566332ECC0EABFA9CF7822FDF209F70024A57B1AA000C55B881F8111B2DCDE494A5' . + 'F485E5BCA4BD88A2763AED1CA2B2FA8F0540678CD1E0F3AD80892', + 16 + ) + ); + $this->setOrder(new BigInteger( + 'AADD9DB8DBE9C48B3FD4E6AE33C9FC07CB308DB3B3C9D20ED6639CCA70330870553E5C414CA' . + '92619418661197FAC10471DB1D381085DDADDB58796829CA90069', + 16 + )); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP512t1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP512t1.php new file mode 100644 index 00000000..745863a6 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP512t1.php @@ -0,0 +1,58 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Crypt\EC\Curves; + +use phpseclib3\Crypt\EC\BaseCurves\Prime; +use phpseclib3\Math\BigInteger; + +class brainpoolP512t1 extends Prime +{ + public function __construct() + { + $this->setModulo(new BigInteger( + 'AADD9DB8DBE9C48B3FD4E6AE33C9FC07CB308DB3B3C9D20ED6639CCA703308717D4D9B009BC' . + '66842AECDA12AE6A380E62881FF2F2D82C68528AA6056583A48F3', + 16 + )); + $this->setCoefficients( + new BigInteger( + 'AADD9DB8DBE9C48B3FD4E6AE33C9FC07CB308DB3B3C9D20ED6639CCA703308717D4D9B009BC' . + '66842AECDA12AE6A380E62881FF2F2D82C68528AA6056583A48F0', + 16 + ), // eg. -3 + new BigInteger( + '7CBBBCF9441CFAB76E1890E46884EAE321F70C0BCB4981527897504BEC3E36A62BCDFA23049' . + '76540F6450085F2DAE145C22553B465763689180EA2571867423E', + 16 + ) + ); + $this->setBasePoint( + new BigInteger( + '640ECE5C12788717B9C1BA06CBC2A6FEBA85842458C56DDE9DB1758D39C0313D82BA51735CD' . + 'B3EA499AA77A7D6943A64F7A3F25FE26F06B51BAA2696FA9035DA', + 16 + ), + new BigInteger( + '5B534BD595F5AF0FA2C892376C84ACE1BB4E3019B71634C01131159CAE03CEE9D9932184BEE' . + 'F216BD71DF2DADF86A627306ECFF96DBB8BACE198B61E00F8B332', + 16 + ) + ); + $this->setOrder(new BigInteger( + 'AADD9DB8DBE9C48B3FD4E6AE33C9FC07CB308DB3B3C9D20ED6639CCA70330870553E5C414CA' . + '92619418661197FAC10471DB1D381085DDADDB58796829CA90069', + 16 + )); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistb233.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistb233.php new file mode 100644 index 00000000..bae12b06 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistb233.php @@ -0,0 +1,18 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Crypt\EC\Curves; + +final class nistb233 extends sect233r1 +{ +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistb409.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistb409.php new file mode 100644 index 00000000..a46153d3 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistb409.php @@ -0,0 +1,18 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Crypt\EC\Curves; + +final class nistb409 extends sect409r1 +{ +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistk163.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistk163.php new file mode 100644 index 00000000..8b263761 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistk163.php @@ -0,0 +1,18 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Crypt\EC\Curves; + +final class nistk163 extends sect163k1 +{ +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistk233.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistk233.php new file mode 100644 index 00000000..69e14138 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistk233.php @@ -0,0 +1,18 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Crypt\EC\Curves; + +final class nistk233 extends sect233k1 +{ +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistk283.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistk283.php new file mode 100644 index 00000000..9e95f10e --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistk283.php @@ -0,0 +1,18 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Crypt\EC\Curves; + +final class nistk283 extends sect283k1 +{ +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistk409.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistk409.php new file mode 100644 index 00000000..06bd9af7 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistk409.php @@ -0,0 +1,18 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Crypt\EC\Curves; + +final class nistk409 extends sect409k1 +{ +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistp192.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistp192.php new file mode 100644 index 00000000..ddead3cf --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistp192.php @@ -0,0 +1,18 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Crypt\EC\Curves; + +final class nistp192 extends secp192r1 +{ +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistp224.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistp224.php new file mode 100644 index 00000000..746571b4 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistp224.php @@ -0,0 +1,18 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Crypt\EC\Curves; + +final class nistp224 extends secp224r1 +{ +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistp256.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistp256.php new file mode 100644 index 00000000..a26c0f99 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistp256.php @@ -0,0 +1,18 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Crypt\EC\Curves; + +final class nistp256 extends secp256r1 +{ +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistp384.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistp384.php new file mode 100644 index 00000000..1f20c02d --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistp384.php @@ -0,0 +1,18 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Crypt\EC\Curves; + +final class nistp384 extends secp384r1 +{ +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistp521.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistp521.php new file mode 100644 index 00000000..86fa0508 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistp521.php @@ -0,0 +1,18 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Crypt\EC\Curves; + +final class nistp521 extends secp521r1 +{ +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistt571.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistt571.php new file mode 100644 index 00000000..7908b38b --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistt571.php @@ -0,0 +1,18 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Crypt\EC\Curves; + +final class nistt571 extends sect571k1 +{ +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/prime192v1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/prime192v1.php new file mode 100644 index 00000000..e9c13cd8 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/prime192v1.php @@ -0,0 +1,18 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Crypt\EC\Curves; + +final class prime192v1 extends secp192r1 +{ +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/prime192v2.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/prime192v2.php new file mode 100644 index 00000000..e3e341f2 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/prime192v2.php @@ -0,0 +1,34 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Crypt\EC\Curves; + +use phpseclib3\Crypt\EC\BaseCurves\Prime; +use phpseclib3\Math\BigInteger; + +class prime192v2 extends Prime +{ + public function __construct() + { + $this->setModulo(new BigInteger('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFFF', 16)); + $this->setCoefficients( + new BigInteger('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFFC', 16), + new BigInteger('CC22D6DFB95C6B25E49C0D6364A4E5980C393AA21668D953', 16) + ); + $this->setBasePoint( + new BigInteger('EEA2BAE7E1497842F2DE7769CFE9C989C072AD696F48034A', 16), + new BigInteger('6574D11D69B6EC7A672BB82A083DF2F2B0847DE970B2DE15', 16) + ); + $this->setOrder(new BigInteger('FFFFFFFFFFFFFFFFFFFFFFFE5FB1A724DC80418648D8DD31', 16)); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/prime192v3.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/prime192v3.php new file mode 100644 index 00000000..1e97992d --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/prime192v3.php @@ -0,0 +1,34 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Crypt\EC\Curves; + +use phpseclib3\Crypt\EC\BaseCurves\Prime; +use phpseclib3\Math\BigInteger; + +class prime192v3 extends Prime +{ + public function __construct() + { + $this->setModulo(new BigInteger('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFFF', 16)); + $this->setCoefficients( + new BigInteger('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFFC', 16), + new BigInteger('22123DC2395A05CAA7423DAECCC94760A7D462256BD56916', 16) + ); + $this->setBasePoint( + new BigInteger('7D29778100C65A1DA1783716588DCE2B8B4AEE8E228F1896', 16), + new BigInteger('38A90F22637337334B49DCB66A6DC8F9978ACA7648A943B0', 16) + ); + $this->setOrder(new BigInteger('FFFFFFFFFFFFFFFFFFFFFFFF7A62D031C83F4294F640EC13', 16)); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/prime239v1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/prime239v1.php new file mode 100644 index 00000000..084be9d7 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/prime239v1.php @@ -0,0 +1,34 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Crypt\EC\Curves; + +use phpseclib3\Crypt\EC\BaseCurves\Prime; +use phpseclib3\Math\BigInteger; + +class prime239v1 extends Prime +{ + public function __construct() + { + $this->setModulo(new BigInteger('7FFFFFFFFFFFFFFFFFFFFFFF7FFFFFFFFFFF8000000000007FFFFFFFFFFF', 16)); + $this->setCoefficients( + new BigInteger('7FFFFFFFFFFFFFFFFFFFFFFF7FFFFFFFFFFF8000000000007FFFFFFFFFFC', 16), + new BigInteger('6B016C3BDCF18941D0D654921475CA71A9DB2FB27D1D37796185C2942C0A', 16) + ); + $this->setBasePoint( + new BigInteger('0FFA963CDCA8816CCC33B8642BEDF905C3D358573D3F27FBBD3B3CB9AAAF', 16), + new BigInteger('7DEBE8E4E90A5DAE6E4054CA530BA04654B36818CE226B39FCCB7B02F1AE', 16) + ); + $this->setOrder(new BigInteger('7FFFFFFFFFFFFFFFFFFFFFFF7FFFFF9E5E9A9F5D9071FBD1522688909D0B', 16)); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/prime239v2.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/prime239v2.php new file mode 100644 index 00000000..21941b83 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/prime239v2.php @@ -0,0 +1,34 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Crypt\EC\Curves; + +use phpseclib3\Crypt\EC\BaseCurves\Prime; +use phpseclib3\Math\BigInteger; + +class prime239v2 extends Prime +{ + public function __construct() + { + $this->setModulo(new BigInteger('7FFFFFFFFFFFFFFFFFFFFFFF7FFFFFFFFFFF8000000000007FFFFFFFFFFF', 16)); + $this->setCoefficients( + new BigInteger('7FFFFFFFFFFFFFFFFFFFFFFF7FFFFFFFFFFF8000000000007FFFFFFFFFFC', 16), + new BigInteger('617FAB6832576CBBFED50D99F0249C3FEE58B94BA0038C7AE84C8C832F2C', 16) + ); + $this->setBasePoint( + new BigInteger('38AF09D98727705120C921BB5E9E26296A3CDCF2F35757A0EAFD87B830E7', 16), + new BigInteger('5B0125E4DBEA0EC7206DA0FC01D9B081329FB555DE6EF460237DFF8BE4BA', 16) + ); + $this->setOrder(new BigInteger('7FFFFFFFFFFFFFFFFFFFFFFF800000CFA7E8594377D414C03821BC582063', 16)); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/prime239v3.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/prime239v3.php new file mode 100644 index 00000000..78c50f06 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/prime239v3.php @@ -0,0 +1,34 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Crypt\EC\Curves; + +use phpseclib3\Crypt\EC\BaseCurves\Prime; +use phpseclib3\Math\BigInteger; + +class prime239v3 extends Prime +{ + public function __construct() + { + $this->setModulo(new BigInteger('7FFFFFFFFFFFFFFFFFFFFFFF7FFFFFFFFFFF8000000000007FFFFFFFFFFF', 16)); + $this->setCoefficients( + new BigInteger('7FFFFFFFFFFFFFFFFFFFFFFF7FFFFFFFFFFF8000000000007FFFFFFFFFFC', 16), + new BigInteger('255705FA2A306654B1F4CB03D6A750A30C250102D4988717D9BA15AB6D3E', 16) + ); + $this->setBasePoint( + new BigInteger('6768AE8E18BB92CFCF005C949AA2C6D94853D0E660BBF854B1C9505FE95A', 16), + new BigInteger('1607E6898F390C06BC1D552BAD226F3B6FCFE48B6E818499AF18E3ED6CF3', 16) + ); + $this->setOrder(new BigInteger('7FFFFFFFFFFFFFFFFFFFFFFF7FFFFF975DEB41B3A6057C3C432146526551', 16)); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/prime256v1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/prime256v1.php new file mode 100644 index 00000000..c72b22e8 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/prime256v1.php @@ -0,0 +1,18 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Crypt\EC\Curves; + +final class prime256v1 extends secp256r1 +{ +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp112r1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp112r1.php new file mode 100644 index 00000000..d1d3194b --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp112r1.php @@ -0,0 +1,34 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Crypt\EC\Curves; + +use phpseclib3\Crypt\EC\BaseCurves\Prime; +use phpseclib3\Math\BigInteger; + +class secp112r1 extends Prime +{ + public function __construct() + { + $this->setModulo(new BigInteger('DB7C2ABF62E35E668076BEAD208B', 16)); + $this->setCoefficients( + new BigInteger('DB7C2ABF62E35E668076BEAD2088', 16), + new BigInteger('659EF8BA043916EEDE8911702B22', 16) + ); + $this->setBasePoint( + new BigInteger('09487239995A5EE76B55F9C2F098', 16), + new BigInteger('A89CE5AF8724C0A23E0E0FF77500', 16) + ); + $this->setOrder(new BigInteger('DB7C2ABF62E35E7628DFAC6561C5', 16)); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp112r2.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp112r2.php new file mode 100644 index 00000000..da44e7fd --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp112r2.php @@ -0,0 +1,35 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Crypt\EC\Curves; + +use phpseclib3\Crypt\EC\BaseCurves\Prime; +use phpseclib3\Math\BigInteger; + +class secp112r2 extends Prime +{ + public function __construct() + { + // same modulo as secp112r1 + $this->setModulo(new BigInteger('DB7C2ABF62E35E668076BEAD208B', 16)); + $this->setCoefficients( + new BigInteger('6127C24C05F38A0AAAF65C0EF02C', 16), + new BigInteger('51DEF1815DB5ED74FCC34C85D709', 16) + ); + $this->setBasePoint( + new BigInteger('4BA30AB5E892B4E1649DD0928643', 16), + new BigInteger('ADCD46F5882E3747DEF36E956E97', 16) + ); + $this->setOrder(new BigInteger('36DF0AAFD8B8D7597CA10520D04B', 16)); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp128r1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp128r1.php new file mode 100644 index 00000000..34456bc0 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp128r1.php @@ -0,0 +1,34 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Crypt\EC\Curves; + +use phpseclib3\Crypt\EC\BaseCurves\Prime; +use phpseclib3\Math\BigInteger; + +class secp128r1 extends Prime +{ + public function __construct() + { + $this->setModulo(new BigInteger('FFFFFFFDFFFFFFFFFFFFFFFFFFFFFFFF', 16)); + $this->setCoefficients( + new BigInteger('FFFFFFFDFFFFFFFFFFFFFFFFFFFFFFFC', 16), + new BigInteger('E87579C11079F43DD824993C2CEE5ED3', 16) + ); + $this->setBasePoint( + new BigInteger('161FF7528B899B2D0C28607CA52C5B86', 16), + new BigInteger('CF5AC8395BAFEB13C02DA292DDED7A83', 16) + ); + $this->setOrder(new BigInteger('FFFFFFFE0000000075A30D1B9038A115', 16)); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp128r2.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp128r2.php new file mode 100644 index 00000000..e102c340 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp128r2.php @@ -0,0 +1,35 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Crypt\EC\Curves; + +use phpseclib3\Crypt\EC\BaseCurves\Prime; +use phpseclib3\Math\BigInteger; + +class secp128r2 extends Prime +{ + public function __construct() + { + // same as secp128r1 + $this->setModulo(new BigInteger('FFFFFFFDFFFFFFFFFFFFFFFFFFFFFFFF', 16)); + $this->setCoefficients( + new BigInteger('D6031998D1B3BBFEBF59CC9BBFF9AEE1', 16), + new BigInteger('5EEEFCA380D02919DC2C6558BB6D8A5D', 16) + ); + $this->setBasePoint( + new BigInteger('7B6AA5D85E572983E6FB32A7CDEBC140', 16), + new BigInteger('27B6916A894D3AEE7106FE805FC34B44', 16) + ); + $this->setOrder(new BigInteger('3FFFFFFF7FFFFFFFBE0024720613B5A3', 16)); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp160k1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp160k1.php new file mode 100644 index 00000000..c6a33344 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp160k1.php @@ -0,0 +1,46 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Crypt\EC\Curves; + +use phpseclib3\Crypt\EC\BaseCurves\KoblitzPrime; +use phpseclib3\Math\BigInteger; + +class secp160k1 extends KoblitzPrime +{ + public function __construct() + { + // same as secp160r2 + $this->setModulo(new BigInteger('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFAC73', 16)); + $this->setCoefficients( + new BigInteger('0000000000000000000000000000000000000000', 16), + new BigInteger('0000000000000000000000000000000000000007', 16) + ); + $this->setBasePoint( + new BigInteger('3B4C382CE37AA192A4019E763036F4F5DD4D7EBB', 16), + new BigInteger('938CF935318FDCED6BC28286531733C3F03C4FEE', 16) + ); + $this->setOrder(new BigInteger('0100000000000000000001B8FA16DFAB9ACA16B6B3', 16)); + + $this->basis = []; + $this->basis[] = [ + 'a' => new BigInteger('0096341F1138933BC2F505', -16), + 'b' => new BigInteger('FF6E9D0418C67BB8D5F562', -16) + ]; + $this->basis[] = [ + 'a' => new BigInteger('01BDCB3A09AAAABEAFF4A8', -16), + 'b' => new BigInteger('04D12329FF0EF498EA67', -16) + ]; + $this->beta = $this->factory->newInteger(new BigInteger('645B7345A143464942CC46D7CF4D5D1E1E6CBB68', -16)); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp160r1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp160r1.php new file mode 100644 index 00000000..af468774 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp160r1.php @@ -0,0 +1,34 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Crypt\EC\Curves; + +use phpseclib3\Crypt\EC\BaseCurves\Prime; +use phpseclib3\Math\BigInteger; + +class secp160r1 extends Prime +{ + public function __construct() + { + $this->setModulo(new BigInteger('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7FFFFFFF', 16)); + $this->setCoefficients( + new BigInteger('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7FFFFFFC', 16), + new BigInteger('1C97BEFC54BD7A8B65ACF89F81D4D4ADC565FA45', 16) + ); + $this->setBasePoint( + new BigInteger('4A96B5688EF573284664698968C38BB913CBFC82', 16), + new BigInteger('23A628553168947D59DCC912042351377AC5FB32', 16) + ); + $this->setOrder(new BigInteger('0100000000000000000001F4C8F927AED3CA752257', 16)); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp160r2.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp160r2.php new file mode 100644 index 00000000..9bd23d23 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp160r2.php @@ -0,0 +1,35 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Crypt\EC\Curves; + +use phpseclib3\Crypt\EC\BaseCurves\Prime; +use phpseclib3\Math\BigInteger; + +class secp160r2 extends Prime +{ + public function __construct() + { + // same as secp160k1 + $this->setModulo(new BigInteger('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFAC73', 16)); + $this->setCoefficients( + new BigInteger('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFAC70', 16), + new BigInteger('B4E134D3FB59EB8BAB57274904664D5AF50388BA', 16) + ); + $this->setBasePoint( + new BigInteger('52DCB034293A117E1F4FF11B30F7199D3144CE6D', 16), + new BigInteger('FEAFFEF2E331F296E071FA0DF9982CFEA7D43F2E', 16) + ); + $this->setOrder(new BigInteger('0100000000000000000000351EE786A818F3A1A16B', 16)); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp192k1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp192k1.php new file mode 100644 index 00000000..79ff2e09 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp192k1.php @@ -0,0 +1,45 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Crypt\EC\Curves; + +use phpseclib3\Crypt\EC\BaseCurves\KoblitzPrime; +use phpseclib3\Math\BigInteger; + +class secp192k1 extends KoblitzPrime +{ + public function __construct() + { + $this->setModulo(new BigInteger('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFEE37', 16)); + $this->setCoefficients( + new BigInteger('000000000000000000000000000000000000000000000000', 16), + new BigInteger('000000000000000000000000000000000000000000000003', 16) + ); + $this->setBasePoint( + new BigInteger('DB4FF10EC057E9AE26B07D0280B7F4341DA5D1B1EAE06C7D', 16), + new BigInteger('9B2F2F6D9C5628A7844163D015BE86344082AA88D95E2F9D', 16) + ); + $this->setOrder(new BigInteger('FFFFFFFFFFFFFFFFFFFFFFFE26F2FC170F69466A74DEFD8D', 16)); + + $this->basis = []; + $this->basis[] = [ + 'a' => new BigInteger('00B3FB3400DEC5C4ADCEB8655C', -16), + 'b' => new BigInteger('8EE96418CCF4CFC7124FDA0F', -16) + ]; + $this->basis[] = [ + 'a' => new BigInteger('01D90D03E8F096B9948B20F0A9', -16), + 'b' => new BigInteger('42E49819ABBA9474E1083F6B', -16) + ]; + $this->beta = $this->factory->newInteger(new BigInteger('447A96E6C647963E2F7809FEAAB46947F34B0AA3CA0BBA74', -16)); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp192r1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp192r1.php new file mode 100644 index 00000000..83ab1c70 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp192r1.php @@ -0,0 +1,78 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Crypt\EC\Curves; + +use phpseclib3\Crypt\EC\BaseCurves\Prime; +use phpseclib3\Math\BigInteger; + +class secp192r1 extends Prime +{ + public function __construct() + { + $modulo = new BigInteger('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFFF', 16); + $this->setModulo($modulo); + + // algorithm 2.27 from http://diamond.boisestate.edu/~liljanab/MATH308/GuideToECC.pdf#page=66 + /* in theory this should be faster than regular modular reductions save for one small issue. + to convert to / from base-2**8 with BCMath you have to call bcmul() and bcdiv() a lot. + to convert to / from base-2**8 with PHP64 you have to call base256_rshift() a lot. + in short, converting to / from base-2**8 is pretty expensive and that expense is + enough to offset whatever else might be gained by a simplified reduction algorithm. + now, if PHP supported unsigned integers things might be different. no bit-shifting + would be required for the PHP engine and it'd be a lot faster. but as is, BigInteger + uses base-2**31 or base-2**26 depending on whether or not the system is has a 32-bit + or a 64-bit OS. + */ + /* + $m_length = $this->getLengthInBytes(); + $this->setReduction(function($c) use ($m_length) { + $cBytes = $c->toBytes(); + $className = $this->className; + + if (strlen($cBytes) > 2 * $m_length) { + list(, $r) = $c->divide($className::$modulo); + return $r; + } + + $c = str_pad($cBytes, 48, "\0", STR_PAD_LEFT); + $c = array_reverse(str_split($c, 8)); + + $null = "\0\0\0\0\0\0\0\0"; + $s1 = new BigInteger($c[2] . $c[1] . $c[0], 256); + $s2 = new BigInteger($null . $c[3] . $c[3], 256); + $s3 = new BigInteger($c[4] . $c[4] . $null, 256); + $s4 = new BigInteger($c[5] . $c[5] . $c[5], 256); + + $r = $s1->add($s2)->add($s3)->add($s4); + while ($r->compare($className::$modulo) >= 0) { + $r = $r->subtract($className::$modulo); + } + + return $r; + }); + */ + + $this->setCoefficients( + new BigInteger('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFFC', 16), + new BigInteger('64210519E59C80E70FA7E9AB72243049FEB8DEECC146B9B1', 16) + ); + $this->setBasePoint( + new BigInteger('188DA80EB03090F67CBF20EB43A18800F4FF0AFD82FF1012', 16), + new BigInteger('07192B95FFC8DA78631011ED6B24CDD573F977A11E794811', 16) + ); + $this->setOrder(new BigInteger('FFFFFFFFFFFFFFFFFFFFFFFF99DEF836146BC9B1B4D22831', 16)); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp224k1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp224k1.php new file mode 100644 index 00000000..79a5c541 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp224k1.php @@ -0,0 +1,45 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Crypt\EC\Curves; + +use phpseclib3\Crypt\EC\BaseCurves\KoblitzPrime; +use phpseclib3\Math\BigInteger; + +class secp224k1 extends KoblitzPrime +{ + public function __construct() + { + $this->setModulo(new BigInteger('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFE56D', 16)); + $this->setCoefficients( + new BigInteger('00000000000000000000000000000000000000000000000000000000', 16), + new BigInteger('00000000000000000000000000000000000000000000000000000005', 16) + ); + $this->setBasePoint( + new BigInteger('A1455B334DF099DF30FC28A169A467E9E47075A90F7E650EB6B7A45C', 16), + new BigInteger('7E089FED7FBA344282CAFBD6F7E319F7C0B0BD59E2CA4BDB556D61A5', 16) + ); + $this->setOrder(new BigInteger('010000000000000000000000000001DCE8D2EC6184CAF0A971769FB1F7', 16)); + + $this->basis = []; + $this->basis[] = [ + 'a' => new BigInteger('00B8ADF1378A6EB73409FA6C9C637D', -16), + 'b' => new BigInteger('94730F82B358A3776A826298FA6F', -16) + ]; + $this->basis[] = [ + 'a' => new BigInteger('01DCE8D2EC6184CAF0A972769FCC8B', -16), + 'b' => new BigInteger('4D2100BA3DC75AAB747CCF355DEC', -16) + ]; + $this->beta = $this->factory->newInteger(new BigInteger('01F178FFA4B17C89E6F73AECE2AAD57AF4C0A748B63C830947B27E04', -16)); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp224r1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp224r1.php new file mode 100644 index 00000000..a9e474a3 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp224r1.php @@ -0,0 +1,34 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Crypt\EC\Curves; + +use phpseclib3\Crypt\EC\BaseCurves\Prime; +use phpseclib3\Math\BigInteger; + +class secp224r1 extends Prime +{ + public function __construct() + { + $this->setModulo(new BigInteger('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF000000000000000000000001', 16)); + $this->setCoefficients( + new BigInteger('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFE', 16), + new BigInteger('B4050A850C04B3ABF54132565044B0B7D7BFD8BA270B39432355FFB4', 16) + ); + $this->setBasePoint( + new BigInteger('B70E0CBD6BB4BF7F321390B94A03C1D356C21122343280D6115C1D21', 16), + new BigInteger('BD376388B5F723FB4C22DFE6CD4375A05A07476444D5819985007E34', 16) + ); + $this->setOrder(new BigInteger('FFFFFFFFFFFFFFFFFFFFFFFFFFFF16A2E0B8F03E13DD29455C5C2A3D', 16)); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp256k1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp256k1.php new file mode 100644 index 00000000..462e7a1c --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp256k1.php @@ -0,0 +1,49 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Crypt\EC\Curves; + +//use phpseclib3\Crypt\EC\BaseCurves\Prime; +use phpseclib3\Crypt\EC\BaseCurves\KoblitzPrime; +use phpseclib3\Math\BigInteger; + +//class secp256k1 extends Prime +class secp256k1 extends KoblitzPrime +{ + public function __construct() + { + $this->setModulo(new BigInteger('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F', 16)); + $this->setCoefficients( + new BigInteger('0000000000000000000000000000000000000000000000000000000000000000', 16), + new BigInteger('0000000000000000000000000000000000000000000000000000000000000007', 16) + ); + $this->setOrder(new BigInteger('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141', 16)); + $this->setBasePoint( + new BigInteger('79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798', 16), + new BigInteger('483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8', 16) + ); + + $this->basis = []; + $this->basis[] = [ + 'a' => new BigInteger('3086D221A7D46BCDE86C90E49284EB15', -16), + 'b' => new BigInteger('FF1BBC8129FEF177D790AB8056F5401B3D', -16) + ]; + $this->basis[] = [ + 'a' => new BigInteger('114CA50F7A8E2F3F657C1108D9D44CFD8', -16), + 'b' => new BigInteger('3086D221A7D46BCDE86C90E49284EB15', -16) + ]; + $this->beta = $this->factory->newInteger(new BigInteger('7AE96A2B657C07106E64479EAC3434E99CF0497512F58995C1396C28719501EE', -16)); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp256r1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp256r1.php new file mode 100644 index 00000000..9003373c --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp256r1.php @@ -0,0 +1,34 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Crypt\EC\Curves; + +use phpseclib3\Crypt\EC\BaseCurves\Prime; +use phpseclib3\Math\BigInteger; + +class secp256r1 extends Prime +{ + public function __construct() + { + $this->setModulo(new BigInteger('FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF', 16)); + $this->setCoefficients( + new BigInteger('FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC', 16), + new BigInteger('5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B', 16) + ); + $this->setBasePoint( + new BigInteger('6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296', 16), + new BigInteger('4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5', 16) + ); + $this->setOrder(new BigInteger('FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551', 16)); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp384r1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp384r1.php new file mode 100644 index 00000000..98764a34 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp384r1.php @@ -0,0 +1,52 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Crypt\EC\Curves; + +use phpseclib3\Crypt\EC\BaseCurves\Prime; +use phpseclib3\Math\BigInteger; + +class secp384r1 extends Prime +{ + public function __construct() + { + $this->setModulo(new BigInteger( + 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFF', + 16 + )); + $this->setCoefficients( + new BigInteger( + 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFC', + 16 + ), + new BigInteger( + 'B3312FA7E23EE7E4988E056BE3F82D19181D9C6EFE8141120314088F5013875AC656398D8A2ED19D2A85C8EDD3EC2AEF', + 16 + ) + ); + $this->setBasePoint( + new BigInteger( + 'AA87CA22BE8B05378EB1C71EF320AD746E1D3B628BA79B9859F741E082542A385502F25DBF55296C3A545E3872760AB7', + 16 + ), + new BigInteger( + '3617DE4A96262C6F5D9E98BF9292DC29F8F41DBD289A147CE9DA3113B5F0B8C00A60B1CE1D7E819D7A431D7C90EA0E5F', + 16 + ) + ); + $this->setOrder(new BigInteger( + 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC7634D81F4372DDF581A0DB248B0A77AECEC196ACCC52973', + 16 + )); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp521r1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp521r1.php new file mode 100644 index 00000000..b89a4ea7 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp521r1.php @@ -0,0 +1,46 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Crypt\EC\Curves; + +use phpseclib3\Crypt\EC\BaseCurves\Prime; +use phpseclib3\Math\BigInteger; + +class secp521r1 extends Prime +{ + public function __construct() + { + $this->setModulo(new BigInteger('01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF' . + 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF' . + 'FFFF', 16)); + $this->setCoefficients( + new BigInteger('01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF' . + 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF' . + 'FFFC', 16), + new BigInteger('0051953EB9618E1C9A1F929A21A0B68540EEA2DA725B99B315F3B8B489918EF1' . + '09E156193951EC7E937B1652C0BD3BB1BF073573DF883D2C34F1EF451FD46B50' . + '3F00', 16) + ); + $this->setBasePoint( + new BigInteger('00C6858E06B70404E9CD9E3ECB662395B4429C648139053FB521F828AF606B4D' . + '3DBAA14B5E77EFE75928FE1DC127A2FFA8DE3348B3C1856A429BF97E7E31C2E5' . + 'BD66', 16), + new BigInteger('011839296A789A3BC0045C8A5FB42C7D1BD998F54449579B446817AFBD17273E' . + '662C97EE72995EF42640C550B9013FAD0761353C7086A272C24088BE94769FD1' . + '6650', 16) + ); + $this->setOrder(new BigInteger('01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF' . + 'FFFA51868783BF2F966B7FCC0148F709A5D03BB5C9B8899C47AEBB6FB71E9138' . + '6409', 16)); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect113r1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect113r1.php new file mode 100644 index 00000000..77ec7603 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect113r1.php @@ -0,0 +1,34 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Crypt\EC\Curves; + +use phpseclib3\Crypt\EC\BaseCurves\Binary; +use phpseclib3\Math\BigInteger; + +class sect113r1 extends Binary +{ + public function __construct() + { + $this->setModulo(113, 9, 0); + $this->setCoefficients( + '003088250CA6E7C7FE649CE85820F7', + '00E8BEE4D3E2260744188BE0E9C723' + ); + $this->setBasePoint( + '009D73616F35F4AB1407D73562C10F', + '00A52830277958EE84D1315ED31886' + ); + $this->setOrder(new BigInteger('0100000000000000D9CCEC8A39E56F', 16)); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect113r2.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect113r2.php new file mode 100644 index 00000000..2185d60e --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect113r2.php @@ -0,0 +1,34 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Crypt\EC\Curves; + +use phpseclib3\Crypt\EC\BaseCurves\Binary; +use phpseclib3\Math\BigInteger; + +class sect113r2 extends Binary +{ + public function __construct() + { + $this->setModulo(113, 9, 0); + $this->setCoefficients( + '00689918DBEC7E5A0DD6DFC0AA55C7', + '0095E9A9EC9B297BD4BF36E059184F' + ); + $this->setBasePoint( + '01A57A6A7B26CA5EF52FCDB8164797', + '00B3ADC94ED1FE674C06E695BABA1D' + ); + $this->setOrder(new BigInteger('010000000000000108789B2496AF93', 16)); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect131r1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect131r1.php new file mode 100644 index 00000000..1365cb60 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect131r1.php @@ -0,0 +1,34 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Crypt\EC\Curves; + +use phpseclib3\Crypt\EC\BaseCurves\Binary; +use phpseclib3\Math\BigInteger; + +class sect131r1 extends Binary +{ + public function __construct() + { + $this->setModulo(131, 8, 3, 2, 0); + $this->setCoefficients( + '07A11B09A76B562144418FF3FF8C2570B8', + '0217C05610884B63B9C6C7291678F9D341' + ); + $this->setBasePoint( + '0081BAF91FDF9833C40F9C181343638399', + '078C6E7EA38C001F73C8134B1B4EF9E150' + ); + $this->setOrder(new BigInteger('0400000000000000023123953A9464B54D', 16)); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect131r2.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect131r2.php new file mode 100644 index 00000000..93c11b2a --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect131r2.php @@ -0,0 +1,34 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Crypt\EC\Curves; + +use phpseclib3\Crypt\EC\BaseCurves\Binary; +use phpseclib3\Math\BigInteger; + +class sect131r2 extends Binary +{ + public function __construct() + { + $this->setModulo(131, 8, 3, 2, 0); + $this->setCoefficients( + '03E5A88919D7CAFCBF415F07C2176573B2', + '04B8266A46C55657AC734CE38F018F2192' + ); + $this->setBasePoint( + '0356DCD8F2F95031AD652D23951BB366A8', + '0648F06D867940A5366D9E265DE9EB240F' + ); + $this->setOrder(new BigInteger('0400000000000000016954A233049BA98F', 16)); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect163k1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect163k1.php new file mode 100644 index 00000000..3c8574bb --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect163k1.php @@ -0,0 +1,34 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Crypt\EC\Curves; + +use phpseclib3\Crypt\EC\BaseCurves\Binary; +use phpseclib3\Math\BigInteger; + +class sect163k1 extends Binary +{ + public function __construct() + { + $this->setModulo(163, 7, 6, 3, 0); + $this->setCoefficients( + '000000000000000000000000000000000000000001', + '000000000000000000000000000000000000000001' + ); + $this->setBasePoint( + '02FE13C0537BBC11ACAA07D793DE4E6D5E5C94EEE8', + '0289070FB05D38FF58321F2E800536D538CCDAA3D9' + ); + $this->setOrder(new BigInteger('04000000000000000000020108A2E0CC0D99F8A5EF', 16)); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect163r1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect163r1.php new file mode 100644 index 00000000..26afd87e --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect163r1.php @@ -0,0 +1,34 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Crypt\EC\Curves; + +use phpseclib3\Crypt\EC\BaseCurves\Binary; +use phpseclib3\Math\BigInteger; + +class sect163r1 extends Binary +{ + public function __construct() + { + $this->setModulo(163, 7, 6, 3, 0); + $this->setCoefficients( + '07B6882CAAEFA84F9554FF8428BD88E246D2782AE2', + '0713612DCDDCB40AAB946BDA29CA91F73AF958AFD9' + ); + $this->setBasePoint( + '0369979697AB43897789566789567F787A7876A654', + '00435EDB42EFAFB2989D51FEFCE3C80988F41FF883' + ); + $this->setOrder(new BigInteger('03FFFFFFFFFFFFFFFFFFFF48AAB689C29CA710279B', 16)); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect163r2.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect163r2.php new file mode 100644 index 00000000..38f94661 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect163r2.php @@ -0,0 +1,34 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Crypt\EC\Curves; + +use phpseclib3\Crypt\EC\BaseCurves\Binary; +use phpseclib3\Math\BigInteger; + +class sect163r2 extends Binary +{ + public function __construct() + { + $this->setModulo(163, 7, 6, 3, 0); + $this->setCoefficients( + '000000000000000000000000000000000000000001', + '020A601907B8C953CA1481EB10512F78744A3205FD' + ); + $this->setBasePoint( + '03F0EBA16286A2D57EA0991168D4994637E8343E36', + '00D51FBC6C71A0094FA2CDD545B11C5C0C797324F1' + ); + $this->setOrder(new BigInteger('040000000000000000000292FE77E70C12A4234C33', 16)); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect193r1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect193r1.php new file mode 100644 index 00000000..951f261e --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect193r1.php @@ -0,0 +1,34 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Crypt\EC\Curves; + +use phpseclib3\Crypt\EC\BaseCurves\Binary; +use phpseclib3\Math\BigInteger; + +class sect193r1 extends Binary +{ + public function __construct() + { + $this->setModulo(193, 15, 0); + $this->setCoefficients( + '0017858FEB7A98975169E171F77B4087DE098AC8A911DF7B01', + '00FDFB49BFE6C3A89FACADAA7A1E5BBC7CC1C2E5D831478814' + ); + $this->setBasePoint( + '01F481BC5F0FF84A74AD6CDF6FDEF4BF6179625372D8C0C5E1', + '0025E399F2903712CCF3EA9E3A1AD17FB0B3201B6AF7CE1B05' + ); + $this->setOrder(new BigInteger('01000000000000000000000000C7F34A778F443ACC920EBA49', 16)); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect193r2.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect193r2.php new file mode 100644 index 00000000..e3ff47ac --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect193r2.php @@ -0,0 +1,34 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Crypt\EC\Curves; + +use phpseclib3\Crypt\EC\BaseCurves\Binary; +use phpseclib3\Math\BigInteger; + +class sect193r2 extends Binary +{ + public function __construct() + { + $this->setModulo(193, 15, 0); + $this->setCoefficients( + '0163F35A5137C2CE3EA6ED8667190B0BC43ECD69977702709B', + '00C9BB9E8927D4D64C377E2AB2856A5B16E3EFB7F61D4316AE' + ); + $this->setBasePoint( + '00D9B67D192E0367C803F39E1A7E82CA14A651350AAE617E8F', + '01CE94335607C304AC29E7DEFBD9CA01F596F927224CDECF6C' + ); + $this->setOrder(new BigInteger('010000000000000000000000015AAB561B005413CCD4EE99D5', 16)); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect233k1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect233k1.php new file mode 100644 index 00000000..eea3f7ad --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect233k1.php @@ -0,0 +1,34 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Crypt\EC\Curves; + +use phpseclib3\Crypt\EC\BaseCurves\Binary; +use phpseclib3\Math\BigInteger; + +class sect233k1 extends Binary +{ + public function __construct() + { + $this->setModulo(233, 74, 0); + $this->setCoefficients( + '000000000000000000000000000000000000000000000000000000000000', + '000000000000000000000000000000000000000000000000000000000001' + ); + $this->setBasePoint( + '017232BA853A7E731AF129F22FF4149563A419C26BF50A4C9D6EEFAD6126', + '01DB537DECE819B7F70F555A67C427A8CD9BF18AEB9B56E0C11056FAE6A3' + ); + $this->setOrder(new BigInteger('8000000000000000000000000000069D5BB915BCD46EFB1AD5F173ABDF', 16)); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect233r1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect233r1.php new file mode 100644 index 00000000..68219f0e --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect233r1.php @@ -0,0 +1,34 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Crypt\EC\Curves; + +use phpseclib3\Crypt\EC\BaseCurves\Binary; +use phpseclib3\Math\BigInteger; + +class sect233r1 extends Binary +{ + public function __construct() + { + $this->setModulo(233, 74, 0); + $this->setCoefficients( + '000000000000000000000000000000000000000000000000000000000001', + '0066647EDE6C332C7F8C0923BB58213B333B20E9CE4281FE115F7D8F90AD' + ); + $this->setBasePoint( + '00FAC9DFCBAC8313BB2139F1BB755FEF65BC391F8B36F8F8EB7371FD558B', + '01006A08A41903350678E58528BEBF8A0BEFF867A7CA36716F7E01F81052' + ); + $this->setOrder(new BigInteger('01000000000000000000000000000013E974E72F8A6922031D2603CFE0D7', 16)); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect239k1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect239k1.php new file mode 100644 index 00000000..0e6994ba --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect239k1.php @@ -0,0 +1,34 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Crypt\EC\Curves; + +use phpseclib3\Crypt\EC\BaseCurves\Binary; +use phpseclib3\Math\BigInteger; + +class sect239k1 extends Binary +{ + public function __construct() + { + $this->setModulo(239, 158, 0); + $this->setCoefficients( + '000000000000000000000000000000000000000000000000000000000000', + '000000000000000000000000000000000000000000000000000000000001' + ); + $this->setBasePoint( + '29A0B6A887A983E9730988A68727A8B2D126C44CC2CC7B2A6555193035DC', + '76310804F12E549BDB011C103089E73510ACB275FC312A5DC6B76553F0CA' + ); + $this->setOrder(new BigInteger('2000000000000000000000000000005A79FEC67CB6E91F1C1DA800E478A5', 16)); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect283k1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect283k1.php new file mode 100644 index 00000000..279c24aa --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect283k1.php @@ -0,0 +1,34 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Crypt\EC\Curves; + +use phpseclib3\Crypt\EC\BaseCurves\Binary; +use phpseclib3\Math\BigInteger; + +class sect283k1 extends Binary +{ + public function __construct() + { + $this->setModulo(283, 12, 7, 5, 0); + $this->setCoefficients( + '000000000000000000000000000000000000000000000000000000000000000000000000', + '000000000000000000000000000000000000000000000000000000000000000000000001' + ); + $this->setBasePoint( + '0503213F78CA44883F1A3B8162F188E553CD265F23C1567A16876913B0C2AC2458492836', + '01CCDA380F1C9E318D90F95D07E5426FE87E45C0E8184698E45962364E34116177DD2259' + ); + $this->setOrder(new BigInteger('01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE9AE2ED07577265DFF7F94451E061E163C61', 16)); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect283r1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect283r1.php new file mode 100644 index 00000000..e44a6076 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect283r1.php @@ -0,0 +1,34 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Crypt\EC\Curves; + +use phpseclib3\Crypt\EC\BaseCurves\Binary; +use phpseclib3\Math\BigInteger; + +class sect283r1 extends Binary +{ + public function __construct() + { + $this->setModulo(283, 12, 7, 5, 0); + $this->setCoefficients( + '000000000000000000000000000000000000000000000000000000000000000000000001', + '027B680AC8B8596DA5A4AF8A19A0303FCA97FD7645309FA2A581485AF6263E313B79A2F5' + ); + $this->setBasePoint( + '05F939258DB7DD90E1934F8C70B0DFEC2EED25B8557EAC9C80E2E198F8CDBECD86B12053', + '03676854FE24141CB98FE6D4B20D02B4516FF702350EDDB0826779C813F0DF45BE8112F4' + ); + $this->setOrder(new BigInteger('03FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEF90399660FC938A90165B042A7CEFADB307', 16)); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect409k1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect409k1.php new file mode 100644 index 00000000..1fe329d8 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect409k1.php @@ -0,0 +1,38 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Crypt\EC\Curves; + +use phpseclib3\Crypt\EC\BaseCurves\Binary; +use phpseclib3\Math\BigInteger; + +class sect409k1 extends Binary +{ + public function __construct() + { + $this->setModulo(409, 87, 0); + $this->setCoefficients( + '00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', + '00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001' + ); + $this->setBasePoint( + '0060F05F658F49C1AD3AB1890F7184210EFD0987E307C84C27ACCFB8F9F67CC2C460189EB5AAAA62EE222EB1B35540CFE9023746', + '01E369050B7C4E42ACBA1DACBF04299C3460782F918EA427E6325165E9EA10E3DA5F6C42E9C55215AA9CA27A5863EC48D8E0286B' + ); + $this->setOrder(new BigInteger( + '7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE5F' . + '83B2D4EA20400EC4557D5ED3E3E7CA5B4B5C83B8E01E5FCF', + 16 + )); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect409r1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect409r1.php new file mode 100644 index 00000000..3e209ef8 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect409r1.php @@ -0,0 +1,38 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Crypt\EC\Curves; + +use phpseclib3\Crypt\EC\BaseCurves\Binary; +use phpseclib3\Math\BigInteger; + +class sect409r1 extends Binary +{ + public function __construct() + { + $this->setModulo(409, 87, 0); + $this->setCoefficients( + '00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001', + '0021A5C2C8EE9FEB5C4B9A753B7B476B7FD6422EF1F3DD674761FA99D6AC27C8A9A197B272822F6CD57A55AA4F50AE317B13545F' + ); + $this->setBasePoint( + '015D4860D088DDB3496B0C6064756260441CDE4AF1771D4DB01FFE5B34E59703DC255A868A1180515603AEAB60794E54BB7996A7', + '0061B1CFAB6BE5F32BBFA78324ED106A7636B9C5A7BD198D0158AA4F5488D08F38514F1FDF4B4F40D2181B3681C364BA0273C706' + ); + $this->setOrder(new BigInteger( + '010000000000000000000000000000000000000000000000000001E2' . + 'AAD6A612F33307BE5FA47C3C9E052F838164CD37D9A21173', + 16 + )); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect571k1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect571k1.php new file mode 100644 index 00000000..3c54eabd --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect571k1.php @@ -0,0 +1,42 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Crypt\EC\Curves; + +use phpseclib3\Crypt\EC\BaseCurves\Binary; +use phpseclib3\Math\BigInteger; + +class sect571k1 extends Binary +{ + public function __construct() + { + $this->setModulo(571, 10, 5, 2, 0); + $this->setCoefficients( + '000000000000000000000000000000000000000000000000000000000000000000000000' . + '000000000000000000000000000000000000000000000000000000000000000000000000', + '000000000000000000000000000000000000000000000000000000000000000000000000' . + '000000000000000000000000000000000000000000000000000000000000000000000001' + ); + $this->setBasePoint( + '026EB7A859923FBC82189631F8103FE4AC9CA2970012D5D46024804801841CA443709584' . + '93B205E647DA304DB4CEB08CBBD1BA39494776FB988B47174DCA88C7E2945283A01C8972', + '0349DC807F4FBF374F4AEADE3BCA95314DD58CEC9F307A54FFC61EFC006D8A2C9D4979C0' . + 'AC44AEA74FBEBBB9F772AEDCB620B01A7BA7AF1B320430C8591984F601CD4C143EF1C7A3' + ); + $this->setOrder(new BigInteger( + '020000000000000000000000000000000000000000000000000000000000000000000000' . + '131850E1F19A63E4B391A8DB917F4138B630D84BE5D639381E91DEB45CFE778F637C1001', + 16 + )); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect571r1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect571r1.php new file mode 100644 index 00000000..172c1af9 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect571r1.php @@ -0,0 +1,42 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Crypt\EC\Curves; + +use phpseclib3\Crypt\EC\BaseCurves\Binary; +use phpseclib3\Math\BigInteger; + +class sect571r1 extends Binary +{ + public function __construct() + { + $this->setModulo(571, 10, 5, 2, 0); + $this->setCoefficients( + '000000000000000000000000000000000000000000000000000000000000000000000000' . + '000000000000000000000000000000000000000000000000000000000000000000000001', + '02F40E7E2221F295DE297117B7F3D62F5C6A97FFCB8CEFF1CD6BA8CE4A9A18AD84FFABBD' . + '8EFA59332BE7AD6756A66E294AFD185A78FF12AA520E4DE739BACA0C7FFEFF7F2955727A' + ); + $this->setBasePoint( + '0303001D34B856296C16C0D40D3CD7750A93D1D2955FA80AA5F40FC8DB7B2ABDBDE53950' . + 'F4C0D293CDD711A35B67FB1499AE60038614F1394ABFA3B4C850D927E1E7769C8EEC2D19', + '037BF27342DA639B6DCCFFFEB73D69D78C6C27A6009CBBCA1980F8533921E8A684423E43' . + 'BAB08A576291AF8F461BB2A8B3531D2F0485C19B16E2F1516E23DD3C1A4827AF1B8AC15B' + ); + $this->setOrder(new BigInteger( + '03FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF' . + 'E661CE18FF55987308059B186823851EC7DD9CA1161DE93D5174D66E8382E9BB2FE84E47', + 16 + )); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/Common.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/Common.php new file mode 100644 index 00000000..63402b4a --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/Common.php @@ -0,0 +1,549 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\Crypt\EC\Formats\Keys; + +use phpseclib3\Common\Functions\Strings; +use phpseclib3\Crypt\EC\BaseCurves\Base as BaseCurve; +use phpseclib3\Crypt\EC\BaseCurves\Binary as BinaryCurve; +use phpseclib3\Crypt\EC\BaseCurves\Prime as PrimeCurve; +use phpseclib3\Crypt\EC\BaseCurves\TwistedEdwards as TwistedEdwardsCurve; +use phpseclib3\Exception\UnsupportedCurveException; +use phpseclib3\File\ASN1; +use phpseclib3\File\ASN1\Maps; +use phpseclib3\Math\BigInteger; + +/** + * Generic EC Key Parsing Helper functions + * + * @author Jim Wigginton + */ +trait Common +{ + /** + * Curve OIDs + * + * @var array + */ + private static $curveOIDs = []; + + /** + * Child OIDs loaded + * + * @var bool + */ + protected static $childOIDsLoaded = false; + + /** + * Use Named Curves + * + * @var bool + */ + private static $useNamedCurves = true; + + /** + * Initialize static variables + */ + private static function initialize_static_variables() + { + if (empty(self::$curveOIDs)) { + // the sec* curves are from the standards for efficient cryptography group + // sect* curves are curves over binary finite fields + // secp* curves are curves over prime finite fields + // sec*r* curves are regular curves; sec*k* curves are koblitz curves + // brainpool*r* curves are regular prime finite field curves + // brainpool*t* curves are twisted versions of the brainpool*r* curves + self::$curveOIDs = [ + 'prime192v1' => '1.2.840.10045.3.1.1', // J.5.1, example 1 (aka secp192r1) + 'prime192v2' => '1.2.840.10045.3.1.2', // J.5.1, example 2 + 'prime192v3' => '1.2.840.10045.3.1.3', // J.5.1, example 3 + 'prime239v1' => '1.2.840.10045.3.1.4', // J.5.2, example 1 + 'prime239v2' => '1.2.840.10045.3.1.5', // J.5.2, example 2 + 'prime239v3' => '1.2.840.10045.3.1.6', // J.5.2, example 3 + 'prime256v1' => '1.2.840.10045.3.1.7', // J.5.3, example 1 (aka secp256r1) + + // https://tools.ietf.org/html/rfc5656#section-10 + 'nistp256' => '1.2.840.10045.3.1.7', // aka secp256r1 + 'nistp384' => '1.3.132.0.34', // aka secp384r1 + 'nistp521' => '1.3.132.0.35', // aka secp521r1 + + 'nistk163' => '1.3.132.0.1', // aka sect163k1 + 'nistp192' => '1.2.840.10045.3.1.1', // aka secp192r1 + 'nistp224' => '1.3.132.0.33', // aka secp224r1 + 'nistk233' => '1.3.132.0.26', // aka sect233k1 + 'nistb233' => '1.3.132.0.27', // aka sect233r1 + 'nistk283' => '1.3.132.0.16', // aka sect283k1 + 'nistk409' => '1.3.132.0.36', // aka sect409k1 + 'nistb409' => '1.3.132.0.37', // aka sect409r1 + 'nistt571' => '1.3.132.0.38', // aka sect571k1 + + // from https://tools.ietf.org/html/rfc5915 + 'secp192r1' => '1.2.840.10045.3.1.1', // aka prime192v1 + 'sect163k1' => '1.3.132.0.1', + 'sect163r2' => '1.3.132.0.15', + 'secp224r1' => '1.3.132.0.33', + 'sect233k1' => '1.3.132.0.26', + 'sect233r1' => '1.3.132.0.27', + 'secp256r1' => '1.2.840.10045.3.1.7', // aka prime256v1 + 'sect283k1' => '1.3.132.0.16', + 'sect283r1' => '1.3.132.0.17', + 'secp384r1' => '1.3.132.0.34', + 'sect409k1' => '1.3.132.0.36', + 'sect409r1' => '1.3.132.0.37', + 'secp521r1' => '1.3.132.0.35', + 'sect571k1' => '1.3.132.0.38', + 'sect571r1' => '1.3.132.0.39', + // from http://www.secg.org/SEC2-Ver-1.0.pdf + 'secp112r1' => '1.3.132.0.6', + 'secp112r2' => '1.3.132.0.7', + 'secp128r1' => '1.3.132.0.28', + 'secp128r2' => '1.3.132.0.29', + 'secp160k1' => '1.3.132.0.9', + 'secp160r1' => '1.3.132.0.8', + 'secp160r2' => '1.3.132.0.30', + 'secp192k1' => '1.3.132.0.31', + 'secp224k1' => '1.3.132.0.32', + 'secp256k1' => '1.3.132.0.10', + + 'sect113r1' => '1.3.132.0.4', + 'sect113r2' => '1.3.132.0.5', + 'sect131r1' => '1.3.132.0.22', + 'sect131r2' => '1.3.132.0.23', + 'sect163r1' => '1.3.132.0.2', + 'sect193r1' => '1.3.132.0.24', + 'sect193r2' => '1.3.132.0.25', + 'sect239k1' => '1.3.132.0.3', + + // from http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.202.2977&rep=rep1&type=pdf#page=36 + /* + 'c2pnb163v1' => '1.2.840.10045.3.0.1', // J.4.1, example 1 + 'c2pnb163v2' => '1.2.840.10045.3.0.2', // J.4.1, example 2 + 'c2pnb163v3' => '1.2.840.10045.3.0.3', // J.4.1, example 3 + 'c2pnb172w1' => '1.2.840.10045.3.0.4', // J.4.2, example 1 + 'c2tnb191v1' => '1.2.840.10045.3.0.5', // J.4.3, example 1 + 'c2tnb191v2' => '1.2.840.10045.3.0.6', // J.4.3, example 2 + 'c2tnb191v3' => '1.2.840.10045.3.0.7', // J.4.3, example 3 + 'c2onb191v4' => '1.2.840.10045.3.0.8', // J.4.3, example 4 + 'c2onb191v5' => '1.2.840.10045.3.0.9', // J.4.3, example 5 + 'c2pnb208w1' => '1.2.840.10045.3.0.10', // J.4.4, example 1 + 'c2tnb239v1' => '1.2.840.10045.3.0.11', // J.4.5, example 1 + 'c2tnb239v2' => '1.2.840.10045.3.0.12', // J.4.5, example 2 + 'c2tnb239v3' => '1.2.840.10045.3.0.13', // J.4.5, example 3 + 'c2onb239v4' => '1.2.840.10045.3.0.14', // J.4.5, example 4 + 'c2onb239v5' => '1.2.840.10045.3.0.15', // J.4.5, example 5 + 'c2pnb272w1' => '1.2.840.10045.3.0.16', // J.4.6, example 1 + 'c2pnb304w1' => '1.2.840.10045.3.0.17', // J.4.7, example 1 + 'c2tnb359v1' => '1.2.840.10045.3.0.18', // J.4.8, example 1 + 'c2pnb368w1' => '1.2.840.10045.3.0.19', // J.4.9, example 1 + 'c2tnb431r1' => '1.2.840.10045.3.0.20', // J.4.10, example 1 + */ + + // http://www.ecc-brainpool.org/download/Domain-parameters.pdf + // https://tools.ietf.org/html/rfc5639 + 'brainpoolP160r1' => '1.3.36.3.3.2.8.1.1.1', + 'brainpoolP160t1' => '1.3.36.3.3.2.8.1.1.2', + 'brainpoolP192r1' => '1.3.36.3.3.2.8.1.1.3', + 'brainpoolP192t1' => '1.3.36.3.3.2.8.1.1.4', + 'brainpoolP224r1' => '1.3.36.3.3.2.8.1.1.5', + 'brainpoolP224t1' => '1.3.36.3.3.2.8.1.1.6', + 'brainpoolP256r1' => '1.3.36.3.3.2.8.1.1.7', + 'brainpoolP256t1' => '1.3.36.3.3.2.8.1.1.8', + 'brainpoolP320r1' => '1.3.36.3.3.2.8.1.1.9', + 'brainpoolP320t1' => '1.3.36.3.3.2.8.1.1.10', + 'brainpoolP384r1' => '1.3.36.3.3.2.8.1.1.11', + 'brainpoolP384t1' => '1.3.36.3.3.2.8.1.1.12', + 'brainpoolP512r1' => '1.3.36.3.3.2.8.1.1.13', + 'brainpoolP512t1' => '1.3.36.3.3.2.8.1.1.14' + ]; + ASN1::loadOIDs([ + 'prime-field' => '1.2.840.10045.1.1', + 'characteristic-two-field' => '1.2.840.10045.1.2', + 'characteristic-two-basis' => '1.2.840.10045.1.2.3', + // per http://www.secg.org/SEC1-Ver-1.0.pdf#page=84, gnBasis "not used here" + 'gnBasis' => '1.2.840.10045.1.2.3.1', // NULL + 'tpBasis' => '1.2.840.10045.1.2.3.2', // Trinomial + 'ppBasis' => '1.2.840.10045.1.2.3.3' // Pentanomial + ] + self::$curveOIDs); + } + } + + /** + * Explicitly set the curve + * + * If the key contains an implicit curve phpseclib needs the curve + * to be explicitly provided + * + * @param \phpseclib3\Crypt\EC\BaseCurves\Base $curve + */ + public static function setImplicitCurve(BaseCurve $curve) + { + self::$implicitCurve = $curve; + } + + /** + * Returns an instance of \phpseclib3\Crypt\EC\BaseCurves\Base based + * on the curve parameters + * + * @param array $params + * @return \phpseclib3\Crypt\EC\BaseCurves\Base|false + */ + protected static function loadCurveByParam(array $params) + { + if (count($params) > 1) { + throw new \RuntimeException('No parameters are present'); + } + if (isset($params['namedCurve'])) { + $curve = '\phpseclib3\Crypt\EC\Curves\\' . $params['namedCurve']; + if (!class_exists($curve)) { + throw new UnsupportedCurveException('Named Curve of ' . $params['namedCurve'] . ' is not supported'); + } + return new $curve(); + } + if (isset($params['implicitCurve'])) { + if (!isset(self::$implicitCurve)) { + throw new \RuntimeException('Implicit curves can be provided by calling setImplicitCurve'); + } + return self::$implicitCurve; + } + if (isset($params['specifiedCurve'])) { + $data = $params['specifiedCurve']; + switch ($data['fieldID']['fieldType']) { + case 'prime-field': + $curve = new PrimeCurve(); + $curve->setModulo($data['fieldID']['parameters']); + $curve->setCoefficients( + new BigInteger($data['curve']['a'], 256), + new BigInteger($data['curve']['b'], 256) + ); + $point = self::extractPoint("\0" . $data['base'], $curve); + $curve->setBasePoint(...$point); + $curve->setOrder($data['order']); + return $curve; + case 'characteristic-two-field': + $curve = new BinaryCurve(); + $params = ASN1::decodeBER($data['fieldID']['parameters']); + $params = ASN1::asn1map($params[0], Maps\Characteristic_two::MAP); + $modulo = [(int) $params['m']->toString()]; + switch ($params['basis']) { + case 'tpBasis': + $modulo[] = (int) $params['parameters']->toString(); + break; + case 'ppBasis': + $temp = ASN1::decodeBER($params['parameters']); + $temp = ASN1::asn1map($temp[0], Maps\Pentanomial::MAP); + $modulo[] = (int) $temp['k3']->toString(); + $modulo[] = (int) $temp['k2']->toString(); + $modulo[] = (int) $temp['k1']->toString(); + } + $modulo[] = 0; + $curve->setModulo(...$modulo); + $len = ceil($modulo[0] / 8); + $curve->setCoefficients( + Strings::bin2hex($data['curve']['a']), + Strings::bin2hex($data['curve']['b']) + ); + $point = self::extractPoint("\0" . $data['base'], $curve); + $curve->setBasePoint(...$point); + $curve->setOrder($data['order']); + return $curve; + default: + throw new UnsupportedCurveException('Field Type of ' . $data['fieldID']['fieldType'] . ' is not supported'); + } + } + throw new \RuntimeException('No valid parameters are present'); + } + + /** + * Extract points from a string + * + * Supports both compressed and uncompressed points + * + * @param string $str + * @param \phpseclib3\Crypt\EC\BaseCurves\Base $curve + * @return object[] + */ + public static function extractPoint($str, BaseCurve $curve) + { + if ($curve instanceof TwistedEdwardsCurve) { + // first step of point deciding as discussed at the following URL's: + // https://tools.ietf.org/html/rfc8032#section-5.1.3 + // https://tools.ietf.org/html/rfc8032#section-5.2.3 + $y = $str; + $y = strrev($y); + $sign = (bool) (ord($y[0]) & 0x80); + $y[0] = $y[0] & chr(0x7F); + $y = new BigInteger($y, 256); + if ($y->compare($curve->getModulo()) >= 0) { + throw new \RuntimeException('The Y coordinate should not be >= the modulo'); + } + $point = $curve->recoverX($y, $sign); + if (!$curve->verifyPoint($point)) { + throw new \RuntimeException('Unable to verify that point exists on curve'); + } + return $point; + } + + // the first byte of a bit string represents the number of bits in the last byte that are to be ignored but, + // currently, bit strings wanting a non-zero amount of bits trimmed are not supported + if (($val = Strings::shift($str)) != "\0") { + throw new \UnexpectedValueException('extractPoint expects the first byte to be null - not ' . Strings::bin2hex($val)); + } + if ($str == "\0") { + return []; + } + + $keylen = strlen($str); + $order = $curve->getLengthInBytes(); + // point compression is being used + if ($keylen == $order + 1) { + return $curve->derivePoint($str); + } + + // point compression is not being used + if ($keylen == 2 * $order + 1) { + preg_match("#(.)(.{{$order}})(.{{$order}})#s", $str, $matches); + list(, $w, $x, $y) = $matches; + if ($w != "\4") { + throw new \UnexpectedValueException('The first byte of an uncompressed point should be 04 - not ' . Strings::bin2hex($val)); + } + $point = [ + $curve->convertInteger(new BigInteger($x, 256)), + $curve->convertInteger(new BigInteger($y, 256)) + ]; + + if (!$curve->verifyPoint($point)) { + throw new \RuntimeException('Unable to verify that point exists on curve'); + } + + return $point; + } + + throw new \UnexpectedValueException('The string representation of the points is not of an appropriate length'); + } + + /** + * Encode Parameters + * + * @todo Maybe at some point this could be moved to __toString() for each of the curves? + * @param \phpseclib3\Crypt\EC\BaseCurves\Base $curve + * @param bool $returnArray optional + * @param array $options optional + * @return string|false + */ + private static function encodeParameters(BaseCurve $curve, $returnArray = false, array $options = []) + { + $useNamedCurves = isset($options['namedCurve']) ? $options['namedCurve'] : self::$useNamedCurves; + + $reflect = new \ReflectionClass($curve); + $name = $reflect->getShortName(); + if ($useNamedCurves) { + if (isset(self::$curveOIDs[$name])) { + if ($reflect->isFinal()) { + $reflect = $reflect->getParentClass(); + $name = $reflect->getShortName(); + } + return $returnArray ? + ['namedCurve' => $name] : + ASN1::encodeDER(['namedCurve' => $name], Maps\ECParameters::MAP); + } + foreach (new \DirectoryIterator(__DIR__ . '/../../Curves/') as $file) { + if ($file->getExtension() != 'php') { + continue; + } + $testName = $file->getBasename('.php'); + $class = 'phpseclib3\Crypt\EC\Curves\\' . $testName; + $reflect = new \ReflectionClass($class); + if ($reflect->isFinal()) { + continue; + } + $candidate = new $class(); + switch ($name) { + case 'Prime': + if (!$candidate instanceof PrimeCurve) { + break; + } + if (!$candidate->getModulo()->equals($curve->getModulo())) { + break; + } + if ($candidate->getA()->toBytes() != $curve->getA()->toBytes()) { + break; + } + if ($candidate->getB()->toBytes() != $curve->getB()->toBytes()) { + break; + } + + list($candidateX, $candidateY) = $candidate->getBasePoint(); + list($curveX, $curveY) = $curve->getBasePoint(); + if ($candidateX->toBytes() != $curveX->toBytes()) { + break; + } + if ($candidateY->toBytes() != $curveY->toBytes()) { + break; + } + + return $returnArray ? + ['namedCurve' => $testName] : + ASN1::encodeDER(['namedCurve' => $testName], Maps\ECParameters::MAP); + case 'Binary': + if (!$candidate instanceof BinaryCurve) { + break; + } + if ($candidate->getModulo() != $curve->getModulo()) { + break; + } + if ($candidate->getA()->toBytes() != $curve->getA()->toBytes()) { + break; + } + if ($candidate->getB()->toBytes() != $curve->getB()->toBytes()) { + break; + } + + list($candidateX, $candidateY) = $candidate->getBasePoint(); + list($curveX, $curveY) = $curve->getBasePoint(); + if ($candidateX->toBytes() != $curveX->toBytes()) { + break; + } + if ($candidateY->toBytes() != $curveY->toBytes()) { + break; + } + + return $returnArray ? + ['namedCurve' => $testName] : + ASN1::encodeDER(['namedCurve' => $testName], Maps\ECParameters::MAP); + } + } + } + + $order = $curve->getOrder(); + // we could try to calculate the order thusly: + // https://crypto.stackexchange.com/a/27914/4520 + // https://en.wikipedia.org/wiki/Schoof%E2%80%93Elkies%E2%80%93Atkin_algorithm + if (!$order) { + throw new \RuntimeException('Specified Curves need the order to be specified'); + } + $point = $curve->getBasePoint(); + $x = $point[0]->toBytes(); + $y = $point[1]->toBytes(); + + if ($curve instanceof PrimeCurve) { + /* + * valid versions are: + * + * ecdpVer1: + * - neither the curve or the base point are generated verifiably randomly. + * ecdpVer2: + * - curve and base point are generated verifiably at random and curve.seed is present + * ecdpVer3: + * - base point is generated verifiably at random but curve is not. curve.seed is present + */ + // other (optional) parameters can be calculated using the methods discused at + // https://crypto.stackexchange.com/q/28947/4520 + $data = [ + 'version' => 'ecdpVer1', + 'fieldID' => [ + 'fieldType' => 'prime-field', + 'parameters' => $curve->getModulo() + ], + 'curve' => [ + 'a' => $curve->getA()->toBytes(), + 'b' => $curve->getB()->toBytes() + ], + 'base' => "\4" . $x . $y, + 'order' => $order + ]; + + return $returnArray ? + ['specifiedCurve' => $data] : + ASN1::encodeDER(['specifiedCurve' => $data], Maps\ECParameters::MAP); + } + if ($curve instanceof BinaryCurve) { + $modulo = $curve->getModulo(); + $basis = count($modulo); + $m = array_shift($modulo); + array_pop($modulo); // the last parameter should always be 0 + //rsort($modulo); + switch ($basis) { + case 3: + $basis = 'tpBasis'; + $modulo = new BigInteger($modulo[0]); + break; + case 5: + $basis = 'ppBasis'; + // these should be in strictly ascending order (hence the commented out rsort above) + $modulo = [ + 'k1' => new BigInteger($modulo[2]), + 'k2' => new BigInteger($modulo[1]), + 'k3' => new BigInteger($modulo[0]) + ]; + $modulo = ASN1::encodeDER($modulo, Maps\Pentanomial::MAP); + $modulo = new ASN1\Element($modulo); + } + $params = ASN1::encodeDER([ + 'm' => new BigInteger($m), + 'basis' => $basis, + 'parameters' => $modulo + ], Maps\Characteristic_two::MAP); + $params = new ASN1\Element($params); + $a = ltrim($curve->getA()->toBytes(), "\0"); + if (!strlen($a)) { + $a = "\0"; + } + $b = ltrim($curve->getB()->toBytes(), "\0"); + if (!strlen($b)) { + $b = "\0"; + } + $data = [ + 'version' => 'ecdpVer1', + 'fieldID' => [ + 'fieldType' => 'characteristic-two-field', + 'parameters' => $params + ], + 'curve' => [ + 'a' => $a, + 'b' => $b + ], + 'base' => "\4" . $x . $y, + 'order' => $order + ]; + + return $returnArray ? + ['specifiedCurve' => $data] : + ASN1::encodeDER(['specifiedCurve' => $data], Maps\ECParameters::MAP); + } + + throw new UnsupportedCurveException('Curve cannot be serialized'); + } + + /** + * Use Specified Curve + * + * A specified curve has all the coefficients, the base points, etc, explicitely included. + * A specified curve is a more verbose way of representing a curve + */ + public static function useSpecifiedCurve() + { + self::$useNamedCurves = false; + } + + /** + * Use Named Curve + * + * A named curve does not include any parameters. It is up to the EC parameters to + * know what the coefficients, the base points, etc, are from the name of the curve. + * A named curve is a more concise way of representing a curve + */ + public static function useNamedCurve() + { + self::$useNamedCurves = true; + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/JWK.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/JWK.php new file mode 100644 index 00000000..fd18a981 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/JWK.php @@ -0,0 +1,189 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\Crypt\EC\Formats\Keys; + +use phpseclib3\Common\Functions\Strings; +use phpseclib3\Crypt\Common\Formats\Keys\JWK as Progenitor; +use phpseclib3\Crypt\EC\BaseCurves\Base as BaseCurve; +use phpseclib3\Crypt\EC\BaseCurves\TwistedEdwards as TwistedEdwardsCurve; +use phpseclib3\Crypt\EC\Curves\Ed25519; +use phpseclib3\Crypt\EC\Curves\secp256k1; +use phpseclib3\Crypt\EC\Curves\secp256r1; +use phpseclib3\Crypt\EC\Curves\secp384r1; +use phpseclib3\Crypt\EC\Curves\secp521r1; +use phpseclib3\Exception\UnsupportedCurveException; +use phpseclib3\Math\BigInteger; + +/** + * JWK Formatted EC Handler + * + * @author Jim Wigginton + */ +abstract class JWK extends Progenitor +{ + use Common; + + /** + * Break a public or private key down into its constituent components + * + * @param string $key + * @param string $password optional + * @return array + */ + public static function load($key, $password = '') + { + $key = parent::load($key, $password); + + switch ($key->kty) { + case 'EC': + switch ($key->crv) { + case 'P-256': + case 'P-384': + case 'P-521': + case 'secp256k1': + break; + default: + throw new UnsupportedCurveException('Only P-256, P-384, P-521 and secp256k1 curves are accepted (' . $key->crv . ' provided)'); + } + break; + case 'OKP': + switch ($key->crv) { + case 'Ed25519': + case 'Ed448': + break; + default: + throw new UnsupportedCurveException('Only Ed25519 and Ed448 curves are accepted (' . $key->crv . ' provided)'); + } + break; + default: + throw new \Exception('Only EC and OKP JWK keys are supported'); + } + + $curve = '\phpseclib3\Crypt\EC\Curves\\' . str_replace('P-', 'nistp', $key->crv); + $curve = new $curve(); + + if ($curve instanceof TwistedEdwardsCurve) { + $QA = self::extractPoint(Strings::base64url_decode($key->x), $curve); + if (!isset($key->d)) { + return compact('curve', 'QA'); + } + $arr = $curve->extractSecret(Strings::base64url_decode($key->d)); + return compact('curve', 'QA') + $arr; + } + + $QA = [ + $curve->convertInteger(new BigInteger(Strings::base64url_decode($key->x), 256)), + $curve->convertInteger(new BigInteger(Strings::base64url_decode($key->y), 256)) + ]; + + if (!$curve->verifyPoint($QA)) { + throw new \RuntimeException('Unable to verify that point exists on curve'); + } + + if (!isset($key->d)) { + return compact('curve', 'QA'); + } + + $dA = new BigInteger(Strings::base64url_decode($key->d), 256); + + $curve->rangeCheck($dA); + + return compact('curve', 'dA', 'QA'); + } + + /** + * Returns the alias that corresponds to a curve + * + * @return string + */ + private static function getAlias(BaseCurve $curve) + { + switch (true) { + case $curve instanceof secp256r1: + return 'P-256'; + case $curve instanceof secp384r1: + return 'P-384'; + case $curve instanceof secp521r1: + return 'P-521'; + case $curve instanceof secp256k1: + return 'secp256k1'; + } + + $reflect = new \ReflectionClass($curve); + $curveName = $reflect->isFinal() ? + $reflect->getParentClass()->getShortName() : + $reflect->getShortName(); + throw new UnsupportedCurveException("$curveName is not a supported curve"); + } + + /** + * Return the array superstructure for an EC public key + * + * @param \phpseclib3\Crypt\EC\BaseCurves\Base $curve + * @param \phpseclib3\Math\Common\FiniteField\Integer[] $publicKey + * @return array + */ + private static function savePublicKeyHelper(BaseCurve $curve, array $publicKey) + { + if ($curve instanceof TwistedEdwardsCurve) { + return [ + 'kty' => 'OKP', + 'crv' => $curve instanceof Ed25519 ? 'Ed25519' : 'Ed448', + 'x' => Strings::base64url_encode($curve->encodePoint($publicKey)) + ]; + } + + return [ + 'kty' => 'EC', + 'crv' => self::getAlias($curve), + 'x' => Strings::base64url_encode($publicKey[0]->toBytes()), + 'y' => Strings::base64url_encode($publicKey[1]->toBytes()) + ]; + } + + /** + * Convert an EC public key to the appropriate format + * + * @param \phpseclib3\Crypt\EC\BaseCurves\Base $curve + * @param \phpseclib3\Math\Common\FiniteField\Integer[] $publicKey + * @param array $options optional + * @return string + */ + public static function savePublicKey(BaseCurve $curve, array $publicKey, array $options = []) + { + $key = self::savePublicKeyHelper($curve, $publicKey); + + return self::wrapKey($key, $options); + } + + /** + * Convert a private key to the appropriate format. + * + * @param \phpseclib3\Math\BigInteger $privateKey + * @param \phpseclib3\Crypt\EC\Curves\Ed25519 $curve + * @param \phpseclib3\Math\Common\FiniteField\Integer[] $publicKey + * @param string $secret optional + * @param string $password optional + * @param array $options optional + * @return string + */ + public static function savePrivateKey(BigInteger $privateKey, BaseCurve $curve, array $publicKey, $secret = null, $password = '', array $options = []) + { + $key = self::savePublicKeyHelper($curve, $publicKey); + $key['d'] = $curve instanceof TwistedEdwardsCurve ? $secret : $privateKey->toBytes(); + $key['d'] = Strings::base64url_encode($key['d']); + + return self::wrapKey($key, $options); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/MontgomeryPrivate.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/MontgomeryPrivate.php new file mode 100644 index 00000000..5741b05a --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/MontgomeryPrivate.php @@ -0,0 +1,101 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\Crypt\EC\Formats\Keys; + +use phpseclib3\Crypt\EC\BaseCurves\Montgomery as MontgomeryCurve; +use phpseclib3\Crypt\EC\Curves\Curve25519; +use phpseclib3\Crypt\EC\Curves\Curve448; +use phpseclib3\Exception\UnsupportedFormatException; +use phpseclib3\Math\BigInteger; + +/** + * Montgomery Curve Private Key Handler + * + * @author Jim Wigginton + */ +abstract class MontgomeryPrivate +{ + /** + * Is invisible flag + * + */ + const IS_INVISIBLE = true; + + /** + * Break a public or private key down into its constituent components + * + * @param string $key + * @param string $password optional + * @return array + */ + public static function load($key, $password = '') + { + switch (strlen($key)) { + case 32: + $curve = new Curve25519(); + break; + case 56: + $curve = new Curve448(); + break; + default: + throw new \LengthException('The only supported lengths are 32 and 56'); + } + + $components = ['curve' => $curve]; + $components['dA'] = new BigInteger($key, 256); + $curve->rangeCheck($components['dA']); + // note that EC::getEncodedCoordinates does some additional "magic" (it does strrev on the result) + $components['QA'] = $components['curve']->multiplyPoint($components['curve']->getBasePoint(), $components['dA']); + + return $components; + } + + /** + * Convert an EC public key to the appropriate format + * + * @param \phpseclib3\Crypt\EC\BaseCurves\Montgomery $curve + * @param \phpseclib3\Math\Common\FiniteField\Integer[] $publicKey + * @return string + */ + public static function savePublicKey(MontgomeryCurve $curve, array $publicKey) + { + return strrev($publicKey[0]->toBytes()); + } + + /** + * Convert a private key to the appropriate format. + * + * @param \phpseclib3\Math\BigInteger $privateKey + * @param \phpseclib3\Crypt\EC\BaseCurves\Montgomery $curve + * @param \phpseclib3\Math\Common\FiniteField\Integer[] $publicKey + * @param string $secret optional + * @param string $password optional + * @return string + */ + public static function savePrivateKey(BigInteger $privateKey, MontgomeryCurve $curve, array $publicKey, $secret = null, $password = '') + { + if (!empty($password) && is_string($password)) { + throw new UnsupportedFormatException('MontgomeryPrivate private keys do not support encryption'); + } + + return $privateKey->toBytes(); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/MontgomeryPublic.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/MontgomeryPublic.php new file mode 100644 index 00000000..d1ad48a5 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/MontgomeryPublic.php @@ -0,0 +1,71 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\Crypt\EC\Formats\Keys; + +use phpseclib3\Crypt\EC\BaseCurves\Montgomery as MontgomeryCurve; +use phpseclib3\Crypt\EC\Curves\Curve25519; +use phpseclib3\Crypt\EC\Curves\Curve448; +use phpseclib3\Math\BigInteger; + +/** + * Montgomery Public Key Handler + * + * @author Jim Wigginton + */ +abstract class MontgomeryPublic +{ + /** + * Is invisible flag + * + */ + const IS_INVISIBLE = true; + + /** + * Break a public or private key down into its constituent components + * + * @param string $key + * @param string $password optional + * @return array + */ + public static function load($key, $password = '') + { + switch (strlen($key)) { + case 32: + $curve = new Curve25519(); + break; + case 56: + $curve = new Curve448(); + break; + default: + throw new \LengthException('The only supported lengths are 32 and 56'); + } + + $components = ['curve' => $curve]; + $components['QA'] = [$components['curve']->convertInteger(new BigInteger(strrev($key), 256))]; + + return $components; + } + + /** + * Convert an EC public key to the appropriate format + * + * @param \phpseclib3\Crypt\EC\BaseCurves\Montgomery $curve + * @param \phpseclib3\Math\Common\FiniteField\Integer[] $publicKey + * @return string + */ + public static function savePublicKey(MontgomeryCurve $curve, array $publicKey) + { + return strrev($publicKey[0]->toBytes()); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/OpenSSH.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/OpenSSH.php new file mode 100644 index 00000000..2cd3e19d --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/OpenSSH.php @@ -0,0 +1,209 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\Crypt\EC\Formats\Keys; + +use phpseclib3\Common\Functions\Strings; +use phpseclib3\Crypt\Common\Formats\Keys\OpenSSH as Progenitor; +use phpseclib3\Crypt\EC\BaseCurves\Base as BaseCurve; +use phpseclib3\Crypt\EC\Curves\Ed25519; +use phpseclib3\Exception\UnsupportedCurveException; +use phpseclib3\Math\BigInteger; + +/** + * OpenSSH Formatted EC Key Handler + * + * @author Jim Wigginton + */ +abstract class OpenSSH extends Progenitor +{ + use Common; + + /** + * Supported Key Types + * + * @var array + */ + protected static $types = [ + 'ecdsa-sha2-nistp256', + 'ecdsa-sha2-nistp384', + 'ecdsa-sha2-nistp521', + 'ssh-ed25519' + ]; + + /** + * Break a public or private key down into its constituent components + * + * @param string $key + * @param string $password optional + * @return array + */ + public static function load($key, $password = '') + { + $parsed = parent::load($key, $password); + + if (isset($parsed['paddedKey'])) { + $paddedKey = $parsed['paddedKey']; + list($type) = Strings::unpackSSH2('s', $paddedKey); + if ($type != $parsed['type']) { + throw new \RuntimeException("The public and private keys are not of the same type ($type vs $parsed[type])"); + } + if ($type == 'ssh-ed25519') { + list(, $key, $comment) = Strings::unpackSSH2('sss', $paddedKey); + $key = libsodium::load($key); + $key['comment'] = $comment; + return $key; + } + list($curveName, $publicKey, $privateKey, $comment) = Strings::unpackSSH2('ssis', $paddedKey); + $curve = self::loadCurveByParam(['namedCurve' => $curveName]); + $curve->rangeCheck($privateKey); + return [ + 'curve' => $curve, + 'dA' => $privateKey, + 'QA' => self::extractPoint("\0$publicKey", $curve), + 'comment' => $comment + ]; + } + + if ($parsed['type'] == 'ssh-ed25519') { + if (Strings::shift($parsed['publicKey'], 4) != "\0\0\0\x20") { + throw new \RuntimeException('Length of ssh-ed25519 key should be 32'); + } + + $curve = new Ed25519(); + $qa = self::extractPoint($parsed['publicKey'], $curve); + } else { + list($curveName, $publicKey) = Strings::unpackSSH2('ss', $parsed['publicKey']); + $curveName = '\phpseclib3\Crypt\EC\Curves\\' . $curveName; + $curve = new $curveName(); + + $qa = self::extractPoint("\0" . $publicKey, $curve); + } + + return [ + 'curve' => $curve, + 'QA' => $qa, + 'comment' => $parsed['comment'] + ]; + } + + /** + * Returns the alias that corresponds to a curve + * + * @return string + */ + private static function getAlias(BaseCurve $curve) + { + self::initialize_static_variables(); + + $reflect = new \ReflectionClass($curve); + $name = $reflect->getShortName(); + + $oid = self::$curveOIDs[$name]; + $aliases = array_filter(self::$curveOIDs, function ($v) use ($oid) { + return $v == $oid; + }); + $aliases = array_keys($aliases); + + for ($i = 0; $i < count($aliases); $i++) { + if (in_array('ecdsa-sha2-' . $aliases[$i], self::$types)) { + $alias = $aliases[$i]; + break; + } + } + + if (!isset($alias)) { + throw new UnsupportedCurveException($name . ' is not a curve that the OpenSSH plugin supports'); + } + + return $alias; + } + + /** + * Convert an EC public key to the appropriate format + * + * @param \phpseclib3\Crypt\EC\BaseCurves\Base $curve + * @param \phpseclib3\Math\Common\FiniteField\Integer[] $publicKey + * @param array $options optional + * @return string + */ + public static function savePublicKey(BaseCurve $curve, array $publicKey, array $options = []) + { + $comment = isset($options['comment']) ? $options['comment'] : self::$comment; + + if ($curve instanceof Ed25519) { + $key = Strings::packSSH2('ss', 'ssh-ed25519', $curve->encodePoint($publicKey)); + + if (isset($options['binary']) ? $options['binary'] : self::$binary) { + return $key; + } + + $key = 'ssh-ed25519 ' . base64_encode($key) . ' ' . $comment; + return $key; + } + + $alias = self::getAlias($curve); + + $points = "\4" . $publicKey[0]->toBytes() . $publicKey[1]->toBytes(); + $key = Strings::packSSH2('sss', 'ecdsa-sha2-' . $alias, $alias, $points); + + if (isset($options['binary']) ? $options['binary'] : self::$binary) { + return $key; + } + + $key = 'ecdsa-sha2-' . $alias . ' ' . base64_encode($key) . ' ' . $comment; + + return $key; + } + + /** + * Convert a private key to the appropriate format. + * + * @param \phpseclib3\Math\BigInteger $privateKey + * @param \phpseclib3\Crypt\EC\Curves\Ed25519 $curve + * @param \phpseclib3\Math\Common\FiniteField\Integer[] $publicKey + * @param string $secret optional + * @param string $password optional + * @param array $options optional + * @return string + */ + public static function savePrivateKey(BigInteger $privateKey, BaseCurve $curve, array $publicKey, $secret = null, $password = '', array $options = []) + { + if ($curve instanceof Ed25519) { + if (!isset($secret)) { + throw new \RuntimeException('Private Key does not have a secret set'); + } + if (strlen($secret) != 32) { + throw new \RuntimeException('Private Key secret is not of the correct length'); + } + + $pubKey = $curve->encodePoint($publicKey); + + $publicKey = Strings::packSSH2('ss', 'ssh-ed25519', $pubKey); + $privateKey = Strings::packSSH2('sss', 'ssh-ed25519', $pubKey, $secret . $pubKey); + + return self::wrapPrivateKey($publicKey, $privateKey, $password, $options); + } + + $alias = self::getAlias($curve); + + $points = "\4" . $publicKey[0]->toBytes() . $publicKey[1]->toBytes(); + $publicKey = self::savePublicKey($curve, $publicKey, ['binary' => true]); + + $privateKey = Strings::packSSH2('sssi', 'ecdsa-sha2-' . $alias, $alias, $points, $privateKey); + + return self::wrapPrivateKey($publicKey, $privateKey, $password, $options); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/PKCS1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/PKCS1.php new file mode 100644 index 00000000..9f4b3300 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/PKCS1.php @@ -0,0 +1,194 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\Crypt\EC\Formats\Keys; + +use phpseclib3\Common\Functions\Strings; +use phpseclib3\Crypt\Common\Formats\Keys\PKCS1 as Progenitor; +use phpseclib3\Crypt\EC\BaseCurves\Base as BaseCurve; +use phpseclib3\Crypt\EC\BaseCurves\Montgomery as MontgomeryCurve; +use phpseclib3\Crypt\EC\BaseCurves\TwistedEdwards as TwistedEdwardsCurve; +use phpseclib3\Exception\UnsupportedCurveException; +use phpseclib3\File\ASN1; +use phpseclib3\File\ASN1\Maps; +use phpseclib3\Math\BigInteger; + +/** + * "PKCS1" (RFC5915) Formatted EC Key Handler + * + * @author Jim Wigginton + */ +abstract class PKCS1 extends Progenitor +{ + use Common; + + /** + * Break a public or private key down into its constituent components + * + * @param string $key + * @param string $password optional + * @return array + */ + public static function load($key, $password = '') + { + self::initialize_static_variables(); + + if (!Strings::is_stringable($key)) { + throw new \UnexpectedValueException('Key should be a string - not a ' . gettype($key)); + } + + if (strpos($key, 'BEGIN EC PARAMETERS') && strpos($key, 'BEGIN EC PRIVATE KEY')) { + $components = []; + + preg_match('#-*BEGIN EC PRIVATE KEY-*[^-]*-*END EC PRIVATE KEY-*#s', $key, $matches); + $decoded = parent::load($matches[0], $password); + $decoded = ASN1::decodeBER($decoded); + if (!$decoded) { + throw new \RuntimeException('Unable to decode BER'); + } + + $ecPrivate = ASN1::asn1map($decoded[0], Maps\ECPrivateKey::MAP); + if (!is_array($ecPrivate)) { + throw new \RuntimeException('Unable to perform ASN1 mapping'); + } + + if (isset($ecPrivate['parameters'])) { + $components['curve'] = self::loadCurveByParam($ecPrivate['parameters']); + } + + preg_match('#-*BEGIN EC PARAMETERS-*[^-]*-*END EC PARAMETERS-*#s', $key, $matches); + $decoded = parent::load($matches[0], ''); + $decoded = ASN1::decodeBER($decoded); + if (!$decoded) { + throw new \RuntimeException('Unable to decode BER'); + } + $ecParams = ASN1::asn1map($decoded[0], Maps\ECParameters::MAP); + if (!is_array($ecParams)) { + throw new \RuntimeException('Unable to perform ASN1 mapping'); + } + $ecParams = self::loadCurveByParam($ecParams); + + // comparing $ecParams and $components['curve'] directly won't work because they'll have different Math\Common\FiniteField classes + // even if the modulo is the same + if (isset($components['curve']) && self::encodeParameters($ecParams, false, []) != self::encodeParameters($components['curve'], false, [])) { + throw new \RuntimeException('EC PARAMETERS does not correspond to EC PRIVATE KEY'); + } + + if (!isset($components['curve'])) { + $components['curve'] = $ecParams; + } + + $components['dA'] = new BigInteger($ecPrivate['privateKey'], 256); + $components['curve']->rangeCheck($components['dA']); + $components['QA'] = isset($ecPrivate['publicKey']) ? + self::extractPoint($ecPrivate['publicKey'], $components['curve']) : + $components['curve']->multiplyPoint($components['curve']->getBasePoint(), $components['dA']); + + return $components; + } + + $key = parent::load($key, $password); + + $decoded = ASN1::decodeBER($key); + if (!$decoded) { + throw new \RuntimeException('Unable to decode BER'); + } + + $key = ASN1::asn1map($decoded[0], Maps\ECParameters::MAP); + if (is_array($key)) { + return ['curve' => self::loadCurveByParam($key)]; + } + + $key = ASN1::asn1map($decoded[0], Maps\ECPrivateKey::MAP); + if (!is_array($key)) { + throw new \RuntimeException('Unable to perform ASN1 mapping'); + } + if (!isset($key['parameters'])) { + throw new \RuntimeException('Key cannot be loaded without parameters'); + } + + $components = []; + $components['curve'] = self::loadCurveByParam($key['parameters']); + $components['dA'] = new BigInteger($key['privateKey'], 256); + $components['QA'] = isset($ecPrivate['publicKey']) ? + self::extractPoint($ecPrivate['publicKey'], $components['curve']) : + $components['curve']->multiplyPoint($components['curve']->getBasePoint(), $components['dA']); + + return $components; + } + + /** + * Convert EC parameters to the appropriate format + * + * @return string + */ + public static function saveParameters(BaseCurve $curve, array $options = []) + { + self::initialize_static_variables(); + + if ($curve instanceof TwistedEdwardsCurve || $curve instanceof MontgomeryCurve) { + throw new UnsupportedCurveException('TwistedEdwards and Montgomery Curves are not supported'); + } + + $key = self::encodeParameters($curve, false, $options); + + return "-----BEGIN EC PARAMETERS-----\r\n" . + chunk_split(Strings::base64_encode($key), 64) . + "-----END EC PARAMETERS-----\r\n"; + } + + /** + * Convert a private key to the appropriate format. + * + * @param \phpseclib3\Math\BigInteger $privateKey + * @param \phpseclib3\Crypt\EC\BaseCurves\Base $curve + * @param \phpseclib3\Math\Common\FiniteField\Integer[] $publicKey + * @param string $secret optional + * @param string $password optional + * @param array $options optional + * @return string + */ + public static function savePrivateKey(BigInteger $privateKey, BaseCurve $curve, array $publicKey, $secret = null, $password = '', array $options = []) + { + self::initialize_static_variables(); + + if ($curve instanceof TwistedEdwardsCurve || $curve instanceof MontgomeryCurve) { + throw new UnsupportedCurveException('TwistedEdwards Curves are not supported'); + } + + $publicKey = "\4" . $publicKey[0]->toBytes() . $publicKey[1]->toBytes(); + + $key = [ + 'version' => 'ecPrivkeyVer1', + 'privateKey' => $privateKey->toBytes(), + 'parameters' => new ASN1\Element(self::encodeParameters($curve)), + 'publicKey' => "\0" . $publicKey + ]; + + $key = ASN1::encodeDER($key, Maps\ECPrivateKey::MAP); + + return self::wrapPrivateKey($key, 'EC', $password, $options); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/PKCS8.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/PKCS8.php new file mode 100644 index 00000000..a75162ed --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/PKCS8.php @@ -0,0 +1,248 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\Crypt\EC\Formats\Keys; + +use phpseclib3\Common\Functions\Strings; +use phpseclib3\Crypt\Common\Formats\Keys\PKCS8 as Progenitor; +use phpseclib3\Crypt\EC\BaseCurves\Base as BaseCurve; +use phpseclib3\Crypt\EC\BaseCurves\Montgomery as MontgomeryCurve; +use phpseclib3\Crypt\EC\BaseCurves\TwistedEdwards as TwistedEdwardsCurve; +use phpseclib3\Crypt\EC\Curves\Ed25519; +use phpseclib3\Crypt\EC\Curves\Ed448; +use phpseclib3\Exception\UnsupportedCurveException; +use phpseclib3\File\ASN1; +use phpseclib3\File\ASN1\Maps; +use phpseclib3\Math\BigInteger; + +/** + * PKCS#8 Formatted EC Key Handler + * + * @author Jim Wigginton + */ +abstract class PKCS8 extends Progenitor +{ + use Common; + + /** + * OID Name + * + * @var array + */ + const OID_NAME = ['id-ecPublicKey', 'id-Ed25519', 'id-Ed448']; + + /** + * OID Value + * + * @var string + */ + const OID_VALUE = ['1.2.840.10045.2.1', '1.3.101.112', '1.3.101.113']; + + /** + * Break a public or private key down into its constituent components + * + * @param string $key + * @param string $password optional + * @return array + */ + public static function load($key, $password = '') + { + // initialize_static_variables() is defined in both the trait and the parent class + // when it's defined in two places it's the traits one that's called + // the parent one is needed, as well, but the parent one is called by other methods + // in the parent class as needed and in the context of the parent it's the parent + // one that's called + self::initialize_static_variables(); + + if (!Strings::is_stringable($key)) { + throw new \UnexpectedValueException('Key should be a string - not a ' . gettype($key)); + } + + $isPublic = strpos($key, 'PUBLIC') !== false; + + $key = parent::load($key, $password); + + $type = isset($key['privateKey']) ? 'privateKey' : 'publicKey'; + + switch (true) { + case !$isPublic && $type == 'publicKey': + throw new \UnexpectedValueException('Human readable string claims non-public key but DER encoded string claims public key'); + case $isPublic && $type == 'privateKey': + throw new \UnexpectedValueException('Human readable string claims public key but DER encoded string claims private key'); + } + + switch ($key[$type . 'Algorithm']['algorithm']) { + case 'id-Ed25519': + case 'id-Ed448': + return self::loadEdDSA($key); + } + + $decoded = ASN1::decodeBER($key[$type . 'Algorithm']['parameters']->element); + if (!$decoded) { + throw new \RuntimeException('Unable to decode BER'); + } + $params = ASN1::asn1map($decoded[0], Maps\ECParameters::MAP); + if (!$params) { + throw new \RuntimeException('Unable to decode the parameters using Maps\ECParameters'); + } + + $components = []; + $components['curve'] = self::loadCurveByParam($params); + + if ($isPublic) { + $components['QA'] = self::extractPoint("\0" . $key['publicKey'], $components['curve']); + + return $components; + } + + $decoded = ASN1::decodeBER($key['privateKey']); + if (!$decoded) { + throw new \RuntimeException('Unable to decode BER'); + } + $key = ASN1::asn1map($decoded[0], Maps\ECPrivateKey::MAP); + if (isset($key['parameters']) && $params != $key['parameters']) { + throw new \RuntimeException('The PKCS8 parameter field does not match the private key parameter field'); + } + + $components['dA'] = new BigInteger($key['privateKey'], 256); + $components['curve']->rangeCheck($components['dA']); + $components['QA'] = isset($key['publicKey']) ? + self::extractPoint($key['publicKey'], $components['curve']) : + $components['curve']->multiplyPoint($components['curve']->getBasePoint(), $components['dA']); + + return $components; + } + + /** + * Break a public or private EdDSA key down into its constituent components + * + * @return array + */ + private static function loadEdDSA(array $key) + { + $components = []; + + if (isset($key['privateKey'])) { + $components['curve'] = $key['privateKeyAlgorithm']['algorithm'] == 'id-Ed25519' ? new Ed25519() : new Ed448(); + + // 0x04 == octet string + // 0x20 == length (32 bytes) + if (substr($key['privateKey'], 0, 2) != "\x04\x20") { + throw new \RuntimeException('The first two bytes of the private key field should be 0x0420'); + } + $arr = $components['curve']->extractSecret(substr($key['privateKey'], 2)); + $components['dA'] = $arr['dA']; + $components['secret'] = $arr['secret']; + } + + if (isset($key['publicKey'])) { + if (!isset($components['curve'])) { + $components['curve'] = $key['publicKeyAlgorithm']['algorithm'] == 'id-Ed25519' ? new Ed25519() : new Ed448(); + } + + $components['QA'] = self::extractPoint($key['publicKey'], $components['curve']); + } + + if (isset($key['privateKey']) && !isset($components['QA'])) { + $components['QA'] = $components['curve']->multiplyPoint($components['curve']->getBasePoint(), $components['dA']); + } + + return $components; + } + + /** + * Convert an EC public key to the appropriate format + * + * @param \phpseclib3\Crypt\EC\BaseCurves\Base $curve + * @param \phpseclib3\Math\Common\FiniteField\Integer[] $publicKey + * @param array $options optional + * @return string + */ + public static function savePublicKey(BaseCurve $curve, array $publicKey, array $options = []) + { + self::initialize_static_variables(); + + if ($curve instanceof MontgomeryCurve) { + throw new UnsupportedCurveException('Montgomery Curves are not supported'); + } + + if ($curve instanceof TwistedEdwardsCurve) { + return self::wrapPublicKey( + $curve->encodePoint($publicKey), + null, + $curve instanceof Ed25519 ? 'id-Ed25519' : 'id-Ed448' + ); + } + + $params = new ASN1\Element(self::encodeParameters($curve, false, $options)); + + $key = "\4" . $publicKey[0]->toBytes() . $publicKey[1]->toBytes(); + + return self::wrapPublicKey($key, $params, 'id-ecPublicKey'); + } + + /** + * Convert a private key to the appropriate format. + * + * @param \phpseclib3\Math\BigInteger $privateKey + * @param \phpseclib3\Crypt\EC\BaseCurves\Base $curve + * @param \phpseclib3\Math\Common\FiniteField\Integer[] $publicKey + * @param string $secret optional + * @param string $password optional + * @param array $options optional + * @return string + */ + public static function savePrivateKey(BigInteger $privateKey, BaseCurve $curve, array $publicKey, $secret = null, $password = '', array $options = []) + { + self::initialize_static_variables(); + + if ($curve instanceof MontgomeryCurve) { + throw new UnsupportedCurveException('Montgomery Curves are not supported'); + } + + if ($curve instanceof TwistedEdwardsCurve) { + return self::wrapPrivateKey( + "\x04\x20" . $secret, + [], + null, + $password, + $curve instanceof Ed25519 ? 'id-Ed25519' : 'id-Ed448' + ); + } + + $publicKey = "\4" . $publicKey[0]->toBytes() . $publicKey[1]->toBytes(); + + $params = new ASN1\Element(self::encodeParameters($curve, false, $options)); + + $key = [ + 'version' => 'ecPrivkeyVer1', + 'privateKey' => $privateKey->toBytes(), + //'parameters' => $params, + 'publicKey' => "\0" . $publicKey + ]; + + $key = ASN1::encodeDER($key, Maps\ECPrivateKey::MAP); + + return self::wrapPrivateKey($key, [], $params, $password, 'id-ecPublicKey', '', $options); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/PuTTY.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/PuTTY.php new file mode 100644 index 00000000..866c883f --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/PuTTY.php @@ -0,0 +1,138 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\Crypt\EC\Formats\Keys; + +use phpseclib3\Common\Functions\Strings; +use phpseclib3\Crypt\Common\Formats\Keys\PuTTY as Progenitor; +use phpseclib3\Crypt\EC\BaseCurves\Base as BaseCurve; +use phpseclib3\Crypt\EC\BaseCurves\TwistedEdwards as TwistedEdwardsCurve; +use phpseclib3\Math\BigInteger; + +/** + * PuTTY Formatted EC Key Handler + * + * @author Jim Wigginton + */ +abstract class PuTTY extends Progenitor +{ + use Common; + + /** + * Public Handler + * + * @var string + */ + const PUBLIC_HANDLER = 'phpseclib3\Crypt\EC\Formats\Keys\OpenSSH'; + + /** + * Supported Key Types + * + * @var array + */ + protected static $types = [ + 'ecdsa-sha2-nistp256', + 'ecdsa-sha2-nistp384', + 'ecdsa-sha2-nistp521', + 'ssh-ed25519' + ]; + + /** + * Break a public or private key down into its constituent components + * + * @param string $key + * @param string $password optional + * @return array + */ + public static function load($key, $password = '') + { + $components = parent::load($key, $password); + if (!isset($components['private'])) { + return $components; + } + + $private = $components['private']; + + $temp = Strings::base64_encode(Strings::packSSH2('s', $components['type']) . $components['public']); + $components = OpenSSH::load($components['type'] . ' ' . $temp . ' ' . $components['comment']); + + if ($components['curve'] instanceof TwistedEdwardsCurve) { + if (Strings::shift($private, 4) != "\0\0\0\x20") { + throw new \RuntimeException('Length of ssh-ed25519 key should be 32'); + } + $arr = $components['curve']->extractSecret($private); + $components['dA'] = $arr['dA']; + $components['secret'] = $arr['secret']; + } else { + list($components['dA']) = Strings::unpackSSH2('i', $private); + $components['curve']->rangeCheck($components['dA']); + } + + return $components; + } + + /** + * Convert a private key to the appropriate format. + * + * @param \phpseclib3\Math\BigInteger $privateKey + * @param \phpseclib3\Crypt\EC\BaseCurves\Base $curve + * @param \phpseclib3\Math\Common\FiniteField\Integer[] $publicKey + * @param string $secret optional + * @param string $password optional + * @param array $options optional + * @return string + */ + public static function savePrivateKey(BigInteger $privateKey, BaseCurve $curve, array $publicKey, $secret = null, $password = false, array $options = []) + { + self::initialize_static_variables(); + + $public = explode(' ', OpenSSH::savePublicKey($curve, $publicKey)); + $name = $public[0]; + $public = Strings::base64_decode($public[1]); + list(, $length) = unpack('N', Strings::shift($public, 4)); + Strings::shift($public, $length); + + // PuTTY pads private keys with a null byte per the following: + // https://github.com/github/putty/blob/a3d14d77f566a41fc61dfdc5c2e0e384c9e6ae8b/sshecc.c#L1926 + if (!$curve instanceof TwistedEdwardsCurve) { + $private = $privateKey->toBytes(); + if (!(strlen($privateKey->toBits()) & 7)) { + $private = "\0$private"; + } + } + + $private = $curve instanceof TwistedEdwardsCurve ? + Strings::packSSH2('s', $secret) : + Strings::packSSH2('s', $private); + + return self::wrapPrivateKey($public, $private, $name, $password, $options); + } + + /** + * Convert an EC public key to the appropriate format + * + * @param \phpseclib3\Crypt\EC\BaseCurves\Base $curve + * @param \phpseclib3\Math\Common\FiniteField[] $publicKey + * @return string + */ + public static function savePublicKey(BaseCurve $curve, array $publicKey) + { + $public = explode(' ', OpenSSH::savePublicKey($curve, $publicKey)); + $type = $public[0]; + $public = Strings::base64_decode($public[1]); + list(, $length) = unpack('N', Strings::shift($public, 4)); + Strings::shift($public, $length); + + return self::wrapPublicKey($public, $type); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/XML.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/XML.php new file mode 100644 index 00000000..27d9218f --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/XML.php @@ -0,0 +1,485 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\Crypt\EC\Formats\Keys; + +use phpseclib3\Common\Functions\Strings; +use phpseclib3\Crypt\EC\BaseCurves\Base as BaseCurve; +use phpseclib3\Crypt\EC\BaseCurves\Montgomery as MontgomeryCurve; +use phpseclib3\Crypt\EC\BaseCurves\Prime as PrimeCurve; +use phpseclib3\Crypt\EC\BaseCurves\TwistedEdwards as TwistedEdwardsCurve; +use phpseclib3\Exception\BadConfigurationException; +use phpseclib3\Exception\UnsupportedCurveException; +use phpseclib3\Math\BigInteger; + +/** + * XML Formatted EC Key Handler + * + * @author Jim Wigginton + */ +abstract class XML +{ + use Common; + + /** + * Default namespace + * + * @var string + */ + private static $namespace; + + /** + * Flag for using RFC4050 syntax + * + * @var bool + */ + private static $rfc4050 = false; + + /** + * Break a public or private key down into its constituent components + * + * @param string $key + * @param string $password optional + * @return array + */ + public static function load($key, $password = '') + { + self::initialize_static_variables(); + + if (!Strings::is_stringable($key)) { + throw new \UnexpectedValueException('Key should be a string - not a ' . gettype($key)); + } + + if (!class_exists('DOMDocument')) { + throw new BadConfigurationException('The dom extension is not setup correctly on this system'); + } + + $use_errors = libxml_use_internal_errors(true); + + $temp = self::isolateNamespace($key, 'http://www.w3.org/2009/xmldsig11#'); + if ($temp) { + $key = $temp; + } + + $temp = self::isolateNamespace($key, 'http://www.w3.org/2001/04/xmldsig-more#'); + if ($temp) { + $key = $temp; + } + + $dom = new \DOMDocument(); + if (substr($key, 0, 5) != '' . $key . ''; + } + + if (!$dom->loadXML($key)) { + libxml_use_internal_errors($use_errors); + throw new \UnexpectedValueException('Key does not appear to contain XML'); + } + $xpath = new \DOMXPath($dom); + libxml_use_internal_errors($use_errors); + $curve = self::loadCurveByParam($xpath); + + $pubkey = self::query($xpath, 'publickey', 'Public Key is not present'); + + $QA = self::query($xpath, 'ecdsakeyvalue')->length ? + self::extractPointRFC4050($xpath, $curve) : + self::extractPoint("\0" . $pubkey, $curve); + + libxml_use_internal_errors($use_errors); + + return compact('curve', 'QA'); + } + + /** + * Case-insensitive xpath query + * + * @param \DOMXPath $xpath + * @param string $name + * @param string $error optional + * @param bool $decode optional + * @return \DOMNodeList + */ + private static function query(\DOMXPath $xpath, $name, $error = null, $decode = true) + { + $query = '/'; + $names = explode('/', $name); + foreach ($names as $name) { + $query .= "/*[translate(local-name(), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz')='$name']"; + } + $result = $xpath->query($query); + if (!isset($error)) { + return $result; + } + + if (!$result->length) { + throw new \RuntimeException($error); + } + return $decode ? self::decodeValue($result->item(0)->textContent) : $result->item(0)->textContent; + } + + /** + * Finds the first element in the relevant namespace, strips the namespacing and returns the XML for that element. + * + * @param string $xml + * @param string $ns + */ + private static function isolateNamespace($xml, $ns) + { + $dom = new \DOMDocument(); + if (!$dom->loadXML($xml)) { + return false; + } + $xpath = new \DOMXPath($dom); + $nodes = $xpath->query("//*[namespace::*[.='$ns'] and not(../namespace::*[.='$ns'])]"); + if (!$nodes->length) { + return false; + } + $node = $nodes->item(0); + $ns_name = $node->lookupPrefix($ns); + if ($ns_name) { + $node->removeAttributeNS($ns, $ns_name); + } + return $dom->saveXML($node); + } + + /** + * Decodes the value + * + * @param string $value + */ + private static function decodeValue($value) + { + return Strings::base64_decode(str_replace(["\r", "\n", ' ', "\t"], '', $value)); + } + + /** + * Extract points from an XML document + * + * @param \DOMXPath $xpath + * @param \phpseclib3\Crypt\EC\BaseCurves\Base $curve + * @return object[] + */ + private static function extractPointRFC4050(\DOMXPath $xpath, BaseCurve $curve) + { + $x = self::query($xpath, 'publickey/x'); + $y = self::query($xpath, 'publickey/y'); + if (!$x->length || !$x->item(0)->hasAttribute('Value')) { + throw new \RuntimeException('Public Key / X coordinate not found'); + } + if (!$y->length || !$y->item(0)->hasAttribute('Value')) { + throw new \RuntimeException('Public Key / Y coordinate not found'); + } + $point = [ + $curve->convertInteger(new BigInteger($x->item(0)->getAttribute('Value'))), + $curve->convertInteger(new BigInteger($y->item(0)->getAttribute('Value'))) + ]; + if (!$curve->verifyPoint($point)) { + throw new \RuntimeException('Unable to verify that point exists on curve'); + } + return $point; + } + + /** + * Returns an instance of \phpseclib3\Crypt\EC\BaseCurves\Base based + * on the curve parameters + * + * @param \DomXPath $xpath + * @return \phpseclib3\Crypt\EC\BaseCurves\Base|false + */ + private static function loadCurveByParam(\DOMXPath $xpath) + { + $namedCurve = self::query($xpath, 'namedcurve'); + if ($namedCurve->length == 1) { + $oid = $namedCurve->item(0)->getAttribute('URN'); + $oid = preg_replace('#[^\d.]#', '', $oid); + $name = array_search($oid, self::$curveOIDs); + if ($name === false) { + throw new UnsupportedCurveException('Curve with OID of ' . $oid . ' is not supported'); + } + + $curve = '\phpseclib3\Crypt\EC\Curves\\' . $name; + if (!class_exists($curve)) { + throw new UnsupportedCurveException('Named Curve of ' . $name . ' is not supported'); + } + return new $curve(); + } + + $params = self::query($xpath, 'explicitparams'); + if ($params->length) { + return self::loadCurveByParamRFC4050($xpath); + } + + $params = self::query($xpath, 'ecparameters'); + if (!$params->length) { + throw new \RuntimeException('No parameters are present'); + } + + $fieldTypes = [ + 'prime-field' => ['fieldid/prime/p'], + 'gnb' => ['fieldid/gnb/m'], + 'tnb' => ['fieldid/tnb/k'], + 'pnb' => ['fieldid/pnb/k1', 'fieldid/pnb/k2', 'fieldid/pnb/k3'], + 'unknown' => [] + ]; + + foreach ($fieldTypes as $type => $queries) { + foreach ($queries as $query) { + $result = self::query($xpath, $query); + if (!$result->length) { + continue 2; + } + $param = preg_replace('#.*/#', '', $query); + $$param = self::decodeValue($result->item(0)->textContent); + } + break; + } + + $a = self::query($xpath, 'curve/a', 'A coefficient is not present'); + $b = self::query($xpath, 'curve/b', 'B coefficient is not present'); + $base = self::query($xpath, 'base', 'Base point is not present'); + $order = self::query($xpath, 'order', 'Order is not present'); + + switch ($type) { + case 'prime-field': + $curve = new PrimeCurve(); + $curve->setModulo(new BigInteger($p, 256)); + $curve->setCoefficients( + new BigInteger($a, 256), + new BigInteger($b, 256) + ); + $point = self::extractPoint("\0" . $base, $curve); + $curve->setBasePoint(...$point); + $curve->setOrder(new BigInteger($order, 256)); + return $curve; + case 'gnb': + case 'tnb': + case 'pnb': + default: + throw new UnsupportedCurveException('Field Type of ' . $type . ' is not supported'); + } + } + + /** + * Returns an instance of \phpseclib3\Crypt\EC\BaseCurves\Base based + * on the curve parameters + * + * @param \DomXPath $xpath + * @return \phpseclib3\Crypt\EC\BaseCurves\Base|false + */ + private static function loadCurveByParamRFC4050(\DOMXPath $xpath) + { + $fieldTypes = [ + 'prime-field' => ['primefieldparamstype/p'], + 'unknown' => [] + ]; + + foreach ($fieldTypes as $type => $queries) { + foreach ($queries as $query) { + $result = self::query($xpath, $query); + if (!$result->length) { + continue 2; + } + $param = preg_replace('#.*/#', '', $query); + $$param = $result->item(0)->textContent; + } + break; + } + + $a = self::query($xpath, 'curveparamstype/a', 'A coefficient is not present', false); + $b = self::query($xpath, 'curveparamstype/b', 'B coefficient is not present', false); + $x = self::query($xpath, 'basepointparams/basepoint/ecpointtype/x', 'Base Point X is not present', false); + $y = self::query($xpath, 'basepointparams/basepoint/ecpointtype/y', 'Base Point Y is not present', false); + $order = self::query($xpath, 'order', 'Order is not present', false); + + switch ($type) { + case 'prime-field': + $curve = new PrimeCurve(); + + $p = str_replace(["\r", "\n", ' ', "\t"], '', $p); + $curve->setModulo(new BigInteger($p)); + + $a = str_replace(["\r", "\n", ' ', "\t"], '', $a); + $b = str_replace(["\r", "\n", ' ', "\t"], '', $b); + $curve->setCoefficients( + new BigInteger($a), + new BigInteger($b) + ); + + $x = str_replace(["\r", "\n", ' ', "\t"], '', $x); + $y = str_replace(["\r", "\n", ' ', "\t"], '', $y); + $curve->setBasePoint( + new BigInteger($x), + new BigInteger($y) + ); + + $order = str_replace(["\r", "\n", ' ', "\t"], '', $order); + $curve->setOrder(new BigInteger($order)); + return $curve; + default: + throw new UnsupportedCurveException('Field Type of ' . $type . ' is not supported'); + } + } + + /** + * Sets the namespace. dsig11 is the most common one. + * + * Set to null to unset. Used only for creating public keys. + * + * @param string $namespace + */ + public static function setNamespace($namespace) + { + self::$namespace = $namespace; + } + + /** + * Uses the XML syntax specified in https://tools.ietf.org/html/rfc4050 + */ + public static function enableRFC4050Syntax() + { + self::$rfc4050 = true; + } + + /** + * Uses the XML syntax specified in https://www.w3.org/TR/xmldsig-core/#sec-ECParameters + */ + public static function disableRFC4050Syntax() + { + self::$rfc4050 = false; + } + + /** + * Convert a public key to the appropriate format + * + * @param \phpseclib3\Crypt\EC\BaseCurves\Base $curve + * @param \phpseclib3\Math\Common\FiniteField\Integer[] $publicKey + * @param array $options optional + * @return string + */ + public static function savePublicKey(BaseCurve $curve, array $publicKey, array $options = []) + { + self::initialize_static_variables(); + + if ($curve instanceof TwistedEdwardsCurve || $curve instanceof MontgomeryCurve) { + throw new UnsupportedCurveException('TwistedEdwards and Montgomery Curves are not supported'); + } + + if (empty(static::$namespace)) { + $pre = $post = ''; + } else { + $pre = static::$namespace . ':'; + $post = ':' . static::$namespace; + } + + if (self::$rfc4050) { + return '<' . $pre . 'ECDSAKeyValue xmlns' . $post . '="http://www.w3.org/2001/04/xmldsig-more#">' . "\r\n" . + self::encodeXMLParameters($curve, $pre, $options) . "\r\n" . + '<' . $pre . 'PublicKey>' . "\r\n" . + '<' . $pre . 'X Value="' . $publicKey[0] . '" />' . "\r\n" . + '<' . $pre . 'Y Value="' . $publicKey[1] . '" />' . "\r\n" . + '' . "\r\n" . + ''; + } + + $publicKey = "\4" . $publicKey[0]->toBytes() . $publicKey[1]->toBytes(); + + return '<' . $pre . 'ECDSAKeyValue xmlns' . $post . '="http://www.w3.org/2009/xmldsig11#">' . "\r\n" . + self::encodeXMLParameters($curve, $pre, $options) . "\r\n" . + '<' . $pre . 'PublicKey>' . Strings::base64_encode($publicKey) . '' . "\r\n" . + ''; + } + + /** + * Encode Parameters + * + * @param \phpseclib3\Crypt\EC\BaseCurves\Base $curve + * @param string $pre + * @param array $options optional + * @return string|false + */ + private static function encodeXMLParameters(BaseCurve $curve, $pre, array $options = []) + { + $result = self::encodeParameters($curve, true, $options); + + if (isset($result['namedCurve'])) { + $namedCurve = '<' . $pre . 'NamedCurve URI="urn:oid:' . self::$curveOIDs[$result['namedCurve']] . '" />'; + return self::$rfc4050 ? + '' . str_replace('URI', 'URN', $namedCurve) . '' : + $namedCurve; + } + + if (self::$rfc4050) { + $xml = '<' . $pre . 'ExplicitParams>' . "\r\n" . + '<' . $pre . 'FieldParams>' . "\r\n"; + $temp = $result['specifiedCurve']; + switch ($temp['fieldID']['fieldType']) { + case 'prime-field': + $xml .= '<' . $pre . 'PrimeFieldParamsType>' . "\r\n" . + '<' . $pre . 'P>' . $temp['fieldID']['parameters'] . '' . "\r\n" . + '' . "\r\n"; + $a = $curve->getA(); + $b = $curve->getB(); + list($x, $y) = $curve->getBasePoint(); + break; + default: + throw new UnsupportedCurveException('Field Type of ' . $temp['fieldID']['fieldType'] . ' is not supported'); + } + $xml .= '' . "\r\n" . + '<' . $pre . 'CurveParamsType>' . "\r\n" . + '<' . $pre . 'A>' . $a . '' . "\r\n" . + '<' . $pre . 'B>' . $b . '' . "\r\n" . + '' . "\r\n" . + '<' . $pre . 'BasePointParams>' . "\r\n" . + '<' . $pre . 'BasePoint>' . "\r\n" . + '<' . $pre . 'ECPointType>' . "\r\n" . + '<' . $pre . 'X>' . $x . '' . "\r\n" . + '<' . $pre . 'Y>' . $y . '' . "\r\n" . + '' . "\r\n" . + '' . "\r\n" . + '<' . $pre . 'Order>' . $curve->getOrder() . '' . "\r\n" . + '' . "\r\n" . + '' . "\r\n"; + + return $xml; + } + + if (isset($result['specifiedCurve'])) { + $xml = '<' . $pre . 'ECParameters>' . "\r\n" . + '<' . $pre . 'FieldID>' . "\r\n"; + $temp = $result['specifiedCurve']; + switch ($temp['fieldID']['fieldType']) { + case 'prime-field': + $xml .= '<' . $pre . 'Prime>' . "\r\n" . + '<' . $pre . 'P>' . Strings::base64_encode($temp['fieldID']['parameters']->toBytes()) . '' . "\r\n" . + '' . "\r\n" ; + break; + default: + throw new UnsupportedCurveException('Field Type of ' . $temp['fieldID']['fieldType'] . ' is not supported'); + } + $xml .= '' . "\r\n" . + '<' . $pre . 'Curve>' . "\r\n" . + '<' . $pre . 'A>' . Strings::base64_encode($temp['curve']['a']) . '' . "\r\n" . + '<' . $pre . 'B>' . Strings::base64_encode($temp['curve']['b']) . '' . "\r\n" . + '' . "\r\n" . + '<' . $pre . 'Base>' . Strings::base64_encode($temp['base']) . '' . "\r\n" . + '<' . $pre . 'Order>' . Strings::base64_encode($temp['order']) . '' . "\r\n" . + ''; + return $xml; + } + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/libsodium.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/libsodium.php new file mode 100644 index 00000000..2be6ba59 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/libsodium.php @@ -0,0 +1,116 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\Crypt\EC\Formats\Keys; + +use phpseclib3\Crypt\EC\Curves\Ed25519; +use phpseclib3\Exception\UnsupportedFormatException; +use phpseclib3\Math\BigInteger; + +/** + * libsodium Key Handler + * + * @author Jim Wigginton + */ +abstract class libsodium +{ + use Common; + + /** + * Is invisible flag + * + */ + const IS_INVISIBLE = true; + + /** + * Break a public or private key down into its constituent components + * + * @param string $key + * @param string $password optional + * @return array + */ + public static function load($key, $password = '') + { + switch (strlen($key)) { + case 32: + $public = $key; + break; + case 64: + $private = substr($key, 0, 32); + $public = substr($key, -32); + break; + case 96: + $public = substr($key, -32); + if (substr($key, 32, 32) != $public) { + throw new \RuntimeException('Keys with 96 bytes should have the 2nd and 3rd set of 32 bytes match'); + } + $private = substr($key, 0, 32); + break; + default: + throw new \RuntimeException('libsodium keys need to either be 32 bytes long, 64 bytes long or 96 bytes long'); + } + + $curve = new Ed25519(); + $components = ['curve' => $curve]; + if (isset($private)) { + $arr = $curve->extractSecret($private); + $components['dA'] = $arr['dA']; + $components['secret'] = $arr['secret']; + } + $components['QA'] = isset($public) ? + self::extractPoint($public, $curve) : + $curve->multiplyPoint($curve->getBasePoint(), $components['dA']); + + return $components; + } + + /** + * Convert an EC public key to the appropriate format + * + * @param \phpseclib3\Crypt\EC\Curves\Ed25519 $curve + * @param \phpseclib3\Math\Common\FiniteField\Integer[] $publicKey + * @return string + */ + public static function savePublicKey(Ed25519 $curve, array $publicKey) + { + return $curve->encodePoint($publicKey); + } + + /** + * Convert a private key to the appropriate format. + * + * @param \phpseclib3\Math\BigInteger $privateKey + * @param \phpseclib3\Crypt\EC\Curves\Ed25519 $curve + * @param \phpseclib3\Math\Common\FiniteField\Integer[] $publicKey + * @param string $secret optional + * @param string $password optional + * @return string + */ + public static function savePrivateKey(BigInteger $privateKey, Ed25519 $curve, array $publicKey, $secret = null, $password = '') + { + if (!isset($secret)) { + throw new \RuntimeException('Private Key does not have a secret set'); + } + if (strlen($secret) != 32) { + throw new \RuntimeException('Private Key secret is not of the correct length'); + } + if (!empty($password) && is_string($password)) { + throw new UnsupportedFormatException('libsodium private keys do not support encryption'); + } + return $secret . $curve->encodePoint($publicKey); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Signature/ASN1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Signature/ASN1.php new file mode 100644 index 00000000..d2a80a14 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Signature/ASN1.php @@ -0,0 +1,62 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\Crypt\EC\Formats\Signature; + +use phpseclib3\File\ASN1 as Encoder; +use phpseclib3\File\ASN1\Maps\EcdsaSigValue; +use phpseclib3\Math\BigInteger; + +/** + * ASN1 Signature Handler + * + * @author Jim Wigginton + */ +abstract class ASN1 +{ + /** + * Loads a signature + * + * @param string $sig + * @return array + */ + public static function load($sig) + { + if (!is_string($sig)) { + return false; + } + + $decoded = Encoder::decodeBER($sig); + if (empty($decoded)) { + return false; + } + $components = Encoder::asn1map($decoded[0], EcdsaSigValue::MAP); + + return $components; + } + + /** + * Returns a signature in the appropriate format + * + * @param \phpseclib3\Math\BigInteger $r + * @param \phpseclib3\Math\BigInteger $s + * @return string + */ + public static function save(BigInteger $r, BigInteger $s) + { + return Encoder::encodeDER(compact('r', 's'), EcdsaSigValue::MAP); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Signature/Raw.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Signature/Raw.php new file mode 100644 index 00000000..7e4b47fe --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Signature/Raw.php @@ -0,0 +1,25 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\Crypt\EC\Formats\Signature; + +use phpseclib3\Crypt\Common\Formats\Signature\Raw as Progenitor; + +/** + * Raw DSA Signature Handler + * + * @author Jim Wigginton + */ +abstract class Raw extends Progenitor +{ +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Signature/SSH2.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Signature/SSH2.php new file mode 100644 index 00000000..e0644421 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Signature/SSH2.php @@ -0,0 +1,94 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\Crypt\EC\Formats\Signature; + +use phpseclib3\Common\Functions\Strings; +use phpseclib3\Math\BigInteger; + +/** + * SSH2 Signature Handler + * + * @author Jim Wigginton + */ +abstract class SSH2 +{ + /** + * Loads a signature + * + * @param string $sig + * @return mixed + */ + public static function load($sig) + { + if (!is_string($sig)) { + return false; + } + + $result = Strings::unpackSSH2('ss', $sig); + if ($result === false) { + return false; + } + list($type, $blob) = $result; + switch ($type) { + // see https://tools.ietf.org/html/rfc5656#section-3.1.2 + case 'ecdsa-sha2-nistp256': + case 'ecdsa-sha2-nistp384': + case 'ecdsa-sha2-nistp521': + break; + default: + return false; + } + + $result = Strings::unpackSSH2('ii', $blob); + if ($result === false) { + return false; + } + + return [ + 'r' => $result[0], + 's' => $result[1] + ]; + } + + /** + * Returns a signature in the appropriate format + * + * @param \phpseclib3\Math\BigInteger $r + * @param \phpseclib3\Math\BigInteger $s + * @param string $curve + * @return string + */ + public static function save(BigInteger $r, BigInteger $s, $curve) + { + switch ($curve) { + case 'secp256r1': + $curve = 'nistp256'; + break; + case 'secp384r1': + $curve = 'nistp384'; + break; + case 'secp521r1': + $curve = 'nistp521'; + break; + default: + return false; + } + + $blob = Strings::packSSH2('ii', $r, $s); + + return Strings::packSSH2('ss', 'ecdsa-sha2-' . $curve, $blob); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Parameters.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Parameters.php new file mode 100644 index 00000000..c9bf1bea --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Parameters.php @@ -0,0 +1,36 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\Crypt\EC; + +use phpseclib3\Crypt\EC; + +/** + * EC Parameters + * + * @author Jim Wigginton + */ +class Parameters extends EC +{ + /** + * Returns the parameters + * + * @param string $type + * @param array $options optional + * @return string + */ + public function toString($type = 'PKCS1', array $options = []) + { + $type = self::validatePlugin('Keys', 'PKCS1', 'saveParameters'); + + return $type::saveParameters($this->curve, $options); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/PrivateKey.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/PrivateKey.php new file mode 100644 index 00000000..8bf86862 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/PrivateKey.php @@ -0,0 +1,256 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\Crypt\EC; + +use phpseclib3\Common\Functions\Strings; +use phpseclib3\Crypt\Common; +use phpseclib3\Crypt\EC; +use phpseclib3\Crypt\EC\BaseCurves\Montgomery as MontgomeryCurve; +use phpseclib3\Crypt\EC\BaseCurves\TwistedEdwards as TwistedEdwardsCurve; +use phpseclib3\Crypt\EC\Curves\Curve25519; +use phpseclib3\Crypt\EC\Curves\Ed25519; +use phpseclib3\Crypt\EC\Formats\Keys\PKCS1; +use phpseclib3\Crypt\EC\Formats\Signature\ASN1 as ASN1Signature; +use phpseclib3\Crypt\Hash; +use phpseclib3\Exception\UnsupportedOperationException; +use phpseclib3\Math\BigInteger; + +/** + * EC Private Key + * + * @author Jim Wigginton + */ +class PrivateKey extends EC implements Common\PrivateKey +{ + use Common\Traits\PasswordProtected; + + /** + * Private Key dA + * + * sign() converts this to a BigInteger so one might wonder why this is a FiniteFieldInteger instead of + * a BigInteger. That's because a FiniteFieldInteger, when converted to a byte string, is null padded by + * a certain amount whereas a BigInteger isn't. + * + * @var object + */ + protected $dA; + + /** + * @var string + */ + protected $secret; + + /** + * Multiplies an encoded point by the private key + * + * Used by ECDH + * + * @param string $coordinates + * @return string + */ + public function multiply($coordinates) + { + if ($this->curve instanceof MontgomeryCurve) { + if ($this->curve instanceof Curve25519 && self::$engines['libsodium']) { + return sodium_crypto_scalarmult($this->dA->toBytes(), $coordinates); + } + + $point = [$this->curve->convertInteger(new BigInteger(strrev($coordinates), 256))]; + $point = $this->curve->multiplyPoint($point, $this->dA); + return strrev($point[0]->toBytes(true)); + } + if (!$this->curve instanceof TwistedEdwardsCurve) { + $coordinates = "\0$coordinates"; + } + $point = PKCS1::extractPoint($coordinates, $this->curve); + $point = $this->curve->multiplyPoint($point, $this->dA); + if ($this->curve instanceof TwistedEdwardsCurve) { + return $this->curve->encodePoint($point); + } + if (empty($point)) { + throw new \RuntimeException('The infinity point is invalid'); + } + return "\4" . $point[0]->toBytes(true) . $point[1]->toBytes(true); + } + + /** + * Create a signature + * + * @see self::verify() + * @param string $message + * @return mixed + */ + public function sign($message) + { + if ($this->curve instanceof MontgomeryCurve) { + throw new UnsupportedOperationException('Montgomery Curves cannot be used to create signatures'); + } + + $dA = $this->dA; + $order = $this->curve->getOrder(); + + $shortFormat = $this->shortFormat; + $format = $this->sigFormat; + if ($format === false) { + return false; + } + + if ($this->curve instanceof TwistedEdwardsCurve) { + if ($this->curve instanceof Ed25519 && self::$engines['libsodium'] && !isset($this->context)) { + $result = sodium_crypto_sign_detached($message, $this->withPassword()->toString('libsodium')); + return $shortFormat == 'SSH2' ? Strings::packSSH2('ss', 'ssh-' . strtolower($this->getCurve()), $result) : $result; + } + + // contexts (Ed25519ctx) are supported but prehashing (Ed25519ph) is not. + // quoting https://tools.ietf.org/html/rfc8032#section-8.5 , + // "The Ed25519ph and Ed448ph variants ... SHOULD NOT be used" + $A = $this->curve->encodePoint($this->QA); + $curve = $this->curve; + $hash = new Hash($curve::HASH); + + $secret = substr($hash->hash($this->secret), $curve::SIZE); + + if ($curve instanceof Ed25519) { + $dom = !isset($this->context) ? '' : + 'SigEd25519 no Ed25519 collisions' . "\0" . chr(strlen($this->context)) . $this->context; + } else { + $context = isset($this->context) ? $this->context : ''; + $dom = 'SigEd448' . "\0" . chr(strlen($context)) . $context; + } + // SHA-512(dom2(F, C) || prefix || PH(M)) + $r = $hash->hash($dom . $secret . $message); + $r = strrev($r); + $r = new BigInteger($r, 256); + list(, $r) = $r->divide($order); + $R = $curve->multiplyPoint($curve->getBasePoint(), $r); + $R = $curve->encodePoint($R); + $k = $hash->hash($dom . $R . $A . $message); + $k = strrev($k); + $k = new BigInteger($k, 256); + list(, $k) = $k->divide($order); + $S = $k->multiply($dA)->add($r); + list(, $S) = $S->divide($order); + $S = str_pad(strrev($S->toBytes()), $curve::SIZE, "\0"); + return $shortFormat == 'SSH2' ? Strings::packSSH2('ss', 'ssh-' . strtolower($this->getCurve()), $R . $S) : $R . $S; + } + + if (self::$engines['OpenSSL'] && in_array($this->hash->getHash(), openssl_get_md_methods())) { + $signature = ''; + // altho PHP's OpenSSL bindings only supported EC key creation in PHP 7.1 they've long + // supported signing / verification + // we use specified curves to avoid issues with OpenSSL possibly not supporting a given named curve; + // doing this may mean some curve-specific optimizations can't be used but idk if OpenSSL even + // has curve-specific optimizations + $result = openssl_sign($message, $signature, $this->toString('PKCS8', ['namedCurve' => false]), $this->hash->getHash()); + + if ($result) { + if ($shortFormat == 'ASN1') { + return $signature; + } + + extract(ASN1Signature::load($signature)); + + return $shortFormat == 'SSH2' ? $format::save($r, $s, $this->getCurve()) : $format::save($r, $s); + } + } + + $e = $this->hash->hash($message); + $e = new BigInteger($e, 256); + + $Ln = $this->hash->getLength() - $order->getLength(); + $z = $Ln > 0 ? $e->bitwise_rightShift($Ln) : $e; + + while (true) { + $k = BigInteger::randomRange(self::$one, $order->subtract(self::$one)); + list($x, $y) = $this->curve->multiplyPoint($this->curve->getBasePoint(), $k); + $x = $x->toBigInteger(); + list(, $r) = $x->divide($order); + if ($r->equals(self::$zero)) { + continue; + } + $kinv = $k->modInverse($order); + $temp = $z->add($dA->multiply($r)); + $temp = $kinv->multiply($temp); + list(, $s) = $temp->divide($order); + if (!$s->equals(self::$zero)) { + break; + } + } + + // the following is an RFC6979 compliant implementation of deterministic ECDSA + // it's unused because it's mainly intended for use when a good CSPRNG isn't + // available. if phpseclib's CSPRNG isn't good then even key generation is + // suspect + /* + // if this were actually being used it'd probably be better if this lived in load() and createKey() + $this->q = $this->curve->getOrder(); + $dA = $this->dA->toBigInteger(); + $this->x = $dA; + + $h1 = $this->hash->hash($message); + $k = $this->computek($h1); + list($x, $y) = $this->curve->multiplyPoint($this->curve->getBasePoint(), $k); + $x = $x->toBigInteger(); + list(, $r) = $x->divide($this->q); + $kinv = $k->modInverse($this->q); + $h1 = $this->bits2int($h1); + $temp = $h1->add($dA->multiply($r)); + $temp = $kinv->multiply($temp); + list(, $s) = $temp->divide($this->q); + */ + + return $shortFormat == 'SSH2' ? $format::save($r, $s, $this->getCurve()) : $format::save($r, $s); + } + + /** + * Returns the private key + * + * @param string $type + * @param array $options optional + * @return string + */ + public function toString($type, array $options = []) + { + $type = self::validatePlugin('Keys', $type, 'savePrivateKey'); + + return $type::savePrivateKey($this->dA, $this->curve, $this->QA, $this->secret, $this->password, $options); + } + + /** + * Returns the public key + * + * @see self::getPrivateKey() + * @return mixed + */ + public function getPublicKey() + { + $format = 'PKCS8'; + if ($this->curve instanceof MontgomeryCurve) { + $format = 'MontgomeryPublic'; + } + + $type = self::validatePlugin('Keys', $format, 'savePublicKey'); + + $key = $type::savePublicKey($this->curve, $this->QA); + $key = EC::loadFormat($format, $key); + if ($this->curve instanceof MontgomeryCurve) { + return $key; + } + $key = $key + ->withHash($this->hash->getHash()) + ->withSignatureFormat($this->shortFormat); + if ($this->curve instanceof TwistedEdwardsCurve) { + $key = $key->withContext($this->context); + } + return $key; + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/PublicKey.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/PublicKey.php new file mode 100644 index 00000000..609d5960 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/PublicKey.php @@ -0,0 +1,172 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\Crypt\EC; + +use phpseclib3\Common\Functions\Strings; +use phpseclib3\Crypt\Common; +use phpseclib3\Crypt\EC; +use phpseclib3\Crypt\EC\BaseCurves\Montgomery as MontgomeryCurve; +use phpseclib3\Crypt\EC\BaseCurves\TwistedEdwards as TwistedEdwardsCurve; +use phpseclib3\Crypt\EC\Curves\Ed25519; +use phpseclib3\Crypt\EC\Formats\Keys\PKCS1; +use phpseclib3\Crypt\EC\Formats\Signature\ASN1 as ASN1Signature; +use phpseclib3\Crypt\Hash; +use phpseclib3\Exception\UnsupportedOperationException; +use phpseclib3\Math\BigInteger; + +/** + * EC Public Key + * + * @author Jim Wigginton + */ +class PublicKey extends EC implements Common\PublicKey +{ + use Common\Traits\Fingerprint; + + /** + * Verify a signature + * + * @see self::verify() + * @param string $message + * @param string $signature + * @return mixed + */ + public function verify($message, $signature) + { + if ($this->curve instanceof MontgomeryCurve) { + throw new UnsupportedOperationException('Montgomery Curves cannot be used to create signatures'); + } + + $shortFormat = $this->shortFormat; + $format = $this->sigFormat; + if ($format === false) { + return false; + } + + $order = $this->curve->getOrder(); + + if ($this->curve instanceof TwistedEdwardsCurve) { + if ($shortFormat == 'SSH2') { + list(, $signature) = Strings::unpackSSH2('ss', $signature); + } + + if ($this->curve instanceof Ed25519 && self::$engines['libsodium'] && !isset($this->context)) { + return sodium_crypto_sign_verify_detached($signature, $message, $this->toString('libsodium')); + } + + $curve = $this->curve; + if (strlen($signature) != 2 * $curve::SIZE) { + return false; + } + + $R = substr($signature, 0, $curve::SIZE); + $S = substr($signature, $curve::SIZE); + + try { + $R = PKCS1::extractPoint($R, $curve); + $R = $this->curve->convertToInternal($R); + } catch (\Exception $e) { + return false; + } + + $S = strrev($S); + $S = new BigInteger($S, 256); + + if ($S->compare($order) >= 0) { + return false; + } + + $A = $curve->encodePoint($this->QA); + + if ($curve instanceof Ed25519) { + $dom2 = !isset($this->context) ? '' : + 'SigEd25519 no Ed25519 collisions' . "\0" . chr(strlen($this->context)) . $this->context; + } else { + $context = isset($this->context) ? $this->context : ''; + $dom2 = 'SigEd448' . "\0" . chr(strlen($context)) . $context; + } + + $hash = new Hash($curve::HASH); + $k = $hash->hash($dom2 . substr($signature, 0, $curve::SIZE) . $A . $message); + $k = strrev($k); + $k = new BigInteger($k, 256); + list(, $k) = $k->divide($order); + + $qa = $curve->convertToInternal($this->QA); + + $lhs = $curve->multiplyPoint($curve->getBasePoint(), $S); + $rhs = $curve->multiplyPoint($qa, $k); + $rhs = $curve->addPoint($rhs, $R); + $rhs = $curve->convertToAffine($rhs); + + return $lhs[0]->equals($rhs[0]) && $lhs[1]->equals($rhs[1]); + } + + $params = $format::load($signature); + if ($params === false || count($params) != 2) { + return false; + } + extract($params); + + if (self::$engines['OpenSSL'] && in_array($this->hash->getHash(), openssl_get_md_methods())) { + $sig = $format != 'ASN1' ? ASN1Signature::save($r, $s) : $signature; + + $result = openssl_verify($message, $sig, $this->toString('PKCS8', ['namedCurve' => false]), $this->hash->getHash()); + + if ($result != -1) { + return (bool) $result; + } + } + + $n_1 = $order->subtract(self::$one); + if (!$r->between(self::$one, $n_1) || !$s->between(self::$one, $n_1)) { + return false; + } + + $e = $this->hash->hash($message); + $e = new BigInteger($e, 256); + + $Ln = $this->hash->getLength() - $order->getLength(); + $z = $Ln > 0 ? $e->bitwise_rightShift($Ln) : $e; + + $w = $s->modInverse($order); + list(, $u1) = $z->multiply($w)->divide($order); + list(, $u2) = $r->multiply($w)->divide($order); + + $u1 = $this->curve->convertInteger($u1); + $u2 = $this->curve->convertInteger($u2); + + list($x1, $y1) = $this->curve->multiplyAddPoints( + [$this->curve->getBasePoint(), $this->QA], + [$u1, $u2] + ); + + $x1 = $x1->toBigInteger(); + list(, $x1) = $x1->divide($order); + + return $x1->equals($r); + } + + /** + * Returns the public key + * + * @param string $type + * @param array $options optional + * @return string + */ + public function toString($type, array $options = []) + { + $type = self::validatePlugin('Keys', $type, 'savePublicKey'); + + return $type::savePublicKey($this->curve, $this->QA, $options); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/Hash.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Hash.php index 248b65ef..0e02544e 100644 --- a/vendor/phpseclib/phpseclib/phpseclib/Crypt/Hash.php +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Hash.php @@ -1,26 +1,19 @@ * setKey('abcdefg'); * @@ -28,148 +21,186 @@ * ?> * * - * @category Crypt - * @package Hash * @author Jim Wigginton - * @copyright 2007 Jim Wigginton + * @copyright 2015 Jim Wigginton + * @author Andreas Fischer + * @copyright 2015 Andreas Fischer * @license http://www.opensource.org/licenses/mit-license.html MIT License * @link http://phpseclib.sourceforge.net */ -namespace phpseclib\Crypt; +namespace phpseclib3\Crypt; -use phpseclib\Math\BigInteger; +use phpseclib3\Common\Functions\Strings; +use phpseclib3\Exception\InsufficientSetupException; +use phpseclib3\Exception\UnsupportedAlgorithmException; +use phpseclib3\Math\BigInteger; +use phpseclib3\Math\PrimeField; /** - * Pure-PHP implementations of keyed-hash message authentication codes (HMACs) and various cryptographic hashing functions. - * - * @package Hash * @author Jim Wigginton - * @access public + * @author Andreas Fischer */ class Hash { - /**#@+ - * @access private - * @see \phpseclib\Crypt\Hash::__construct() - */ /** - * Toggles the internal implementation + * Padding Types + * */ - const MODE_INTERNAL = 1; + const PADDING_KECCAK = 1; + /** - * Toggles the mhash() implementation, which has been deprecated on PHP 5.3.0+. + * Padding Types + * */ - const MODE_MHASH = 2; + const PADDING_SHA3 = 2; + /** - * Toggles the hash() implementation, which works on PHP 5.1.2+. + * Padding Types + * */ - const MODE_HASH = 3; - /**#@-*/ + const PADDING_SHAKE = 3; /** - * Hash Parameter + * Padding Type + * + * Only used by SHA3 * - * @see self::setHash() * @var int - * @access private */ - var $hashParam; + private $paddingType = 0; /** - * Byte-length of compression blocks / key (Internal HMAC) + * Hash Parameter * - * @see self::setAlgorithm() + * @see self::setHash() * @var int - * @access private */ - var $b; + private $hashParam; /** * Byte-length of hash output (Internal HMAC) * * @see self::setHash() * @var int - * @access private */ - var $l = false; + private $length; /** * Hash Algorithm * * @see self::setHash() * @var string - * @access private */ - var $hash; + private $algo; /** * Key * * @see self::setKey() * @var string - * @access private */ - var $key = false; + private $key = false; + + /** + * Nonce + * + * @see self::setNonce() + * @var string + */ + private $nonce = false; + + /** + * Hash Parameters + * + * @var array + */ + private $parameters = []; /** * Computed Key * * @see self::_computeKey() * @var string - * @access private */ - var $computedKey = false; + private $computedKey = false; /** * Outer XOR (Internal HMAC) * - * @see self::setKey() + * Used only for sha512/* + * + * @see self::hash() * @var string - * @access private */ - var $opad; + private $opad; /** * Inner XOR (Internal HMAC) * - * @see self::setKey() + * Used only for sha512/* + * + * @see self::hash() * @var string - * @access private */ - var $ipad; + private $ipad; /** - * Engine + * Recompute AES Key * - * @see self::setHash() + * Used only for umac + * + * @see self::hash() + * @var boolean + */ + private $recomputeAESKey; + + /** + * umac cipher object + * + * @see self::hash() + * @var \phpseclib3\Crypt\AES + */ + private $c; + + /** + * umac pad + * + * @see self::hash() * @var string - * @access private */ - var $engine; + private $pad; + + /** + * Block Size + * + * @var int + */ + private $blockSize; + + /**#@+ + * UMAC variables + * + * @var PrimeField + */ + private static $factory36; + private static $factory64; + private static $factory128; + private static $offset64; + private static $offset128; + private static $marker64; + private static $marker128; + private static $maxwordrange64; + private static $maxwordrange128; + /**#@-*/ /** * Default Constructor. * * @param string $hash - * @return \phpseclib\Crypt\Hash - * @access public */ - function __construct($hash = 'sha1') + public function __construct($hash = 'sha256') { - if (!defined('CRYPT_HASH_MODE')) { - switch (true) { - case extension_loaded('hash'): - define('CRYPT_HASH_MODE', self::MODE_HASH); - break; - case extension_loaded('mhash'): - define('CRYPT_HASH_MODE', self::MODE_MHASH); - break; - default: - define('CRYPT_HASH_MODE', self::MODE_INTERNAL); - } - } - $this->setHash($hash); } @@ -178,13 +209,33 @@ function __construct($hash = 'sha1') * * Keys can be of any length. * - * @access public * @param string $key */ - function setKey($key = false) + public function setKey($key = false) { $this->key = $key; - $this->_computeKey(); + $this->computeKey(); + $this->recomputeAESKey = true; + } + + /** + * Sets the nonce for UMACs + * + * Keys can be of any length. + * + * @param string $nonce + */ + public function setNonce($nonce = false) + { + switch (true) { + case !is_string($nonce): + case strlen($nonce) > 0 && strlen($nonce) <= 16: + $this->recomputeAESKey = true; + $this->nonce = $nonce; + return; + } + + throw new \LengthException('The nonce length must be between 1 and 16 bytes, inclusive'); } /** @@ -197,30 +248,22 @@ function setKey($key = false) * when doing an HMAC multiple times it's faster to compute the hash once instead of computing it during * every call * - * @access private */ - function _computeKey() + private function computeKey() { if ($this->key === false) { $this->computedKey = false; return; } - if (strlen($this->key) <= $this->b) { + if (strlen($this->key) <= $this->getBlockLengthInBytes()) { $this->computedKey = $this->key; return; } - switch ($this->engine) { - case self::MODE_MHASH: - $this->computedKey = mhash($this->hash, $this->key); - break; - case self::MODE_HASH: - $this->computedKey = hash($this->hash, $this->key, true); - break; - case self::MODE_INTERNAL: - $this->computedKey = call_user_func($this->hash, $this->key); - } + $this->computedKey = is_array($this->algo) ? + call_user_func($this->algo, $this->key) : + hash($this->algo, $this->key, true); } /** @@ -228,10 +271,9 @@ function _computeKey() * * As set by the constructor or by the setHash() method. * - * @access public * @return string */ - function getHash() + public function getHash() { return $this->hashParam; } @@ -239,418 +281,1027 @@ function getHash() /** * Sets the hash function. * - * @access public * @param string $hash */ - function setHash($hash) + public function setHash($hash) { $this->hashParam = $hash = strtolower($hash); switch ($hash) { + case 'umac-32': + case 'umac-64': + case 'umac-96': + case 'umac-128': + $this->blockSize = 128; + $this->length = abs(substr($hash, -3)) >> 3; + $this->algo = 'umac'; + return; + case 'md2-96': case 'md5-96': case 'sha1-96': + case 'sha224-96': case 'sha256-96': + case 'sha384-96': case 'sha512-96': + case 'sha512/224-96': + case 'sha512/256-96': $hash = substr($hash, 0, -3); - $this->l = 12; // 96 / 8 = 12 + $this->length = 12; // 96 / 8 = 12 break; case 'md2': case 'md5': - $this->l = 16; + $this->length = 16; break; case 'sha1': - $this->l = 20; + $this->length = 20; break; + case 'sha224': + case 'sha512/224': + case 'sha3-224': + $this->length = 28; + break; + case 'keccak256': + $this->paddingType = self::PADDING_KECCAK; + // fall-through case 'sha256': - $this->l = 32; + case 'sha512/256': + case 'sha3-256': + $this->length = 32; break; case 'sha384': - $this->l = 48; + case 'sha3-384': + $this->length = 48; break; case 'sha512': - $this->l = 64; + case 'sha3-512': + $this->length = 64; + break; + default: + if (preg_match('#^(shake(?:128|256))-(\d+)$#', $hash, $matches)) { + $this->paddingType = self::PADDING_SHAKE; + $hash = $matches[1]; + $this->length = $matches[2] >> 3; + } else { + throw new UnsupportedAlgorithmException( + "$hash is not a supported algorithm" + ); + } } switch ($hash) { - case 'md2-96': case 'md2': - $this->b = 16; + case 'md2-96': + $this->blockSize = 128; + break; case 'md5-96': case 'sha1-96': case 'sha224-96': case 'sha256-96': - case 'md2': case 'md5': case 'sha1': case 'sha224': case 'sha256': - $this->b = 64; + $this->blockSize = 512; break; - default: - $this->b = 128; - } - - switch ($hash) { - case 'md2': - $this->engine = CRYPT_HASH_MODE == self::MODE_HASH && in_array('md2', hash_algos()) ? - self::MODE_HASH : self::MODE_INTERNAL; + case 'sha3-224': + $this->blockSize = 1152; // 1600 - 2*224 break; - case 'sha384': - case 'sha512': - $this->engine = CRYPT_HASH_MODE == self::MODE_MHASH ? self::MODE_INTERNAL : CRYPT_HASH_MODE; + case 'sha3-256': + case 'shake256': + case 'keccak256': + $this->blockSize = 1088; // 1600 - 2*256 + break; + case 'sha3-384': + $this->blockSize = 832; // 1600 - 2*384 + break; + case 'sha3-512': + $this->blockSize = 576; // 1600 - 2*512 + break; + case 'shake128': + $this->blockSize = 1344; // 1600 - 2*128 break; default: - $this->engine = CRYPT_HASH_MODE; + $this->blockSize = 1024; } - switch ($this->engine) { - case self::MODE_MHASH: - switch ($hash) { - case 'md5': - $this->hash = MHASH_MD5; - break; - case 'sha256': - $this->hash = MHASH_SHA256; - break; - case 'sha1': - default: - $this->hash = MHASH_SHA1; + if (in_array(substr($hash, 0, 5), ['sha3-', 'shake', 'kecca'])) { + // PHP 7.1.0 introduced support for "SHA3 fixed mode algorithms": + // http://php.net/ChangeLog-7.php#7.1.0 + if (version_compare(PHP_VERSION, '7.1.0') < 0 || substr($hash, 0, 5) != 'sha3-') { + //preg_match('#(\d+)$#', $hash, $matches); + //$this->parameters['capacity'] = 2 * $matches[1]; // 1600 - $this->blockSize + //$this->parameters['rate'] = 1600 - $this->parameters['capacity']; // == $this->blockSize + if (!$this->paddingType) { + $this->paddingType = self::PADDING_SHA3; } - $this->_computeKey(self::MODE_MHASH); - return; - case self::MODE_HASH: - switch ($hash) { - case 'md5': - $this->hash = 'md5'; - return; - case 'md2': - case 'sha256': - case 'sha384': - case 'sha512': - $this->hash = $hash; - return; - case 'sha1': - default: - $this->hash = 'sha1'; + $this->parameters = [ + 'capacity' => 1600 - $this->blockSize, + 'rate' => $this->blockSize, + 'length' => $this->length, + 'padding' => $this->paddingType + ]; + $hash = ['phpseclib3\Crypt\Hash', PHP_INT_SIZE == 8 ? 'sha3_64' : 'sha3_32']; + } + } + + if ($hash == 'sha512/224' || $hash == 'sha512/256') { + // PHP 7.1.0 introduced sha512/224 and sha512/256 support: + // http://php.net/ChangeLog-7.php#7.1.0 + if (version_compare(PHP_VERSION, '7.1.0') < 0) { + // from http://csrc.nist.gov/publications/fips/fips180-4/fips-180-4.pdf#page=24 + $initial = $hash == 'sha512/256' ? + [ + '22312194FC2BF72C', '9F555FA3C84C64C2', '2393B86B6F53B151', '963877195940EABD', + '96283EE2A88EFFE3', 'BE5E1E2553863992', '2B0199FC2C85B8AA', '0EB72DDC81C52CA2' + ] : + [ + '8C3D37C819544DA2', '73E1996689DCD4D6', '1DFAB7AE32FF9C82', '679DD514582F9FCF', + '0F6D2B697BD44DA8', '77E36F7304C48942', '3F9D85A86A1D36C8', '1112E6AD91D692A1' + ]; + for ($i = 0; $i < 8; $i++) { + $initial[$i] = new BigInteger($initial[$i], 16); + $initial[$i]->setPrecision(64); } - $this->_computeKey(self::MODE_HASH); - return; + + $this->parameters = compact('initial'); + + $hash = ['phpseclib3\Crypt\Hash', 'sha512']; + } } - switch ($hash) { - case 'md2': - $this->hash = array($this, '_md2'); - break; - case 'md5': - $this->hash = array($this, '_md5'); - break; - case 'sha256': - $this->hash = array($this, '_sha256'); - break; - case 'sha384': - case 'sha512': - $this->hash = array($this, '_sha512'); - break; - case 'sha1': - default: - $this->hash = array($this, '_sha1'); + if (is_array($hash)) { + $b = $this->blockSize >> 3; + $this->ipad = str_repeat(chr(0x36), $b); + $this->opad = str_repeat(chr(0x5C), $b); } - $this->ipad = str_repeat(chr(0x36), $this->b); - $this->opad = str_repeat(chr(0x5C), $this->b); + $this->algo = $hash; + + $this->computeKey(); + } + + /** + * KDF: Key-Derivation Function + * + * The key-derivation function generates pseudorandom bits used to key the hash functions. + * + * @param int $index a non-negative integer less than 2^64 + * @param int $numbytes a non-negative integer less than 2^64 + * @return string string of length numbytes bytes + */ + private function kdf($index, $numbytes) + { + $this->c->setIV(pack('N4', 0, $index, 0, 1)); + + return $this->c->encrypt(str_repeat("\0", $numbytes)); + } + + /** + * PDF Algorithm + * + * @return string string of length taglen bytes. + */ + private function pdf() + { + $k = $this->key; + $nonce = $this->nonce; + $taglen = $this->length; + + // + // Extract and zero low bit(s) of Nonce if needed + // + if ($taglen <= 8) { + $last = strlen($nonce) - 1; + $mask = $taglen == 4 ? "\3" : "\1"; + $index = $nonce[$last] & $mask; + $nonce[$last] = $nonce[$last] ^ $index; + } - $this->_computeKey(self::MODE_INTERNAL); + // + // Make Nonce BLOCKLEN bytes by appending zeroes if needed + // + $nonce = str_pad($nonce, 16, "\0"); + + // + // Generate subkey, encipher and extract indexed substring + // + $kp = $this->kdf(0, 16); + $c = new AES('ctr'); + $c->disablePadding(); + $c->setKey($kp); + $c->setIV($nonce); + $t = $c->encrypt("\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"); + + // we could use ord() but per https://paragonie.com/blog/2016/06/constant-time-encoding-boring-cryptography-rfc-4648-and-you + // unpack() doesn't leak timing info + return $taglen <= 8 ? + substr($t, unpack('C', $index)[1] * $taglen, $taglen) : + substr($t, 0, $taglen); } /** - * Compute the HMAC. + * UHASH Algorithm + * + * @param string $m string of length less than 2^67 bits. + * @param int $taglen the integer 4, 8, 12 or 16. + * @return string string of length taglen bytes. + */ + private function uhash($m, $taglen) + { + // + // One internal iteration per 4 bytes of output + // + $iters = $taglen >> 2; + + // + // Define total key needed for all iterations using KDF. + // L1Key reuses most key material between iterations. + // + //$L1Key = $this->kdf(1, 1024 + ($iters - 1) * 16); + $L1Key = $this->kdf(1, (1024 + ($iters - 1)) * 16); + $L2Key = $this->kdf(2, $iters * 24); + $L3Key1 = $this->kdf(3, $iters * 64); + $L3Key2 = $this->kdf(4, $iters * 4); + + // + // For each iteration, extract key and do three-layer hash. + // If bytelength(M) <= 1024, then skip L2-HASH. + // + $y = ''; + for ($i = 0; $i < $iters; $i++) { + $L1Key_i = substr($L1Key, $i * 16, 1024); + $L2Key_i = substr($L2Key, $i * 24, 24); + $L3Key1_i = substr($L3Key1, $i * 64, 64); + $L3Key2_i = substr($L3Key2, $i * 4, 4); + + $a = self::L1Hash($L1Key_i, $m); + $b = strlen($m) <= 1024 ? "\0\0\0\0\0\0\0\0$a" : self::L2Hash($L2Key_i, $a); + $c = self::L3Hash($L3Key1_i, $L3Key2_i, $b); + $y .= $c; + } + + return $y; + } + + /** + * L1-HASH Algorithm + * + * The first-layer hash breaks the message into 1024-byte chunks and + * hashes each with a function called NH. Concatenating the results + * forms a string, which is up to 128 times shorter than the original. + * + * @param string $k string of length 1024 bytes. + * @param string $m string of length less than 2^67 bits. + * @return string string of length (8 * ceil(bitlength(M)/8192)) bytes. + */ + private static function L1Hash($k, $m) + { + // + // Break M into 1024 byte chunks (final chunk may be shorter) + // + $m = str_split($m, 1024); + + // + // For each chunk, except the last: endian-adjust, NH hash + // and add bit-length. Use results to build Y. + // + $length = new BigInteger(1024 * 8); + $y = ''; + for ($i = 0; $i < count($m) - 1; $i++) { + $m[$i] = pack('N*', ...unpack('V*', $m[$i])); // ENDIAN-SWAP + $y .= static::nh($k, $m[$i], $length); + } + + // + // For the last chunk: pad to 32-byte boundary, endian-adjust, + // NH hash and add bit-length. Concatenate the result to Y. + // + $length = count($m) ? strlen($m[$i]) : 0; + $pad = 32 - ($length % 32); + $pad = max(32, $length + $pad % 32); + $m[$i] = str_pad(isset($m[$i]) ? $m[$i] : '', $pad, "\0"); // zeropad + $m[$i] = pack('N*', ...unpack('V*', $m[$i])); // ENDIAN-SWAP + + $y .= static::nh($k, $m[$i], new BigInteger($length * 8)); + + return $y; + } + + /** + * NH Algorithm + * + * @param string $k string of length 1024 bytes. + * @param string $m string with length divisible by 32 bytes. + * @return string string of length 8 bytes. + */ + private static function nh($k, $m, $length) + { + $toUInt32 = function ($x) { + $x = new BigInteger($x, 256); + $x->setPrecision(32); + return $x; + }; + + // + // Break M and K into 4-byte chunks + // + //$t = strlen($m) >> 2; + $m = str_split($m, 4); + $t = count($m); + $k = str_split($k, 4); + $k = array_pad(array_slice($k, 0, $t), $t, 0); + + $m = array_map($toUInt32, $m); + $k = array_map($toUInt32, $k); + + // + // Perform NH hash on the chunks, pairing words for multiplication + // which are 4 apart to accommodate vector-parallelism. + // + $y = new BigInteger(); + $y->setPrecision(64); + $i = 0; + while ($i < $t) { + $temp = $m[$i]->add($k[$i]); + $temp->setPrecision(64); + $temp = $temp->multiply($m[$i + 4]->add($k[$i + 4])); + $y = $y->add($temp); + + $temp = $m[$i + 1]->add($k[$i + 1]); + $temp->setPrecision(64); + $temp = $temp->multiply($m[$i + 5]->add($k[$i + 5])); + $y = $y->add($temp); + + $temp = $m[$i + 2]->add($k[$i + 2]); + $temp->setPrecision(64); + $temp = $temp->multiply($m[$i + 6]->add($k[$i + 6])); + $y = $y->add($temp); + + $temp = $m[$i + 3]->add($k[$i + 3]); + $temp->setPrecision(64); + $temp = $temp->multiply($m[$i + 7]->add($k[$i + 7])); + $y = $y->add($temp); + + $i += 8; + } + + return $y->add($length)->toBytes(); + } + + /** + * L2-HASH: Second-Layer Hash + * + * The second-layer rehashes the L1-HASH output using a polynomial hash + * called POLY. If the L1-HASH output is long, then POLY is called once + * on a prefix of the L1-HASH output and called using different settings + * on the remainder. (This two-step hashing of the L1-HASH output is + * needed only if the message length is greater than 16 megabytes.) + * Careful implementation of POLY is necessary to avoid a possible + * timing attack (see Section 6.6 for more information). + * + * @param string $k string of length 24 bytes. + * @param string $m string of length less than 2^64 bytes. + * @return string string of length 16 bytes. + */ + private static function L2Hash($k, $m) + { + // + // Extract keys and restrict to special key-sets + // + $k64 = $k & "\x01\xFF\xFF\xFF\x01\xFF\xFF\xFF"; + $k64 = new BigInteger($k64, 256); + $k128 = substr($k, 8) & "\x01\xFF\xFF\xFF\x01\xFF\xFF\xFF\x01\xFF\xFF\xFF\x01\xFF\xFF\xFF"; + $k128 = new BigInteger($k128, 256); + + // + // If M is no more than 2^17 bytes, hash under 64-bit prime, + // otherwise, hash first 2^17 bytes under 64-bit prime and + // remainder under 128-bit prime. + // + if (strlen($m) <= 0x20000) { // 2^14 64-bit words + $y = self::poly(64, self::$maxwordrange64, $k64, $m); + } else { + $m_1 = substr($m, 0, 0x20000); // 1 << 17 + $m_2 = substr($m, 0x20000) . "\x80"; + $length = strlen($m_2); + $pad = 16 - ($length % 16); + $pad %= 16; + $m_2 = str_pad($m_2, $length + $pad, "\0"); // zeropad + $y = self::poly(64, self::$maxwordrange64, $k64, $m_1); + $y = str_pad($y, 16, "\0", STR_PAD_LEFT); + $y = self::poly(128, self::$maxwordrange128, $k128, $y . $m_2); + } + + return str_pad($y, 16, "\0", STR_PAD_LEFT); + } + + /** + * POLY Algorithm + * + * @param int $wordbits the integer 64 or 128. + * @param BigInteger $maxwordrange positive integer less than 2^wordbits. + * @param BigInteger $k integer in the range 0 ... prime(wordbits) - 1. + * @param string $m string with length divisible by (wordbits / 8) bytes. + * @return integer in the range 0 ... prime(wordbits) - 1. + */ + private static function poly($wordbits, $maxwordrange, $k, $m) + { + // + // Define constants used for fixing out-of-range words + // + $wordbytes = $wordbits >> 3; + if ($wordbits == 128) { + $factory = self::$factory128; + $offset = self::$offset128; + $marker = self::$marker128; + } else { + $factory = self::$factory64; + $offset = self::$offset64; + $marker = self::$marker64; + } + + $k = $factory->newInteger($k); + + // + // Break M into chunks of length wordbytes bytes + // + $m_i = str_split($m, $wordbytes); + + // + // Each input word m is compared with maxwordrange. If not smaller + // then 'marker' and (m - offset), both in range, are hashed. + // + $y = $factory->newInteger(new BigInteger(1)); + foreach ($m_i as $m) { + $m = $factory->newInteger(new BigInteger($m, 256)); + if ($m->compare($maxwordrange) >= 0) { + $y = $k->multiply($y)->add($marker); + $y = $k->multiply($y)->add($m->subtract($offset)); + } else { + $y = $k->multiply($y)->add($m); + } + } + + return $y->toBytes(); + } + + /** + * L3-HASH: Third-Layer Hash + * + * The output from L2-HASH is 16 bytes long. This final hash function + * hashes the 16-byte string to a fixed length of 4 bytes. + * + * @param string $k1 string of length 64 bytes. + * @param string $k2 string of length 4 bytes. + * @param string $m string of length 16 bytes. + * @return string string of length 4 bytes. + */ + private static function L3Hash($k1, $k2, $m) + { + $factory = self::$factory36; + + $y = $factory->newInteger(new BigInteger()); + for ($i = 0; $i < 8; $i++) { + $m_i = $factory->newInteger(new BigInteger(substr($m, 2 * $i, 2), 256)); + $k_i = $factory->newInteger(new BigInteger(substr($k1, 8 * $i, 8), 256)); + $y = $y->add($m_i->multiply($k_i)); + } + $y = str_pad(substr($y->toBytes(), -4), 4, "\0", STR_PAD_LEFT); + $y = $y ^ $k2; + + return $y; + } + + /** + * Compute the Hash / HMAC / UMAC. * - * @access public * @param string $text * @return string */ - function hash($text) + public function hash($text) { - if (!empty($this->key) || is_string($this->key)) { - switch ($this->engine) { - case self::MODE_MHASH: - $output = mhash($this->hash, $text, $this->computedKey); - break; - case self::MODE_HASH: - $output = hash_hmac($this->hash, $text, $this->computedKey, true); - break; - case self::MODE_INTERNAL: - $key = str_pad($this->computedKey, $this->b, chr(0)); // step 1 - $temp = $this->ipad ^ $key; // step 2 - $temp .= $text; // step 3 - $temp = call_user_func($this->hash, $temp); // step 4 - $output = $this->opad ^ $key; // step 5 - $output.= $temp; // step 6 - $output = call_user_func($this->hash, $output); // step 7 + $algo = $this->algo; + if ($algo == 'umac') { + if ($this->recomputeAESKey) { + if (!is_string($this->nonce)) { + throw new InsufficientSetupException('No nonce has been set'); + } + if (!is_string($this->key)) { + throw new InsufficientSetupException('No key has been set'); + } + if (strlen($this->key) != 16) { + throw new \LengthException('Key must be 16 bytes long'); + } + + if (!isset(self::$maxwordrange64)) { + $one = new BigInteger(1); + + $prime36 = new BigInteger("\x00\x00\x00\x0F\xFF\xFF\xFF\xFB", 256); + self::$factory36 = new PrimeField($prime36); + + $prime64 = new BigInteger("\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xC5", 256); + self::$factory64 = new PrimeField($prime64); + + $prime128 = new BigInteger("\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x61", 256); + self::$factory128 = new PrimeField($prime128); + + self::$offset64 = new BigInteger("\1\0\0\0\0\0\0\0\0", 256); + self::$offset64 = self::$factory64->newInteger(self::$offset64->subtract($prime64)); + self::$offset128 = new BigInteger("\1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", 256); + self::$offset128 = self::$factory128->newInteger(self::$offset128->subtract($prime128)); + + self::$marker64 = self::$factory64->newInteger($prime64->subtract($one)); + self::$marker128 = self::$factory128->newInteger($prime128->subtract($one)); + + $maxwordrange64 = $one->bitwise_leftShift(64)->subtract($one->bitwise_leftShift(32)); + self::$maxwordrange64 = self::$factory64->newInteger($maxwordrange64); + + $maxwordrange128 = $one->bitwise_leftShift(128)->subtract($one->bitwise_leftShift(96)); + self::$maxwordrange128 = self::$factory128->newInteger($maxwordrange128); + } + + $this->c = new AES('ctr'); + $this->c->disablePadding(); + $this->c->setKey($this->key); + + $this->pad = $this->pdf(); + + $this->recomputeAESKey = false; } - } else { - switch ($this->engine) { - case self::MODE_MHASH: - $output = mhash($this->hash, $text); - break; - case self::MODE_HASH: - $output = hash($this->hash, $text, true); - break; - case self::MODE_INTERNAL: - $output = call_user_func($this->hash, $text); + + $hashedmessage = $this->uhash($text, $this->length); + return $hashedmessage ^ $this->pad; + } + + if (is_array($algo)) { + if (empty($this->key) || !is_string($this->key)) { + return substr($algo($text, ...array_values($this->parameters)), 0, $this->length); } + + // SHA3 HMACs are discussed at https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.202.pdf#page=30 + + $key = str_pad($this->computedKey, $b, chr(0)); + $temp = $this->ipad ^ $key; + $temp .= $text; + $temp = substr($algo($temp, ...array_values($this->parameters)), 0, $this->length); + $output = $this->opad ^ $key; + $output .= $temp; + $output = $algo($output, ...array_values($this->parameters)); + + return substr($output, 0, $this->length); } - return substr($output, 0, $this->l); + $output = !empty($this->key) || is_string($this->key) ? + hash_hmac($algo, $text, $this->computedKey, true) : + hash($algo, $text, true); + + return strlen($output) > $this->length + ? substr($output, 0, $this->length) + : $output; } /** - * Returns the hash length (in bytes) + * Returns the hash length (in bits) * - * @access public * @return int */ - function getLength() + public function getLength() { - return $this->l; + return $this->length << 3; } /** - * Wrapper for MD5 + * Returns the hash length (in bytes) * - * @access private - * @param string $m + * @return int */ - function _md5($m) + public function getLengthInBytes() { - return pack('H*', md5($m)); + return $this->length; } /** - * Wrapper for SHA1 + * Returns the block length (in bits) * - * @access private - * @param string $m + * @return int */ - function _sha1($m) + public function getBlockLength() { - return pack('H*', sha1($m)); + return $this->blockSize; } /** - * Pure-PHP implementation of MD2 + * Returns the block length (in bytes) * - * See {@link http://tools.ietf.org/html/rfc1319 RFC1319}. + * @return int + */ + public function getBlockLengthInBytes() + { + return $this->blockSize >> 3; + } + + /** + * Pads SHA3 based on the mode * - * @access private - * @param string $m + * @param int $padLength + * @param int $padType + * @return string */ - function _md2($m) + private static function sha3_pad($padLength, $padType) { - static $s = array( - 41, 46, 67, 201, 162, 216, 124, 1, 61, 54, 84, 161, 236, 240, 6, - 19, 98, 167, 5, 243, 192, 199, 115, 140, 152, 147, 43, 217, 188, - 76, 130, 202, 30, 155, 87, 60, 253, 212, 224, 22, 103, 66, 111, 24, - 138, 23, 229, 18, 190, 78, 196, 214, 218, 158, 222, 73, 160, 251, - 245, 142, 187, 47, 238, 122, 169, 104, 121, 145, 21, 178, 7, 63, - 148, 194, 16, 137, 11, 34, 95, 33, 128, 127, 93, 154, 90, 144, 50, - 39, 53, 62, 204, 231, 191, 247, 151, 3, 255, 25, 48, 179, 72, 165, - 181, 209, 215, 94, 146, 42, 172, 86, 170, 198, 79, 184, 56, 210, - 150, 164, 125, 182, 118, 252, 107, 226, 156, 116, 4, 241, 69, 157, - 112, 89, 100, 113, 135, 32, 134, 91, 207, 101, 230, 45, 168, 2, 27, - 96, 37, 173, 174, 176, 185, 246, 28, 70, 97, 105, 52, 64, 126, 15, - 85, 71, 163, 35, 221, 81, 175, 58, 195, 92, 249, 206, 186, 197, - 234, 38, 44, 83, 13, 110, 133, 40, 132, 9, 211, 223, 205, 244, 65, - 129, 77, 82, 106, 220, 55, 200, 108, 193, 171, 250, 36, 225, 123, - 8, 12, 189, 177, 74, 120, 136, 149, 139, 227, 99, 232, 109, 233, - 203, 213, 254, 59, 0, 29, 57, 242, 239, 183, 14, 102, 88, 208, 228, - 166, 119, 114, 248, 235, 117, 75, 10, 49, 68, 80, 180, 143, 237, - 31, 26, 219, 153, 141, 51, 159, 17, 131, 20 - ); - - // Step 1. Append Padding Bytes - $pad = 16 - (strlen($m) & 0xF); - $m.= str_repeat(chr($pad), $pad); + switch ($padType) { + case self::PADDING_KECCAK: + $temp = chr(0x01) . str_repeat("\0", $padLength - 1); + $temp[$padLength - 1] = $temp[$padLength - 1] | chr(0x80); + return $temp; + case self::PADDING_SHAKE: + $temp = chr(0x1F) . str_repeat("\0", $padLength - 1); + $temp[$padLength - 1] = $temp[$padLength - 1] | chr(0x80); + return $temp; + //case self::PADDING_SHA3: + default: + // from https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.202.pdf#page=36 + return $padLength == 1 ? chr(0x86) : chr(0x06) . str_repeat("\0", $padLength - 2) . chr(0x80); + } + } - $length = strlen($m); + /** + * Pure-PHP 32-bit implementation of SHA3 + * + * Whereas BigInteger.php's 32-bit engine works on PHP 64-bit this 32-bit implementation + * of SHA3 will *not* work on PHP 64-bit. This is because this implementation + * employees bitwise NOTs and bitwise left shifts. And the round constants only work + * on 32-bit PHP. eg. dechex(-2147483648) returns 80000000 on 32-bit PHP and + * FFFFFFFF80000000 on 64-bit PHP. Sure, we could do bitwise ANDs but that would slow + * things down. + * + * SHA512 requires BigInteger to simulate 64-bit unsigned integers because SHA2 employees + * addition whereas SHA3 just employees bitwise operators. PHP64 only supports signed + * 64-bit integers, which complicates addition, whereas that limitation isn't an issue + * for SHA3. + * + * In https://ws680.nist.gov/publication/get_pdf.cfm?pub_id=919061#page=16 KECCAK[C] is + * defined as "the KECCAK instance with KECCAK-f[1600] as the underlying permutation and + * capacity c". This is relevant because, altho the KECCAK standard defines a mode + * (KECCAK-f[800]) designed for 32-bit machines that mode is incompatible with SHA3 + * + * @param string $p + * @param int $c + * @param int $r + * @param int $d + * @param int $padType + */ + private static function sha3_32($p, $c, $r, $d, $padType) + { + $block_size = $r >> 3; + $padLength = $block_size - (strlen($p) % $block_size); + $num_ints = $block_size >> 2; + + $p .= static::sha3_pad($padLength, $padType); + + $n = strlen($p) / $r; // number of blocks + + $s = [ + [[0, 0], [0, 0], [0, 0], [0, 0], [0, 0]], + [[0, 0], [0, 0], [0, 0], [0, 0], [0, 0]], + [[0, 0], [0, 0], [0, 0], [0, 0], [0, 0]], + [[0, 0], [0, 0], [0, 0], [0, 0], [0, 0]], + [[0, 0], [0, 0], [0, 0], [0, 0], [0, 0]] + ]; + + $p = str_split($p, $block_size); + + foreach ($p as $pi) { + $pi = unpack('V*', $pi); + $x = $y = 0; + for ($i = 1; $i <= $num_ints; $i += 2) { + $s[$x][$y][0] ^= $pi[$i + 1]; + $s[$x][$y][1] ^= $pi[$i]; + if (++$y == 5) { + $y = 0; + $x++; + } + } + static::processSHA3Block32($s); + } - // Step 2. Append Checksum - $c = str_repeat(chr(0), 16); - $l = chr(0); - for ($i = 0; $i < $length; $i+= 16) { - for ($j = 0; $j < 16; $j++) { - // RFC1319 incorrectly states that C[j] should be set to S[c xor L] - //$c[$j] = chr($s[ord($m[$i + $j] ^ $l)]); - // per , however, C[j] should be set to S[c xor L] xor C[j] - $c[$j] = chr($s[ord($m[$i + $j] ^ $l)] ^ ord($c[$j])); - $l = $c[$j]; + $z = ''; + $i = $j = 0; + while (strlen($z) < $d) { + $z .= pack('V2', $s[$i][$j][1], $s[$i][$j++][0]); + if ($j == 5) { + $j = 0; + $i++; + if ($i == 5) { + $i = 0; + static::processSHA3Block32($s); + } } } - $m.= $c; - $length+= 16; + return $z; + } - // Step 3. Initialize MD Buffer - $x = str_repeat(chr(0), 48); + /** + * 32-bit block processing method for SHA3 + * + * @param array $s + */ + private static function processSHA3Block32(&$s) + { + static $rotationOffsets = [ + [ 0, 1, 62, 28, 27], + [36, 44, 6, 55, 20], + [ 3, 10, 43, 25, 39], + [41, 45, 15, 21, 8], + [18, 2, 61, 56, 14] + ]; + + // the standards give these constants in hexadecimal notation. it's tempting to want to use + // that same notation, here, however, we can't, because 0x80000000, on PHP32, is a positive + // float - not the negative int that we need to be in PHP32. so we use -2147483648 instead + static $roundConstants = [ + [0, 1], + [0, 32898], + [-2147483648, 32906], + [-2147483648, -2147450880], + [0, 32907], + [0, -2147483647], + [-2147483648, -2147450751], + [-2147483648, 32777], + [0, 138], + [0, 136], + [0, -2147450871], + [0, -2147483638], + [0, -2147450741], + [-2147483648, 139], + [-2147483648, 32905], + [-2147483648, 32771], + [-2147483648, 32770], + [-2147483648, 128], + [0, 32778], + [-2147483648, -2147483638], + [-2147483648, -2147450751], + [-2147483648, 32896], + [0, -2147483647], + [-2147483648, -2147450872] + ]; + + for ($round = 0; $round < 24; $round++) { + // theta step + $parity = $rotated = []; + for ($i = 0; $i < 5; $i++) { + $parity[] = [ + $s[0][$i][0] ^ $s[1][$i][0] ^ $s[2][$i][0] ^ $s[3][$i][0] ^ $s[4][$i][0], + $s[0][$i][1] ^ $s[1][$i][1] ^ $s[2][$i][1] ^ $s[3][$i][1] ^ $s[4][$i][1] + ]; + $rotated[] = static::rotateLeft32($parity[$i], 1); + } - // Step 4. Process Message in 16-Byte Blocks - for ($i = 0; $i < $length; $i+= 16) { - for ($j = 0; $j < 16; $j++) { - $x[$j + 16] = $m[$i + $j]; - $x[$j + 32] = $x[$j + 16] ^ $x[$j]; + $temp = [ + [$parity[4][0] ^ $rotated[1][0], $parity[4][1] ^ $rotated[1][1]], + [$parity[0][0] ^ $rotated[2][0], $parity[0][1] ^ $rotated[2][1]], + [$parity[1][0] ^ $rotated[3][0], $parity[1][1] ^ $rotated[3][1]], + [$parity[2][0] ^ $rotated[4][0], $parity[2][1] ^ $rotated[4][1]], + [$parity[3][0] ^ $rotated[0][0], $parity[3][1] ^ $rotated[0][1]] + ]; + for ($i = 0; $i < 5; $i++) { + for ($j = 0; $j < 5; $j++) { + $s[$i][$j][0] ^= $temp[$j][0]; + $s[$i][$j][1] ^= $temp[$j][1]; + } } - $t = chr(0); - for ($j = 0; $j < 18; $j++) { - for ($k = 0; $k < 48; $k++) { - $x[$k] = $t = $x[$k] ^ chr($s[ord($t)]); - //$t = $x[$k] = $x[$k] ^ chr($s[ord($t)]); + + $st = $s; + + // rho and pi steps + for ($i = 0; $i < 5; $i++) { + for ($j = 0; $j < 5; $j++) { + $st[(2 * $i + 3 * $j) % 5][$j] = static::rotateLeft32($s[$j][$i], $rotationOffsets[$j][$i]); } - $t = chr(ord($t) + $j); } - } - // Step 5. Output - return substr($x, 0, 16); + // chi step + for ($i = 0; $i < 5; $i++) { + $s[$i][0] = [ + $st[$i][0][0] ^ (~$st[$i][1][0] & $st[$i][2][0]), + $st[$i][0][1] ^ (~$st[$i][1][1] & $st[$i][2][1]) + ]; + $s[$i][1] = [ + $st[$i][1][0] ^ (~$st[$i][2][0] & $st[$i][3][0]), + $st[$i][1][1] ^ (~$st[$i][2][1] & $st[$i][3][1]) + ]; + $s[$i][2] = [ + $st[$i][2][0] ^ (~$st[$i][3][0] & $st[$i][4][0]), + $st[$i][2][1] ^ (~$st[$i][3][1] & $st[$i][4][1]) + ]; + $s[$i][3] = [ + $st[$i][3][0] ^ (~$st[$i][4][0] & $st[$i][0][0]), + $st[$i][3][1] ^ (~$st[$i][4][1] & $st[$i][0][1]) + ]; + $s[$i][4] = [ + $st[$i][4][0] ^ (~$st[$i][0][0] & $st[$i][1][0]), + $st[$i][4][1] ^ (~$st[$i][0][1] & $st[$i][1][1]) + ]; + } + + // iota step + $s[0][0][0] ^= $roundConstants[$round][0]; + $s[0][0][1] ^= $roundConstants[$round][1]; + } } /** - * Pure-PHP implementation of SHA256 + * Rotate 32-bit int * - * See {@link http://en.wikipedia.org/wiki/SHA_hash_functions#SHA-256_.28a_SHA-2_variant.29_pseudocode SHA-256 (a SHA-2 variant) pseudocode - Wikipedia}. - * - * @access private - * @param string $m + * @param array $x + * @param int $shift */ - function _sha256($m) + private static function rotateLeft32($x, $shift) { - if (extension_loaded('suhosin')) { - return pack('H*', sha256($m)); + if ($shift < 32) { + list($hi, $lo) = $x; + } else { + $shift -= 32; + list($lo, $hi) = $x; } - // Initialize variables - $hash = array( - 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 - ); - // Initialize table of round constants - // (first 32 bits of the fractional parts of the cube roots of the first 64 primes 2..311) - static $k = array( - 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, - 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, - 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, - 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, - 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, - 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, - 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, - 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 - ); + return [ + ($hi << $shift) | (($lo >> (32 - $shift)) & (1 << $shift) - 1), + ($lo << $shift) | (($hi >> (32 - $shift)) & (1 << $shift) - 1) + ]; + } - // Pre-processing - $length = strlen($m); - // to round to nearest 56 mod 64, we'll add 64 - (length + (64 - 56)) % 64 - $m.= str_repeat(chr(0), 64 - (($length + 8) & 0x3F)); - $m[$length] = chr(0x80); - // we don't support hashing strings 512MB long - $m.= pack('N2', 0, $length << 3); + /** + * Pure-PHP 64-bit implementation of SHA3 + * + * @param string $p + * @param int $c + * @param int $r + * @param int $d + * @param int $padType + */ + private static function sha3_64($p, $c, $r, $d, $padType) + { + $block_size = $r >> 3; + $padLength = $block_size - (strlen($p) % $block_size); + $num_ints = $block_size >> 2; + + $p .= static::sha3_pad($padLength, $padType); + + $n = strlen($p) / $r; // number of blocks + + $s = [ + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0] + ]; + + $p = str_split($p, $block_size); + + foreach ($p as $pi) { + $pi = unpack('P*', $pi); + $x = $y = 0; + foreach ($pi as $subpi) { + $s[$x][$y++] ^= $subpi; + if ($y == 5) { + $y = 0; + $x++; + } + } + static::processSHA3Block64($s); + } - // Process the message in successive 512-bit chunks - $chunks = str_split($m, 64); - foreach ($chunks as $chunk) { - $w = array(); - for ($i = 0; $i < 16; $i++) { - extract(unpack('Ntemp', $this->_string_shift($chunk, 4))); - $w[] = $temp; + $z = ''; + $i = $j = 0; + while (strlen($z) < $d) { + $z .= pack('P', $s[$i][$j++]); + if ($j == 5) { + $j = 0; + $i++; + if ($i == 5) { + $i = 0; + static::processSHA3Block64($s); + } } + } + + return $z; + } - // Extend the sixteen 32-bit words into sixty-four 32-bit words - for ($i = 16; $i < 64; $i++) { - // @codingStandardsIgnoreStart - $s0 = $this->_rightRotate($w[$i - 15], 7) ^ - $this->_rightRotate($w[$i - 15], 18) ^ - $this->_rightShift( $w[$i - 15], 3); - $s1 = $this->_rightRotate($w[$i - 2], 17) ^ - $this->_rightRotate($w[$i - 2], 19) ^ - $this->_rightShift( $w[$i - 2], 10); - // @codingStandardsIgnoreEnd - $w[$i] = $this->_add($w[$i - 16], $s0, $w[$i - 7], $s1); + /** + * 64-bit block processing method for SHA3 + * + * @param array $s + */ + private static function processSHA3Block64(&$s) + { + static $rotationOffsets = [ + [ 0, 1, 62, 28, 27], + [36, 44, 6, 55, 20], + [ 3, 10, 43, 25, 39], + [41, 45, 15, 21, 8], + [18, 2, 61, 56, 14] + ]; + + static $roundConstants = [ + 1, + 32898, + -9223372036854742902, + -9223372034707259392, + 32907, + 2147483649, + -9223372034707259263, + -9223372036854743031, + 138, + 136, + 2147516425, + 2147483658, + 2147516555, + -9223372036854775669, + -9223372036854742903, + -9223372036854743037, + -9223372036854743038, + -9223372036854775680, + 32778, + -9223372034707292150, + -9223372034707259263, + -9223372036854742912, + 2147483649, + -9223372034707259384 + ]; + + for ($round = 0; $round < 24; $round++) { + // theta step + $parity = []; + for ($i = 0; $i < 5; $i++) { + $parity[] = $s[0][$i] ^ $s[1][$i] ^ $s[2][$i] ^ $s[3][$i] ^ $s[4][$i]; + } + $temp = [ + $parity[4] ^ static::rotateLeft64($parity[1], 1), + $parity[0] ^ static::rotateLeft64($parity[2], 1), + $parity[1] ^ static::rotateLeft64($parity[3], 1), + $parity[2] ^ static::rotateLeft64($parity[4], 1), + $parity[3] ^ static::rotateLeft64($parity[0], 1) + ]; + for ($i = 0; $i < 5; $i++) { + for ($j = 0; $j < 5; $j++) { + $s[$i][$j] ^= $temp[$j]; + } } - // Initialize hash value for this chunk - list($a, $b, $c, $d, $e, $f, $g, $h) = $hash; + $st = $s; - // Main loop - for ($i = 0; $i < 64; $i++) { - $s0 = $this->_rightRotate($a, 2) ^ - $this->_rightRotate($a, 13) ^ - $this->_rightRotate($a, 22); - $maj = ($a & $b) ^ - ($a & $c) ^ - ($b & $c); - $t2 = $this->_add($s0, $maj); - - $s1 = $this->_rightRotate($e, 6) ^ - $this->_rightRotate($e, 11) ^ - $this->_rightRotate($e, 25); - $ch = ($e & $f) ^ - ($this->_not($e) & $g); - $t1 = $this->_add($h, $s1, $ch, $k[$i], $w[$i]); - - $h = $g; - $g = $f; - $f = $e; - $e = $this->_add($d, $t1); - $d = $c; - $c = $b; - $b = $a; - $a = $this->_add($t1, $t2); + // rho and pi steps + for ($i = 0; $i < 5; $i++) { + for ($j = 0; $j < 5; $j++) { + $st[(2 * $i + 3 * $j) % 5][$j] = static::rotateLeft64($s[$j][$i], $rotationOffsets[$j][$i]); + } } - // Add this chunk's hash to result so far - $hash = array( - $this->_add($hash[0], $a), - $this->_add($hash[1], $b), - $this->_add($hash[2], $c), - $this->_add($hash[3], $d), - $this->_add($hash[4], $e), - $this->_add($hash[5], $f), - $this->_add($hash[6], $g), - $this->_add($hash[7], $h) - ); + // chi step + for ($i = 0; $i < 5; $i++) { + $s[$i] = [ + $st[$i][0] ^ (~$st[$i][1] & $st[$i][2]), + $st[$i][1] ^ (~$st[$i][2] & $st[$i][3]), + $st[$i][2] ^ (~$st[$i][3] & $st[$i][4]), + $st[$i][3] ^ (~$st[$i][4] & $st[$i][0]), + $st[$i][4] ^ (~$st[$i][0] & $st[$i][1]) + ]; + } + + // iota step + $s[0][0] ^= $roundConstants[$round]; } + } - // Produce the final hash value (big-endian) - return pack('N8', $hash[0], $hash[1], $hash[2], $hash[3], $hash[4], $hash[5], $hash[6], $hash[7]); + /** + * Rotate 64-bit int + * + * @param int $x + * @param int $shift + */ + private static function rotateLeft64($x, $shift) + { + return ($x << $shift) | (($x >> (64 - $shift)) & ((1 << $shift) - 1)); } /** - * Pure-PHP implementation of SHA384 and SHA512 + * Pure-PHP implementation of SHA512 * - * @access private * @param string $m + * @param array $hash + * @return string */ - function _sha512($m) + private static function sha512($m, $hash) { - static $init384, $init512, $k; + static $k; if (!isset($k)) { - // Initialize variables - $init384 = array( // initial values for SHA384 - 'cbbb9d5dc1059ed8', '629a292a367cd507', '9159015a3070dd17', '152fecd8f70e5939', - '67332667ffc00b31', '8eb44a8768581511', 'db0c2e0d64f98fa7', '47b5481dbefa4fa4' - ); - $init512 = array( // initial values for SHA512 - '6a09e667f3bcc908', 'bb67ae8584caa73b', '3c6ef372fe94f82b', 'a54ff53a5f1d36f1', - '510e527fade682d1', '9b05688c2b3e6c1f', '1f83d9abfb41bd6b', '5be0cd19137e2179' - ); - - for ($i = 0; $i < 8; $i++) { - $init384[$i] = new BigInteger($init384[$i], 16); - $init384[$i]->setPrecision(64); - $init512[$i] = new BigInteger($init512[$i], 16); - $init512[$i]->setPrecision(64); - } - // Initialize table of round constants // (first 64 bits of the fractional parts of the cube roots of the first 80 primes 2..409) - $k = array( + $k = [ '428a2f98d728ae22', '7137449123ef65cd', 'b5c0fbcfec4d3b2f', 'e9b5dba58189dbbc', '3956c25bf348b538', '59f111f1b605d019', '923f82a4af194f9b', 'ab1c5ed5da6d8118', 'd807aa98a3030242', '12835b0145706fbe', '243185be4ee4b28c', '550c7dc3d5ffb4e2', @@ -671,112 +1322,110 @@ function _sha512($m) '06f067aa72176fba', '0a637dc5a2c898a6', '113f9804bef90dae', '1b710b35131c471b', '28db77f523047d84', '32caab7b40c72493', '3c9ebe0a15c9bebc', '431d67c49c100d4c', '4cc5d4becb3e42b6', '597f299cfc657e2a', '5fcb6fab3ad6faec', '6c44198c4a475817' - ); + ]; for ($i = 0; $i < 80; $i++) { $k[$i] = new BigInteger($k[$i], 16); } } - $hash = $this->l == 48 ? $init384 : $init512; - // Pre-processing $length = strlen($m); // to round to nearest 112 mod 128, we'll add 128 - (length + (128 - 112)) % 128 - $m.= str_repeat(chr(0), 128 - (($length + 16) & 0x7F)); + $m .= str_repeat(chr(0), 128 - (($length + 16) & 0x7F)); $m[$length] = chr(0x80); // we don't support hashing strings 512MB long - $m.= pack('N4', 0, 0, 0, $length << 3); + $m .= pack('N4', 0, 0, 0, $length << 3); // Process the message in successive 1024-bit chunks $chunks = str_split($m, 128); foreach ($chunks as $chunk) { - $w = array(); + $w = []; for ($i = 0; $i < 16; $i++) { - $temp = new BigInteger($this->_string_shift($chunk, 8), 256); + $temp = new BigInteger(Strings::shift($chunk, 8), 256); $temp->setPrecision(64); $w[] = $temp; } // Extend the sixteen 32-bit words into eighty 32-bit words for ($i = 16; $i < 80; $i++) { - $temp = array( + $temp = [ $w[$i - 15]->bitwise_rightRotate(1), $w[$i - 15]->bitwise_rightRotate(8), $w[$i - 15]->bitwise_rightShift(7) - ); + ]; $s0 = $temp[0]->bitwise_xor($temp[1]); $s0 = $s0->bitwise_xor($temp[2]); - $temp = array( + $temp = [ $w[$i - 2]->bitwise_rightRotate(19), $w[$i - 2]->bitwise_rightRotate(61), $w[$i - 2]->bitwise_rightShift(6) - ); + ]; $s1 = $temp[0]->bitwise_xor($temp[1]); $s1 = $s1->bitwise_xor($temp[2]); - $w[$i] = $w[$i - 16]->copy(); + $w[$i] = clone $w[$i - 16]; $w[$i] = $w[$i]->add($s0); $w[$i] = $w[$i]->add($w[$i - 7]); $w[$i] = $w[$i]->add($s1); } // Initialize hash value for this chunk - $a = $hash[0]->copy(); - $b = $hash[1]->copy(); - $c = $hash[2]->copy(); - $d = $hash[3]->copy(); - $e = $hash[4]->copy(); - $f = $hash[5]->copy(); - $g = $hash[6]->copy(); - $h = $hash[7]->copy(); + $a = clone $hash[0]; + $b = clone $hash[1]; + $c = clone $hash[2]; + $d = clone $hash[3]; + $e = clone $hash[4]; + $f = clone $hash[5]; + $g = clone $hash[6]; + $h = clone $hash[7]; // Main loop for ($i = 0; $i < 80; $i++) { - $temp = array( + $temp = [ $a->bitwise_rightRotate(28), $a->bitwise_rightRotate(34), $a->bitwise_rightRotate(39) - ); + ]; $s0 = $temp[0]->bitwise_xor($temp[1]); $s0 = $s0->bitwise_xor($temp[2]); - $temp = array( + $temp = [ $a->bitwise_and($b), $a->bitwise_and($c), $b->bitwise_and($c) - ); + ]; $maj = $temp[0]->bitwise_xor($temp[1]); $maj = $maj->bitwise_xor($temp[2]); $t2 = $s0->add($maj); - $temp = array( + $temp = [ $e->bitwise_rightRotate(14), $e->bitwise_rightRotate(18), $e->bitwise_rightRotate(41) - ); + ]; $s1 = $temp[0]->bitwise_xor($temp[1]); $s1 = $s1->bitwise_xor($temp[2]); - $temp = array( + $temp = [ $e->bitwise_and($f), $g->bitwise_and($e->bitwise_not()) - ); + ]; $ch = $temp[0]->bitwise_xor($temp[1]); $t1 = $h->add($s1); $t1 = $t1->add($ch); $t1 = $t1->add($k[$i]); $t1 = $t1->add($w[$i]); - $h = $g->copy(); - $g = $f->copy(); - $f = $e->copy(); + $h = clone $g; + $g = clone $f; + $f = clone $e; $e = $d->add($t1); - $d = $c->copy(); - $c = $b->copy(); - $b = $a->copy(); + $d = clone $c; + $c = clone $b; + $b = clone $a; $a = $t1->add($t2); } // Add this chunk's hash to result so far - $hash = array( + $hash = [ $hash[0]->add($a), $hash[1]->add($b), $hash[2]->add($c), @@ -785,109 +1434,22 @@ function _sha512($m) $hash[5]->add($f), $hash[6]->add($g), $hash[7]->add($h) - ); + ]; } // Produce the final hash value (big-endian) - // (\phpseclib\Crypt\Hash::hash() trims the output for hashes but not for HMACs. as such, we trim the output here) + // (\phpseclib3\Crypt\Hash::hash() trims the output for hashes but not for HMACs. as such, we trim the output here) $temp = $hash[0]->toBytes() . $hash[1]->toBytes() . $hash[2]->toBytes() . $hash[3]->toBytes() . - $hash[4]->toBytes() . $hash[5]->toBytes(); - if ($this->l != 48) { - $temp.= $hash[6]->toBytes() . $hash[7]->toBytes(); - } + $hash[4]->toBytes() . $hash[5]->toBytes() . $hash[6]->toBytes() . $hash[7]->toBytes(); return $temp; } /** - * Right Rotate - * - * @access private - * @param int $int - * @param int $amt - * @see self::_sha256() - * @return int - */ - function _rightRotate($int, $amt) - { - $invamt = 32 - $amt; - $mask = (1 << $invamt) - 1; - return (($int << $invamt) & 0xFFFFFFFF) | (($int >> $amt) & $mask); - } - - /** - * Right Shift - * - * @access private - * @param int $int - * @param int $amt - * @see self::_sha256() - * @return int - */ - function _rightShift($int, $amt) - { - $mask = (1 << (32 - $amt)) - 1; - return ($int >> $amt) & $mask; - } - - /** - * Not - * - * @access private - * @param int $int - * @see self::_sha256() - * @return int - */ - function _not($int) - { - return ~$int & 0xFFFFFFFF; - } - - /** - * Add - * - * _sha256() adds multiple unsigned 32-bit integers. Since PHP doesn't support unsigned integers and since the - * possibility of overflow exists, care has to be taken. BigInteger could be used but this should be faster. - * - * @return int - * @see self::_sha256() - * @access private - */ - function _add() - { - static $mod; - if (!isset($mod)) { - $mod = pow(2, 32); - } - - $result = 0; - $arguments = func_get_args(); - foreach ($arguments as $argument) { - $result+= $argument < 0 ? ($argument & 0x7FFFFFFF) + 0x80000000 : $argument; - } - - if ((php_uname('m') & "\xDF\xDF\xDF") != 'ARM') { - return fmod($result, $mod); - } - - return (fmod($result, 0x80000000) & 0x7FFFFFFF) | - ((fmod(floor($result / 0x80000000), 2) & 1) << 31); - } - - /** - * String Shift - * - * Inspired by array_shift - * - * @param string $string - * @param int $index - * @return string - * @access private + * __toString() magic method */ - function _string_shift(&$string, $index = 1) + public function __toString() { - $substr = substr($string, 0, $index); - $string = substr($string, $index); - return $substr; + return $this->getHash(); } } diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/PublicKeyLoader.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/PublicKeyLoader.php new file mode 100644 index 00000000..61afbaeb --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/PublicKeyLoader.php @@ -0,0 +1,111 @@ + + * @copyright 2009 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\Crypt; + +use phpseclib3\Crypt\Common\AsymmetricKey; +use phpseclib3\Crypt\Common\PrivateKey; +use phpseclib3\Crypt\Common\PublicKey; +use phpseclib3\Exception\NoKeyLoadedException; +use phpseclib3\File\X509; + +/** + * PublicKeyLoader + * + * @author Jim Wigginton + */ +abstract class PublicKeyLoader +{ + /** + * Loads a public or private key + * + * @return AsymmetricKey + * @param string|array $key + * @param string $password optional + */ + public static function load($key, $password = false) + { + try { + return EC::load($key, $password); + } catch (NoKeyLoadedException $e) { + } + + try { + return RSA::load($key, $password); + } catch (NoKeyLoadedException $e) { + } + + try { + return DSA::load($key, $password); + } catch (NoKeyLoadedException $e) { + } + + try { + $x509 = new X509(); + $x509->loadX509($key); + $key = $x509->getPublicKey(); + if ($key) { + return $key; + } + } catch (\Exception $e) { + } + + throw new NoKeyLoadedException('Unable to read key'); + } + + /** + * Loads a private key + * + * @return PrivateKey + * @param string|array $key + * @param string $password optional + */ + public static function loadPrivateKey($key, $password = false) + { + $key = self::load($key, $password); + if (!$key instanceof PrivateKey) { + throw new NoKeyLoadedException('The key that was loaded was not a private key'); + } + return $key; + } + + /** + * Loads a public key + * + * @return PublicKey + * @param string|array $key + */ + public static function loadPublicKey($key) + { + $key = self::load($key); + if (!$key instanceof PublicKey) { + throw new NoKeyLoadedException('The key that was loaded was not a public key'); + } + return $key; + } + + /** + * Loads parameters + * + * @return AsymmetricKey + * @param string|array $key + */ + public static function loadParameters($key) + { + $key = self::load($key); + if (!$key instanceof PrivateKey && !$key instanceof PublicKey) { + throw new NoKeyLoadedException('The key that was loaded was not a parameter'); + } + return $key; + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/RC2.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/RC2.php index b2b9d48e..d8c7562d 100644 --- a/vendor/phpseclib/phpseclib/phpseclib/Crypt/RC2.php +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/RC2.php @@ -16,7 +16,7 @@ * setKey('abcdefgh'); * @@ -26,121 +26,105 @@ * ?> * * - * @category Crypt - * @package RC2 * @author Patrick Monnerat * @license http://www.opensource.org/licenses/mit-license.html MIT License * @link http://phpseclib.sourceforge.net */ -namespace phpseclib\Crypt; +namespace phpseclib3\Crypt; + +use phpseclib3\Crypt\Common\BlockCipher; +use phpseclib3\Exception\BadModeException; /** * Pure-PHP implementation of RC2. * - * @package RC2 - * @access public */ -class RC2 extends Base +class RC2 extends BlockCipher { /** * Block Length of the cipher * - * @see \phpseclib\Crypt\Base::block_size + * @see \phpseclib3\Crypt\Common\SymmetricKey::block_size * @var int - * @access private */ - var $block_size = 8; + protected $block_size = 8; /** * The Key * - * @see \phpseclib\Crypt\Base::key + * @see \phpseclib3\Crypt\Common\SymmetricKey::key * @see self::setKey() * @var string - * @access private */ - var $key; + protected $key; /** * The Original (unpadded) Key * - * @see \phpseclib\Crypt\Base::key + * @see \phpseclib3\Crypt\Common\SymmetricKey::key * @see self::setKey() * @see self::encrypt() * @see self::decrypt() * @var string - * @access private - */ - var $orig_key; - - /** - * Don't truncate / null pad key - * - * @see \phpseclib\Crypt\Base::_clearBuffers() - * @var bool - * @access private */ - var $skip_key_adjustment = true; + private $orig_key; /** * Key Length (in bytes) * - * @see \phpseclib\Crypt\RC2::setKeyLength() + * @see \phpseclib3\Crypt\RC2::setKeyLength() * @var int - * @access private */ - var $key_length = 16; // = 128 bits + protected $key_length = 16; // = 128 bits /** * The mcrypt specific name of the cipher * - * @see \phpseclib\Crypt\Base::cipher_name_mcrypt + * @see \phpseclib3\Crypt\Common\SymmetricKey::cipher_name_mcrypt * @var string - * @access private */ - var $cipher_name_mcrypt = 'rc2'; + protected $cipher_name_mcrypt = 'rc2'; /** * Optimizing value while CFB-encrypting * - * @see \phpseclib\Crypt\Base::cfb_init_len + * @see \phpseclib3\Crypt\Common\SymmetricKey::cfb_init_len * @var int - * @access private */ - var $cfb_init_len = 500; + protected $cfb_init_len = 500; /** * The key length in bits. * + * {@internal Should be in range [1..1024].} + * + * {@internal Changing this value after setting the key has no effect.} + * * @see self::setKeyLength() * @see self::setKey() * @var int - * @access private - * @internal Should be in range [1..1024]. - * @internal Changing this value after setting the key has no effect. */ - var $default_key_length = 1024; + private $default_key_length = 1024; /** * The key length in bits. * + * {@internal Should be in range [1..1024].} + * * @see self::isValidEnine() * @see self::setKey() * @var int - * @access private - * @internal Should be in range [1..1024]. */ - var $current_key_length; + private $current_key_length; /** * The Key Schedule * - * @see self::_setupKey() + * @see self::setupKey() * @var array - * @access private */ - var $keys; + private $keys; /** * Key expansion randomization table. @@ -148,9 +132,8 @@ class RC2 extends Base * * @see self::setKey() * @var array - * @access private */ - var $pitable = array( + private static $pitable = [ 0xD9, 0x78, 0xF9, 0xC4, 0x19, 0xDD, 0xB5, 0xED, 0x28, 0xE9, 0xFD, 0x79, 0x4A, 0xA0, 0xD8, 0x9D, 0xC6, 0x7E, 0x37, 0x83, 0x2B, 0x76, 0x53, 0x8E, @@ -215,16 +198,15 @@ class RC2 extends Base 0xBB, 0x48, 0x0C, 0x5F, 0xB9, 0xB1, 0xCD, 0x2E, 0xC5, 0xF3, 0xDB, 0x47, 0xE5, 0xA5, 0x9C, 0x77, 0x0A, 0xA6, 0x20, 0x68, 0xFE, 0x7F, 0xC1, 0xAD - ); + ]; /** * Inverse key expansion randomization table. * * @see self::setKey() * @var array - * @access private */ - var $invpitable = array( + private static $invpitable = [ 0xD1, 0xDA, 0xB9, 0x6F, 0x9C, 0xC8, 0x78, 0x66, 0x80, 0x2C, 0xF8, 0x37, 0xEA, 0xE0, 0x62, 0xA4, 0xCB, 0x71, 0x50, 0x27, 0x4B, 0x95, 0xD9, 0x20, @@ -257,19 +239,33 @@ class RC2 extends Base 0x81, 0x09, 0x82, 0x33, 0x9F, 0x07, 0x86, 0x75, 0x38, 0x4E, 0x69, 0xF1, 0xAD, 0x23, 0x73, 0x87, 0x70, 0x02, 0xC2, 0x1E, 0xB8, 0x0A, 0xFC, 0xE6 - ); + ]; + + /** + * Default Constructor. + * + * @param string $mode + * @throws \InvalidArgumentException if an invalid / unsupported mode is provided + */ + public function __construct($mode) + { + parent::__construct($mode); + + if ($this->mode == self::MODE_STREAM) { + throw new BadModeException('Block ciphers cannot be ran in stream mode'); + } + } /** * Test for engine validity * - * This is mainly just a wrapper to set things up for \phpseclib\Crypt\Base::isValidEngine() + * This is mainly just a wrapper to set things up for \phpseclib3\Crypt\Common\SymmetricKey::isValidEngine() * - * @see \phpseclib\Crypt\Base::__construct() + * @see \phpseclib3\Crypt\Common\SymmetricKey::__construct() * @param int $engine - * @access public * @return bool */ - function isValidEngine($engine) + protected function isValidEngineHelper($engine) { switch ($engine) { case self::ENGINE_OPENSSL: @@ -277,10 +273,10 @@ function isValidEngine($engine) return false; } $this->cipher_name_openssl_ecb = 'rc2-ecb'; - $this->cipher_name_openssl = 'rc2-' . $this->_openssl_translate_mode(); + $this->cipher_name_openssl = 'rc2-' . $this->openssl_translate_mode(); } - return parent::isValidEngine($engine); + return parent::isValidEngineHelper($engine); } /** @@ -288,32 +284,27 @@ function isValidEngine($engine) * * Valid key lengths are 8 to 1024. * Calling this function after setting the key has no effect until the next - * \phpseclib\Crypt\RC2::setKey() call. + * \phpseclib3\Crypt\RC2::setKey() call. * - * @access public * @param int $length in bits + * @throws \LengthException if the key length isn't supported */ - function setKeyLength($length) + public function setKeyLength($length) { - if ($length < 8) { - $this->default_key_length = 1; - } elseif ($length > 1024) { - $this->default_key_length = 128; - } else { - $this->default_key_length = $length; + if ($length < 8 || $length > 1024) { + throw new \LengthException('Key size of ' . $length . ' bits is not supported by this algorithm. Only keys between 1 and 1024 bits, inclusive, are supported'); } - $this->current_key_length = $this->default_key_length; - parent::setKeyLength($length); + $this->default_key_length = $this->current_key_length = $length; + $this->explicit_key_length = $length >> 3; } /** * Returns the current key length * - * @access public * @return int */ - function getKeyLength() + public function getKeyLength() { return $this->current_key_length; } @@ -326,26 +317,28 @@ function getKeyLength() * has more then 128 bytes in it, and set $key to a single null byte if * it is empty. * - * If the key is not explicitly set, it'll be assumed to be a single - * null byte. - * - * @see \phpseclib\Crypt\Base::setKey() - * @access public + * @see \phpseclib3\Crypt\Common\SymmetricKey::setKey() * @param string $key - * @param int $t1 optional Effective key length in bits. + * @param int|boolean $t1 optional Effective key length in bits. + * @throws \LengthException if the key length isn't supported */ - function setKey($key, $t1 = 0) + public function setKey($key, $t1 = false) { $this->orig_key = $key; - if ($t1 <= 0) { + if ($t1 === false) { $t1 = $this->default_key_length; - } elseif ($t1 > 1024) { - $t1 = 1024; } + + if ($t1 < 1 || $t1 > 1024) { + throw new \LengthException('Key size of ' . $length . ' bits is not supported by this algorithm. Only keys between 1 and 1024 bits, inclusive, are supported'); + } + $this->current_key_length = $t1; - // Key byte count should be 1..128. - $key = strlen($key) ? substr($key, 0, 128) : "\x00"; + if (strlen($key) < 1 || strlen($key) > 128) { + throw new \LengthException('Key of size ' . strlen($key) . ' not supported by this algorithm. Only keys of sizes between 8 and 1024 bits, inclusive, are supported'); + } + $t = strlen($key); // The mcrypt RC2 implementation only supports effective key length @@ -360,7 +353,7 @@ function setKey($key, $t1 = 0) $tm = 0xFF >> (8 * $t8 - $t1); // Expand key. - $pitable = $this->pitable; + $pitable = self::$pitable; for ($i = $t; $i < 128; $i++) { $l[$i] = $pitable[$l[$i - 1] + $l[$i - $t]]; } @@ -371,23 +364,25 @@ function setKey($key, $t1 = 0) } // Prepare the key for mcrypt. - $l[0] = $this->invpitable[$l[0]]; + $l[0] = self::$invpitable[$l[0]]; array_unshift($l, 'C*'); - parent::setKey(call_user_func_array('pack', $l)); + $this->key = pack(...$l); + $this->key_length = strlen($this->key); + $this->changed = $this->nonIVChanged = true; + $this->setEngine(); } /** * Encrypts a message. * - * Mostly a wrapper for \phpseclib\Crypt\Base::encrypt, with some additional OpenSSL handling code + * Mostly a wrapper for \phpseclib3\Crypt\Common\SymmetricKey::encrypt, with some additional OpenSSL handling code * * @see self::decrypt() - * @access public * @param string $plaintext * @return string $ciphertext */ - function encrypt($plaintext) + public function encrypt($plaintext) { if ($this->engine == self::ENGINE_OPENSSL) { $temp = $this->key; @@ -403,14 +398,13 @@ function encrypt($plaintext) /** * Decrypts a message. * - * Mostly a wrapper for \phpseclib\Crypt\Base::decrypt, with some additional OpenSSL handling code + * Mostly a wrapper for \phpseclib3\Crypt\Common\SymmetricKey::decrypt, with some additional OpenSSL handling code * * @see self::encrypt() - * @access public * @param string $ciphertext * @return string $plaintext */ - function decrypt($ciphertext) + public function decrypt($ciphertext) { if ($this->engine == self::ENGINE_OPENSSL) { $temp = $this->key; @@ -426,18 +420,17 @@ function decrypt($ciphertext) /** * Encrypts a block * - * @see \phpseclib\Crypt\Base::_encryptBlock() - * @see \phpseclib\Crypt\Base::encrypt() - * @access private + * @see \phpseclib3\Crypt\Common\SymmetricKey::encryptBlock() + * @see \phpseclib3\Crypt\Common\SymmetricKey::encrypt() * @param string $in * @return string */ - function _encryptBlock($in) + protected function encryptBlock($in) { list($r0, $r1, $r2, $r3) = array_values(unpack('v*', $in)); $keys = $this->keys; $limit = 20; - $actions = array($limit => 44, 44 => 64); + $actions = [$limit => 44, 44 => 64]; $j = 0; for (;;) { @@ -471,18 +464,17 @@ function _encryptBlock($in) /** * Decrypts a block * - * @see \phpseclib\Crypt\Base::_decryptBlock() - * @see \phpseclib\Crypt\Base::decrypt() - * @access private + * @see \phpseclib3\Crypt\Common\SymmetricKey::decryptBlock() + * @see \phpseclib3\Crypt\Common\SymmetricKey::decrypt() * @param string $in * @return string */ - function _decryptBlock($in) + protected function decryptBlock($in) { list($r0, $r1, $r2, $r3) = array_values(unpack('v*', $in)); $keys = $this->keys; $limit = 44; - $actions = array($limit => 20, 20 => 0); + $actions = [$limit => 20, 20 => 0]; $j = 64; for (;;) { @@ -513,37 +505,21 @@ function _decryptBlock($in) return pack('vvvv', $r0, $r1, $r2, $r3); } - /** - * Setup the \phpseclib\Crypt\Base::ENGINE_MCRYPT $engine - * - * @see \phpseclib\Crypt\Base::_setupMcrypt() - * @access private - */ - function _setupMcrypt() - { - if (!isset($this->key)) { - $this->setKey(''); - } - - parent::_setupMcrypt(); - } - /** * Creates the key schedule * - * @see \phpseclib\Crypt\Base::_setupKey() - * @access private + * @see \phpseclib3\Crypt\Common\SymmetricKey::setupKey() */ - function _setupKey() + protected function setupKey() { if (!isset($this->key)) { $this->setKey(''); } - // Key has already been expanded in \phpseclib\Crypt\RC2::setKey(): + // Key has already been expanded in \phpseclib3\Crypt\RC2::setKey(): // Only the first value must be altered. $l = unpack('Ca/Cb/v*', $this->key); - array_unshift($l, $this->pitable[$l['a']] | ($l['b'] << 8)); + array_unshift($l, self::$pitable[$l['a']] | ($l['b'] << 8)); unset($l['a']); unset($l['b']); $this->keys = $l; @@ -552,137 +528,107 @@ function _setupKey() /** * Setup the performance-optimized function for de/encrypt() * - * @see \phpseclib\Crypt\Base::_setupInlineCrypt() - * @access private + * @see \phpseclib3\Crypt\Common\SymmetricKey::setupInlineCrypt() */ - function _setupInlineCrypt() + protected function setupInlineCrypt() { - $lambda_functions =& self::_getLambdaFunctions(); - - // The first 10 generated $lambda_functions will use the $keys hardcoded as integers - // for the mixing rounds, for better inline crypt performance [~20% faster]. - // But for memory reason we have to limit those ultra-optimized $lambda_functions to an amount of 10. - // (Currently, for Crypt_RC2, one generated $lambda_function cost on php5.5@32bit ~60kb unfreeable mem and ~100kb on php5.5@64bit) - $gen_hi_opt_code = (bool)(count($lambda_functions) < 10); - - // Generation of a unique hash for our generated code - $code_hash = "Crypt_RC2, {$this->mode}"; - if ($gen_hi_opt_code) { - $code_hash = str_pad($code_hash, 32) . $this->_hashInlineCryptFunction($this->key); - } + // Init code for both, encrypt and decrypt. + $init_crypt = '$keys = $this->keys;'; - // Is there a re-usable $lambda_functions in there? - // If not, we have to create it. - if (!isset($lambda_functions[$code_hash])) { - // Init code for both, encrypt and decrypt. - $init_crypt = '$keys = $self->keys;'; - - switch (true) { - case $gen_hi_opt_code: - $keys = $this->keys; - default: - $keys = array(); - foreach ($this->keys as $k => $v) { - $keys[$k] = '$keys[' . $k . ']'; - } - } + $keys = $this->keys; - // $in is the current 8 bytes block which has to be en/decrypt - $encrypt_block = $decrypt_block = ' - $in = unpack("v4", $in); - $r0 = $in[1]; - $r1 = $in[2]; - $r2 = $in[3]; - $r3 = $in[4]; - '; - - // Create code for encryption. - $limit = 20; - $actions = array($limit => 44, 44 => 64); - $j = 0; - - for (;;) { - // Mixing round. - $encrypt_block .= ' - $r0 = (($r0 + ' . $keys[$j++] . ' + - ((($r1 ^ $r2) & $r3) ^ $r1)) & 0xFFFF) << 1; - $r0 |= $r0 >> 16; - $r1 = (($r1 + ' . $keys[$j++] . ' + - ((($r2 ^ $r3) & $r0) ^ $r2)) & 0xFFFF) << 2; - $r1 |= $r1 >> 16; - $r2 = (($r2 + ' . $keys[$j++] . ' + - ((($r3 ^ $r0) & $r1) ^ $r3)) & 0xFFFF) << 3; - $r2 |= $r2 >> 16; - $r3 = (($r3 + ' . $keys[$j++] . ' + - ((($r0 ^ $r1) & $r2) ^ $r0)) & 0xFFFF) << 5; - $r3 |= $r3 >> 16;'; - - if ($j === $limit) { - if ($limit === 64) { - break; - } - - // Mashing round. - $encrypt_block .= ' - $r0 += $keys[$r3 & 0x3F]; - $r1 += $keys[$r0 & 0x3F]; - $r2 += $keys[$r1 & 0x3F]; - $r3 += $keys[$r2 & 0x3F];'; - $limit = $actions[$limit]; - } - } + // $in is the current 8 bytes block which has to be en/decrypt + $encrypt_block = $decrypt_block = ' + $in = unpack("v4", $in); + $r0 = $in[1]; + $r1 = $in[2]; + $r2 = $in[3]; + $r3 = $in[4]; + '; - $encrypt_block .= '$in = pack("v4", $r0, $r1, $r2, $r3);'; + // Create code for encryption. + $limit = 20; + $actions = [$limit => 44, 44 => 64]; + $j = 0; - // Create code for decryption. - $limit = 44; - $actions = array($limit => 20, 20 => 0); - $j = 64; + for (;;) { + // Mixing round. + $encrypt_block .= ' + $r0 = (($r0 + ' . $keys[$j++] . ' + + ((($r1 ^ $r2) & $r3) ^ $r1)) & 0xFFFF) << 1; + $r0 |= $r0 >> 16; + $r1 = (($r1 + ' . $keys[$j++] . ' + + ((($r2 ^ $r3) & $r0) ^ $r2)) & 0xFFFF) << 2; + $r1 |= $r1 >> 16; + $r2 = (($r2 + ' . $keys[$j++] . ' + + ((($r3 ^ $r0) & $r1) ^ $r3)) & 0xFFFF) << 3; + $r2 |= $r2 >> 16; + $r3 = (($r3 + ' . $keys[$j++] . ' + + ((($r0 ^ $r1) & $r2) ^ $r0)) & 0xFFFF) << 5; + $r3 |= $r3 >> 16;'; - for (;;) { - // R-mixing round. - $decrypt_block .= ' - $r3 = ($r3 | ($r3 << 16)) >> 5; - $r3 = ($r3 - ' . $keys[--$j] . ' - - ((($r0 ^ $r1) & $r2) ^ $r0)) & 0xFFFF; - $r2 = ($r2 | ($r2 << 16)) >> 3; - $r2 = ($r2 - ' . $keys[--$j] . ' - - ((($r3 ^ $r0) & $r1) ^ $r3)) & 0xFFFF; - $r1 = ($r1 | ($r1 << 16)) >> 2; - $r1 = ($r1 - ' . $keys[--$j] . ' - - ((($r2 ^ $r3) & $r0) ^ $r2)) & 0xFFFF; - $r0 = ($r0 | ($r0 << 16)) >> 1; - $r0 = ($r0 - ' . $keys[--$j] . ' - - ((($r1 ^ $r2) & $r3) ^ $r1)) & 0xFFFF;'; - - if ($j === $limit) { - if ($limit === 0) { - break; - } - - // R-mashing round. - $decrypt_block .= ' - $r3 = ($r3 - $keys[$r2 & 0x3F]) & 0xFFFF; - $r2 = ($r2 - $keys[$r1 & 0x3F]) & 0xFFFF; - $r1 = ($r1 - $keys[$r0 & 0x3F]) & 0xFFFF; - $r0 = ($r0 - $keys[$r3 & 0x3F]) & 0xFFFF;'; - $limit = $actions[$limit]; + if ($j === $limit) { + if ($limit === 64) { + break; } + + // Mashing round. + $encrypt_block .= ' + $r0 += $keys[$r3 & 0x3F]; + $r1 += $keys[$r0 & 0x3F]; + $r2 += $keys[$r1 & 0x3F]; + $r3 += $keys[$r2 & 0x3F];'; + $limit = $actions[$limit]; } + } - $decrypt_block .= '$in = pack("v4", $r0, $r1, $r2, $r3);'; + $encrypt_block .= '$in = pack("v4", $r0, $r1, $r2, $r3);'; - // Creates the inline-crypt function - $lambda_functions[$code_hash] = $this->_createInlineCryptFunction( - array( - 'init_crypt' => $init_crypt, - 'encrypt_block' => $encrypt_block, - 'decrypt_block' => $decrypt_block - ) - ); + // Create code for decryption. + $limit = 44; + $actions = [$limit => 20, 20 => 0]; + $j = 64; + + for (;;) { + // R-mixing round. + $decrypt_block .= ' + $r3 = ($r3 | ($r3 << 16)) >> 5; + $r3 = ($r3 - ' . $keys[--$j] . ' - + ((($r0 ^ $r1) & $r2) ^ $r0)) & 0xFFFF; + $r2 = ($r2 | ($r2 << 16)) >> 3; + $r2 = ($r2 - ' . $keys[--$j] . ' - + ((($r3 ^ $r0) & $r1) ^ $r3)) & 0xFFFF; + $r1 = ($r1 | ($r1 << 16)) >> 2; + $r1 = ($r1 - ' . $keys[--$j] . ' - + ((($r2 ^ $r3) & $r0) ^ $r2)) & 0xFFFF; + $r0 = ($r0 | ($r0 << 16)) >> 1; + $r0 = ($r0 - ' . $keys[--$j] . ' - + ((($r1 ^ $r2) & $r3) ^ $r1)) & 0xFFFF;'; + + if ($j === $limit) { + if ($limit === 0) { + break; + } + + // R-mashing round. + $decrypt_block .= ' + $r3 = ($r3 - $keys[$r2 & 0x3F]) & 0xFFFF; + $r2 = ($r2 - $keys[$r1 & 0x3F]) & 0xFFFF; + $r1 = ($r1 - $keys[$r0 & 0x3F]) & 0xFFFF; + $r0 = ($r0 - $keys[$r3 & 0x3F]) & 0xFFFF;'; + $limit = $actions[$limit]; + } } - // Set the inline-crypt function as callback in: $this->inline_crypt - $this->inline_crypt = $lambda_functions[$code_hash]; + $decrypt_block .= '$in = pack("v4", $r0, $r1, $r2, $r3);'; + + // Creates the inline-crypt function + $this->inline_crypt = $this->createInlineCryptFunction( + [ + 'init_crypt' => $init_crypt, + 'encrypt_block' => $encrypt_block, + 'decrypt_block' => $decrypt_block + ] + ); } } diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/RC4.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/RC4.php index 25e4ff85..f7ee7349 100644 --- a/vendor/phpseclib/phpseclib/phpseclib/Crypt/RC4.php +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/RC4.php @@ -20,7 +20,7 @@ * setKey('abcdefgh'); * @@ -34,117 +34,80 @@ * ?> * * - * @category Crypt - * @package RC4 * @author Jim Wigginton * @copyright 2007 Jim Wigginton * @license http://www.opensource.org/licenses/mit-license.html MIT License * @link http://phpseclib.sourceforge.net */ -namespace phpseclib\Crypt; +namespace phpseclib3\Crypt; + +use phpseclib3\Crypt\Common\StreamCipher; /** * Pure-PHP implementation of RC4. * - * @package RC4 * @author Jim Wigginton - * @access public */ -class RC4 extends Base +class RC4 extends StreamCipher { - /**#@+ - * @access private - * @see \phpseclib\Crypt\RC4::_crypt() - */ + /** + * @see \phpseclib3\Crypt\RC4::_crypt() + */ const ENCRYPT = 0; - const DECRYPT = 1; - /**#@-*/ /** - * Block Length of the cipher - * - * RC4 is a stream cipher - * so we the block_size to 0 - * - * @see \phpseclib\Crypt\Base::block_size - * @var int - * @access private + * @see \phpseclib3\Crypt\RC4::_crypt() */ - var $block_size = 0; + const DECRYPT = 1; /** * Key Length (in bytes) * - * @see \phpseclib\Crypt\RC4::setKeyLength() + * @see \phpseclib3\Crypt\RC4::setKeyLength() * @var int - * @access private */ - var $key_length = 128; // = 1024 bits + protected $key_length = 128; // = 1024 bits /** * The mcrypt specific name of the cipher * - * @see \phpseclib\Crypt\Base::cipher_name_mcrypt + * @see \phpseclib3\Crypt\Common\SymmetricKey::cipher_name_mcrypt * @var string - * @access private */ - var $cipher_name_mcrypt = 'arcfour'; - - /** - * Holds whether performance-optimized $inline_crypt() can/should be used. - * - * @see \phpseclib\Crypt\Base::inline_crypt - * @var mixed - * @access private - */ - var $use_inline_crypt = false; // currently not available + protected $cipher_name_mcrypt = 'arcfour'; /** * The Key * * @see self::setKey() * @var string - * @access private */ - var $key; + protected $key; /** * The Key Stream for decryption and encryption * * @see self::setKey() * @var array - * @access private */ - var $stream; - - /** - * Default Constructor. - * - * Determines whether or not the mcrypt extension should be used. - * - * @see \phpseclib\Crypt\Base::__construct() - * @return \phpseclib\Crypt\RC4 - * @access public - */ - function __construct() - { - parent::__construct(Base::MODE_STREAM); - } + private $stream; /** * Test for engine validity * - * This is mainly just a wrapper to set things up for \phpseclib\Crypt\Base::isValidEngine() + * This is mainly just a wrapper to set things up for \phpseclib3\Crypt\Common\SymmetricKey::isValidEngine() * - * @see \phpseclib\Crypt\Base::__construct() + * @see \phpseclib3\Crypt\Common\SymmetricKey::__construct() * @param int $engine - * @access public * @return bool */ - function isValidEngine($engine) + protected function isValidEngineHelper($engine) { - if ($engine == Base::ENGINE_OPENSSL) { + if ($engine == self::ENGINE_OPENSSL) { + if ($this->continuousBuffer) { + return false; + } if (version_compare(PHP_VERSION, '5.3.7') >= 0) { $this->cipher_name_openssl = 'rc4-40'; } else { @@ -164,30 +127,26 @@ function isValidEngine($engine) } } - return parent::isValidEngine($engine); + return parent::isValidEngineHelper($engine); } /** - * Dummy function. - * - * Some protocols, such as WEP, prepend an "initialization vector" to the key, effectively creating a new key [1]. - * If you need to use an initialization vector in this manner, feel free to prepend it to the key, yourself, before - * calling setKey(). - * - * [1] WEP's initialization vectors (IV's) are used in a somewhat insecure way. Since, in that protocol, - * the IV's are relatively easy to predict, an attack described by - * {@link http://www.drizzle.com/~aboba/IEEE/rc4_ksaproc.pdf Scott Fluhrer, Itsik Mantin, and Adi Shamir} - * can be used to quickly guess at the rest of the key. The following links elaborate: + * Sets the key length * - * {@link http://www.rsa.com/rsalabs/node.asp?id=2009 http://www.rsa.com/rsalabs/node.asp?id=2009} - * {@link http://en.wikipedia.org/wiki/Related_key_attack http://en.wikipedia.org/wiki/Related_key_attack} + * Keys can be between 1 and 256 bytes long. * - * @param string $iv - * @see self::setKey() - * @access public + * @param int $length + * @throws \LengthException if the key length is invalid */ - function setIV($iv) + public function setKeyLength($length) { + if ($length < 8 || $length > 2048) { + throw new \LengthException('Key size of ' . $length . ' bits is not supported by this algorithm. Only keys between 1 and 256 bytes are supported'); + } + + $this->key_length = $length >> 3; + + parent::setKeyLength($length); } /** @@ -195,37 +154,32 @@ function setIV($iv) * * Keys can be between 1 and 256 bytes long. * - * @access public - * @param int $length + * @param string $key */ - function setKeyLength($length) + public function setKey($key) { - if ($length < 8) { - $this->key_length = 1; - } elseif ($length > 2048) { - $this->key_length = 256; - } else { - $this->key_length = $length >> 3; + $length = strlen($key); + if ($length < 1 || $length > 256) { + throw new \LengthException('Key size of ' . $length . ' bytes is not supported by RC4. Keys must be between 1 and 256 bytes long'); } - parent::setKeyLength($length); + parent::setKey($key); } /** * Encrypts a message. * - * @see \phpseclib\Crypt\Base::decrypt() - * @see self::_crypt() - * @access public + * @see \phpseclib3\Crypt\Common\SymmetricKey::decrypt() + * @see self::crypt() * @param string $plaintext * @return string $ciphertext */ - function encrypt($plaintext) + public function encrypt($plaintext) { - if ($this->engine != Base::ENGINE_INTERNAL) { + if ($this->engine != self::ENGINE_INTERNAL) { return parent::encrypt($plaintext); } - return $this->_crypt($plaintext, self::ENCRYPT); + return $this->crypt($plaintext, self::ENCRYPT); } /** @@ -234,27 +188,25 @@ function encrypt($plaintext) * $this->decrypt($this->encrypt($plaintext)) == $this->encrypt($this->encrypt($plaintext)). * At least if the continuous buffer is disabled. * - * @see \phpseclib\Crypt\Base::encrypt() - * @see self::_crypt() - * @access public + * @see \phpseclib3\Crypt\Common\SymmetricKey::encrypt() + * @see self::crypt() * @param string $ciphertext * @return string $plaintext */ - function decrypt($ciphertext) + public function decrypt($ciphertext) { - if ($this->engine != Base::ENGINE_INTERNAL) { + if ($this->engine != self::ENGINE_INTERNAL) { return parent::decrypt($ciphertext); } - return $this->_crypt($ciphertext, self::DECRYPT); + return $this->crypt($ciphertext, self::DECRYPT); } /** * Encrypts a block * - * @access private * @param string $in */ - function _encryptBlock($in) + protected function encryptBlock($in) { // RC4 does not utilize this method } @@ -262,10 +214,9 @@ function _encryptBlock($in) /** * Decrypts a block * - * @access private * @param string $in */ - function _decryptBlock($in) + protected function decryptBlock($in) { // RC4 does not utilize this method } @@ -273,10 +224,9 @@ function _decryptBlock($in) /** * Setup the key (expansion) * - * @see \phpseclib\Crypt\Base::_setupKey() - * @access private + * @see \phpseclib3\Crypt\Common\SymmetricKey::_setupKey() */ - function _setupKey() + protected function setupKey() { $key = $this->key; $keyLength = strlen($key); @@ -289,12 +239,12 @@ function _setupKey() $keyStream[$j] = $temp; } - $this->stream = array(); - $this->stream[self::DECRYPT] = $this->stream[self::ENCRYPT] = array( + $this->stream = []; + $this->stream[self::DECRYPT] = $this->stream[self::ENCRYPT] = [ 0, // index $i 0, // index $j $keyStream - ); + ]; } /** @@ -302,16 +252,14 @@ function _setupKey() * * @see self::encrypt() * @see self::decrypt() - * @access private * @param string $text * @param int $mode * @return string $text */ - function _crypt($text, $mode) + private function crypt($text, $mode) { if ($this->changed) { - $this->_setup(); - $this->changed = false; + $this->setup(); } $stream = &$this->stream[$mode]; diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php index 811d039d..207a9051 100644 --- a/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php @@ -8,659 +8,357 @@ * Here's an example of how to encrypt and decrypt text with this library: * * createKey()); + * $private = \phpseclib3\Crypt\RSA::createKey(); + * $public = $private->getPublicKey(); * - * $plaintext = 'terrafrost'; + * $plaintext = 'terrafrost'; * - * $rsa->loadKey($privatekey); - * $ciphertext = $rsa->encrypt($plaintext); + * $ciphertext = $public->encrypt($plaintext); * - * $rsa->loadKey($publickey); - * echo $rsa->decrypt($ciphertext); + * echo $private->decrypt($ciphertext); * ?> * * * Here's an example of how to create signatures and verify signatures with this library: * * createKey()); + * $private = \phpseclib3\Crypt\RSA::createKey(); + * $public = $private->getPublicKey(); * - * $plaintext = 'terrafrost'; + * $plaintext = 'terrafrost'; * - * $rsa->loadKey($privatekey); - * $signature = $rsa->sign($plaintext); + * $signature = $private->sign($plaintext); * - * $rsa->loadKey($publickey); - * echo $rsa->verify($plaintext, $signature) ? 'verified' : 'unverified'; + * echo $public->verify($plaintext, $signature) ? 'verified' : 'unverified'; * ?> * * - * @category Crypt - * @package RSA + * One thing to consider when using this: so phpseclib uses PSS mode by default. + * Technically, id-RSASSA-PSS has a different key format than rsaEncryption. So + * should phpseclib save to the id-RSASSA-PSS format by default or the + * rsaEncryption format? For stand-alone keys I figure rsaEncryption is better + * because SSH doesn't use PSS and idk how many SSH servers would be able to + * decode an id-RSASSA-PSS key. For X.509 certificates the id-RSASSA-PSS + * format is used by default (unless you change it up to use PKCS1 instead) + * * @author Jim Wigginton * @copyright 2009 Jim Wigginton * @license http://www.opensource.org/licenses/mit-license.html MIT License * @link http://phpseclib.sourceforge.net */ -namespace phpseclib\Crypt; +namespace phpseclib3\Crypt; -use phpseclib\Math\BigInteger; +use phpseclib3\Crypt\Common\AsymmetricKey; +use phpseclib3\Crypt\RSA\Formats\Keys\PSS; +use phpseclib3\Crypt\RSA\PrivateKey; +use phpseclib3\Crypt\RSA\PublicKey; +use phpseclib3\Exception\InconsistentSetupException; +use phpseclib3\Exception\UnsupportedAlgorithmException; +use phpseclib3\Math\BigInteger; /** * Pure-PHP PKCS#1 compliant implementation of RSA. * - * @package RSA * @author Jim Wigginton - * @access public */ -class RSA +abstract class RSA extends AsymmetricKey { - /**#@+ - * @access public - * @see self::encrypt() - * @see self::decrypt() + /** + * Algorithm Name + * + * @var string */ + const ALGORITHM = 'RSA'; + /** * Use {@link http://en.wikipedia.org/wiki/Optimal_Asymmetric_Encryption_Padding Optimal Asymmetric Encryption Padding} * (OAEP) for encryption / decryption. * - * Uses sha1 by default. + * Uses sha256 by default * * @see self::setHash() * @see self::setMGFHash() + * @see self::encrypt() + * @see self::decrypt() */ const ENCRYPTION_OAEP = 1; + /** * Use PKCS#1 padding. * - * Although self::ENCRYPTION_OAEP offers more security, including PKCS#1 padding is necessary for purposes of backwards + * Although self::PADDING_OAEP / self::PADDING_PSS offers more security, including PKCS#1 padding is necessary for purposes of backwards * compatibility with protocols (like SSH-1) written before OAEP's introduction. + * + * @see self::encrypt() + * @see self::decrypt() */ const ENCRYPTION_PKCS1 = 2; + /** * Do not use any padding * * Although this method is not recommended it can none-the-less sometimes be useful if you're trying to decrypt some legacy * stuff, if you're trying to diagnose why an encrypted message isn't decrypting, etc. + * + * @see self::encrypt() + * @see self::decrypt() */ - const ENCRYPTION_NONE = 3; - /**#@-*/ + const ENCRYPTION_NONE = 4; - /**#@+ - * @access public - * @see self::sign() - * @see self::verify() - * @see self::setHash() - */ /** * Use the Probabilistic Signature Scheme for signing * - * Uses sha1 by default. + * Uses sha256 and 0 as the salt length * * @see self::setSaltLength() * @see self::setMGFHash() + * @see self::setHash() + * @see self::sign() + * @see self::verify() + * @see self::setHash() */ - const SIGNATURE_PSS = 1; - /** - * Use the PKCS#1 scheme by default. - * - * Although self::SIGNATURE_PSS offers more security, including PKCS#1 signing is necessary for purposes of backwards - * compatibility with protocols (like SSH-2) written before PSS's introduction. - */ - const SIGNATURE_PKCS1 = 2; - /**#@-*/ - - /**#@+ - * @access private - * @see \phpseclib\Crypt\RSA::createKey() - */ - /** - * ASN1 Integer - */ - const ASN1_INTEGER = 2; - /** - * ASN1 Bit String - */ - const ASN1_BITSTRING = 3; - /** - * ASN1 Octet String - */ - const ASN1_OCTETSTRING = 4; - /** - * ASN1 Object Identifier - */ - const ASN1_OBJECT = 6; - /** - * ASN1 Sequence (with the constucted bit set) - */ - const ASN1_SEQUENCE = 48; - /**#@-*/ - - /**#@+ - * @access private - * @see \phpseclib\Crypt\RSA::__construct() - */ - /** - * To use the pure-PHP implementation - */ - const MODE_INTERNAL = 1; - /** - * To use the OpenSSL library - * - * (if enabled; otherwise, the internal implementation will be used) - */ - const MODE_OPENSSL = 2; - /**#@-*/ - - /**#@+ - * @access public - * @see \phpseclib\Crypt\RSA::createKey() - * @see \phpseclib\Crypt\RSA::setPrivateKeyFormat() - */ - /** - * PKCS#1 formatted private key - * - * Used by OpenSSH - */ - const PRIVATE_FORMAT_PKCS1 = 0; - /** - * PuTTY formatted private key - */ - const PRIVATE_FORMAT_PUTTY = 1; - /** - * XML formatted private key - */ - const PRIVATE_FORMAT_XML = 2; - /** - * PKCS#8 formatted private key - */ - const PRIVATE_FORMAT_PKCS8 = 8; - /** - * OpenSSH formatted private key - */ - const PRIVATE_FORMAT_OPENSSH = 9; - /**#@-*/ - - /**#@+ - * @access public - * @see \phpseclib\Crypt\RSA::createKey() - * @see \phpseclib\Crypt\RSA::setPublicKeyFormat() - */ - /** - * Raw public key - * - * An array containing two \phpseclib\Math\BigInteger objects. - * - * The exponent can be indexed with any of the following: - * - * 0, e, exponent, publicExponent - * - * The modulus can be indexed with any of the following: - * - * 1, n, modulo, modulus - */ - const PUBLIC_FORMAT_RAW = 3; - /** - * PKCS#1 formatted public key (raw) - * - * Used by File/X509.php - * - * Has the following header: - * - * -----BEGIN RSA PUBLIC KEY----- - * - * Analogous to ssh-keygen's pem format (as specified by -m) - */ - const PUBLIC_FORMAT_PKCS1 = 4; - const PUBLIC_FORMAT_PKCS1_RAW = 4; - /** - * XML formatted public key - */ - const PUBLIC_FORMAT_XML = 5; - /** - * OpenSSH formatted public key - * - * Place in $HOME/.ssh/authorized_keys - */ - const PUBLIC_FORMAT_OPENSSH = 6; - /** - * PKCS#1 formatted public key (encapsulated) - * - * Used by PHP's openssl_public_encrypt() and openssl's rsautl (when -pubin is set) - * - * Has the following header: - * - * -----BEGIN PUBLIC KEY----- - * - * Analogous to ssh-keygen's pkcs8 format (as specified by -m). Although PKCS8 - * is specific to private keys it's basically creating a DER-encoded wrapper - * for keys. This just extends that same concept to public keys (much like ssh-keygen) - */ - const PUBLIC_FORMAT_PKCS8 = 7; - /**#@-*/ + const SIGNATURE_PSS = 16; /** - * Precomputed Zero + * Use a relaxed version of PKCS#1 padding for signature verification * - * @var \phpseclib\Math\BigInteger - * @access private + * @see self::sign() + * @see self::verify() + * @see self::setHash() */ - var $zero; + const SIGNATURE_RELAXED_PKCS1 = 32; /** - * Precomputed One + * Use PKCS#1 padding for signature verification * - * @var \phpseclib\Math\BigInteger - * @access private + * @see self::sign() + * @see self::verify() + * @see self::setHash() */ - var $one; + const SIGNATURE_PKCS1 = 64; /** - * Private Key Format + * Encryption padding mode * * @var int - * @access private */ - var $privateKeyFormat = self::PRIVATE_FORMAT_PKCS1; + protected $encryptionPadding = self::ENCRYPTION_OAEP; /** - * Public Key Format + * Signature padding mode * * @var int - * @access public - */ - var $publicKeyFormat = self::PUBLIC_FORMAT_PKCS8; - - /** - * Modulus (ie. n) - * - * @var \phpseclib\Math\BigInteger - * @access private */ - var $modulus; + protected $signaturePadding = self::SIGNATURE_PSS; /** - * Modulus length + * Length of hash function output * - * @var \phpseclib\Math\BigInteger - * @access private + * @var int */ - var $k; + protected $hLen; /** - * Exponent (ie. e or d) + * Length of salt * - * @var \phpseclib\Math\BigInteger - * @access private + * @var int */ - var $exponent; + protected $sLen; /** - * Primes for Chinese Remainder Theorem (ie. p and q) + * Label * - * @var array - * @access private + * @var string */ - var $primes; + protected $label = ''; /** - * Exponents for Chinese Remainder Theorem (ie. dP and dQ) + * Hash function for the Mask Generation Function * - * @var array - * @access private + * @var \phpseclib3\Crypt\Hash */ - var $exponents; + protected $mgfHash; /** - * Coefficients for Chinese Remainder Theorem (ie. qInv) + * Length of MGF hash function output * - * @var array - * @access private + * @var int */ - var $coefficients; + protected $mgfHLen; /** - * Hash name + * Modulus (ie. n) * - * @var string - * @access private + * @var \phpseclib3\Math\BigInteger */ - var $hashName; + protected $modulus; /** - * Hash function + * Modulus length * - * @var \phpseclib\Crypt\Hash - * @access private + * @var \phpseclib3\Math\BigInteger */ - var $hash; + protected $k; /** - * Length of hash function output + * Exponent (ie. e or d) * - * @var int - * @access private + * @var \phpseclib3\Math\BigInteger */ - var $hLen; + protected $exponent; /** - * Length of salt + * Default public exponent * * @var int - * @access private + * @link http://en.wikipedia.org/wiki/65537_%28number%29 */ - var $sLen; + private static $defaultExponent = 65537; /** - * Hash function for the Mask Generation Function + * Enable Blinding? * - * @var \phpseclib\Crypt\Hash - * @access private + * @var bool */ - var $mgfHash; + protected static $enableBlinding = true; /** - * Length of MGF hash function output + * OpenSSL configuration file name. * - * @var int - * @access private + * @see self::createKey() + * @var ?string */ - var $mgfHLen; + protected static $configFile; /** - * Encryption mode + * Smallest Prime * - * @var int - * @access private - */ - var $encryptionMode = self::ENCRYPTION_OAEP; - - /** - * Signature mode + * Per , this number ought not result in primes smaller + * than 256 bits. As a consequence if the key you're trying to create is 1024 bits and you've set smallestPrime + * to 384 bits then you're going to get a 384 bit prime and a 640 bit prime (384 + 1024 % 384). At least if + * engine is set to self::ENGINE_INTERNAL. If Engine is set to self::ENGINE_OPENSSL then smallest Prime is + * ignored (ie. multi-prime RSA support is more intended as a way to speed up RSA key generation when there's + * a chance neither gmp nor OpenSSL are installed) * * @var int - * @access private */ - var $signatureMode = self::SIGNATURE_PSS; + private static $smallestPrime = 4096; /** * Public Exponent * - * @var mixed - * @access private - */ - var $publicExponent = false; - - /** - * Password - * - * @var string - * @access private - */ - var $password = false; - - /** - * Components - * - * For use with parsing XML formatted keys. PHP's XML Parser functions use utilized - instead of PHP's DOM functions - - * because PHP's XML Parser functions work on PHP4 whereas PHP's DOM functions - although surperior - don't. - * - * @see self::_start_element_handler() - * @var array - * @access private + * @var \phpseclib3\Math\BigInteger */ - var $components = array(); + protected $publicExponent; /** - * Current String + * Sets the public exponent for key generation * - * For use with parsing XML formatted keys. + * This will be 65537 unless changed. * - * @see self::_character_handler() - * @see self::_stop_element_handler() - * @var mixed - * @access private + * @param int $val */ - var $current; + public static function setExponent($val) + { + self::$defaultExponent = $val; + } /** - * OpenSSL configuration file name. + * Sets the smallest prime number in bits. Used for key generation * - * Set to null to use system configuration file. - * @see self::createKey() - * @var mixed - * @Access public - */ - var $configFile; - - /** - * Public key comment field. + * This will be 4096 unless changed. * - * @var string - * @access private + * @param int $val */ - var $comment = 'phpseclib-generated-key'; + public static function setSmallestPrime($val) + { + self::$smallestPrime = $val; + } /** - * The constructor + * Sets the OpenSSL config file path * - * If you want to make use of the openssl extension, you'll need to set the mode manually, yourself. The reason - * \phpseclib\Crypt\RSA doesn't do it is because OpenSSL doesn't fail gracefully. openssl_pkey_new(), in particular, requires - * openssl.cnf be present somewhere and, unfortunately, the only real way to find out is too late. + * Set to the empty string to use the default config file * - * @return \phpseclib\Crypt\RSA - * @access public + * @param string $val */ - function __construct() + public static function setOpenSSLConfigPath($val) { - $this->configFile = dirname(__FILE__) . '/../openssl.cnf'; - - if (!defined('CRYPT_RSA_MODE')) { - switch (true) { - // Math/BigInteger's openssl requirements are a little less stringent than Crypt/RSA's. in particular, - // Math/BigInteger doesn't require an openssl.cfg file whereas Crypt/RSA does. so if Math/BigInteger - // can't use OpenSSL it can be pretty trivially assumed, then, that Crypt/RSA can't either. - case defined('MATH_BIGINTEGER_OPENSSL_DISABLE'): - define('CRYPT_RSA_MODE', self::MODE_INTERNAL); - break; - case extension_loaded('openssl') && file_exists($this->configFile): - // some versions of XAMPP have mismatched versions of OpenSSL which causes it not to work - $versions = array(); - - // avoid generating errors (even with suppression) when phpinfo() is disabled (common in production systems) - if (strpos(ini_get('disable_functions'), 'phpinfo') === false) { - ob_start(); - @phpinfo(); - $content = ob_get_contents(); - ob_end_clean(); - - preg_match_all('#OpenSSL (Header|Library) Version(.*)#im', $content, $matches); - - if (!empty($matches[1])) { - for ($i = 0; $i < count($matches[1]); $i++) { - $fullVersion = trim(str_replace('=>', '', strip_tags($matches[2][$i]))); - - // Remove letter part in OpenSSL version - if (!preg_match('/(\d+\.\d+\.\d+)/i', $fullVersion, $m)) { - $versions[$matches[1][$i]] = $fullVersion; - } else { - $versions[$matches[1][$i]] = $m[0]; - } - } - } - } - - // it doesn't appear that OpenSSL versions were reported upon until PHP 5.3+ - switch (true) { - case !isset($versions['Header']): - case !isset($versions['Library']): - case $versions['Header'] == $versions['Library']: - case version_compare($versions['Header'], '1.0.0') >= 0 && version_compare($versions['Library'], '1.0.0') >= 0: - define('CRYPT_RSA_MODE', self::MODE_OPENSSL); - break; - default: - define('CRYPT_RSA_MODE', self::MODE_INTERNAL); - define('MATH_BIGINTEGER_OPENSSL_DISABLE', true); - } - break; - default: - define('CRYPT_RSA_MODE', self::MODE_INTERNAL); - } - } - - $this->zero = new BigInteger(); - $this->one = new BigInteger(1); - - $this->hash = new Hash('sha1'); - $this->hLen = $this->hash->getLength(); - $this->hashName = 'sha1'; - $this->mgfHash = new Hash('sha1'); - $this->mgfHLen = $this->mgfHash->getLength(); + self::$configFile = $val; } /** - * Create public / private key pair + * Create a private key * - * Returns an array with the following three elements: - * - 'privatekey': The private key. - * - 'publickey': The public key. - * - 'partialkey': A partially computed key (if the execution time exceeded $timeout). - * Will need to be passed back to \phpseclib\Crypt\RSA::createKey() as the third parameter for further processing. + * The public key can be extracted from the private key * - * @access public + * @return RSA\PrivateKey * @param int $bits - * @param int $timeout - * @param array $partial */ - function createKey($bits = 1024, $timeout = false, $partial = array()) + public static function createKey($bits = 2048) { - if (!defined('CRYPT_RSA_EXPONENT')) { - // http://en.wikipedia.org/wiki/65537_%28number%29 - define('CRYPT_RSA_EXPONENT', '65537'); - } - // per , this number ought not result in primes smaller - // than 256 bits. as a consequence if the key you're trying to create is 1024 bits and you've set CRYPT_RSA_SMALLEST_PRIME - // to 384 bits then you're going to get a 384 bit prime and a 640 bit prime (384 + 1024 % 384). at least if - // CRYPT_RSA_MODE is set to self::MODE_INTERNAL. if CRYPT_RSA_MODE is set to self::MODE_OPENSSL then - // CRYPT_RSA_SMALLEST_PRIME is ignored (ie. multi-prime RSA support is more intended as a way to speed up RSA key - // generation when there's a chance neither gmp nor OpenSSL are installed) - if (!defined('CRYPT_RSA_SMALLEST_PRIME')) { - define('CRYPT_RSA_SMALLEST_PRIME', 4096); + self::initialize_static_variables(); + + $regSize = $bits >> 1; // divide by two to see how many bits P and Q would be + if ($regSize > self::$smallestPrime) { + $num_primes = floor($bits / self::$smallestPrime); + $regSize = self::$smallestPrime; + } else { + $num_primes = 2; } - // OpenSSL uses 65537 as the exponent and requires RSA keys be 384 bits minimum - if (CRYPT_RSA_MODE == self::MODE_OPENSSL && $bits >= 384 && CRYPT_RSA_EXPONENT == 65537) { - $config = array(); - if (isset($this->configFile)) { - $config['config'] = $this->configFile; + if ($num_primes == 2 && $bits >= 384 && self::$defaultExponent == 65537) { + if (!isset(self::$engines['PHP'])) { + self::useBestEngine(); } - $rsa = openssl_pkey_new(array('private_key_bits' => $bits) + $config); - openssl_pkey_export($rsa, $privatekey, null, $config); - $publickey = openssl_pkey_get_details($rsa); - $publickey = $publickey['key']; - $privatekey = call_user_func_array(array($this, '_convertPrivateKey'), array_values($this->_parseKey($privatekey, self::PRIVATE_FORMAT_PKCS1))); - $publickey = call_user_func_array(array($this, '_convertPublicKey'), array_values($this->_parseKey($publickey, self::PUBLIC_FORMAT_PKCS1))); + // OpenSSL uses 65537 as the exponent and requires RSA keys be 384 bits minimum + if (self::$engines['OpenSSL']) { + $config = []; + if (self::$configFile) { + $config['config'] = self::$configFile; + } + $rsa = openssl_pkey_new(['private_key_bits' => $bits] + $config); + openssl_pkey_export($rsa, $privatekeystr, null, $config); - // clear the buffer of error strings stemming from a minimalistic openssl.cnf - while (openssl_error_string() !== false) { - } + // clear the buffer of error strings stemming from a minimalistic openssl.cnf + while (openssl_error_string() !== false) { + } - return array( - 'privatekey' => $privatekey, - 'publickey' => $publickey, - 'partialkey' => false - ); + return RSA::load($privatekeystr); + } } static $e; if (!isset($e)) { - $e = new BigInteger(CRYPT_RSA_EXPONENT); - } - - extract($this->_generateMinMax($bits)); - $absoluteMin = $min; - $temp = $bits >> 1; // divide by two to see how many bits P and Q would be - if ($temp > CRYPT_RSA_SMALLEST_PRIME) { - $num_primes = floor($bits / CRYPT_RSA_SMALLEST_PRIME); - $temp = CRYPT_RSA_SMALLEST_PRIME; - } else { - $num_primes = 2; + $e = new BigInteger(self::$defaultExponent); } - extract($this->_generateMinMax($temp + $bits % $temp)); - $finalMax = $max; - extract($this->_generateMinMax($temp)); - $generator = new BigInteger(); - - $n = $this->one->copy(); - if (!empty($partial)) { - extract(unserialize($partial)); - } else { - $exponents = $coefficients = $primes = array(); - $lcm = array( - 'top' => $this->one->copy(), - 'bottom' => false - ); - } - - $start = time(); - $i0 = count($primes) + 1; + $n = clone self::$one; + $exponents = $coefficients = $primes = []; + $lcm = [ + 'top' => clone self::$one, + 'bottom' => false + ]; do { - for ($i = $i0; $i <= $num_primes; $i++) { - if ($timeout !== false) { - $timeout-= time() - $start; - $start = time(); - if ($timeout <= 0) { - return array( - 'privatekey' => '', - 'publickey' => '', - 'partialkey' => serialize(array( - 'primes' => $primes, - 'coefficients' => $coefficients, - 'lcm' => $lcm, - 'exponents' => $exponents - )) - ); - } - } - - if ($i == $num_primes) { - list($min, $temp) = $absoluteMin->divide($n); - if (!$temp->equals($this->zero)) { - $min = $min->add($this->one); // ie. ceil() - } - $primes[$i] = $generator->randomPrime($min, $finalMax, $timeout); + for ($i = 1; $i <= $num_primes; $i++) { + if ($i != $num_primes) { + $primes[$i] = BigInteger::randomPrime($regSize); } else { - $primes[$i] = $generator->randomPrime($min, $max, $timeout); - } - - if ($primes[$i] === false) { // if we've reached the timeout - if (count($primes) > 1) { - $partialkey = ''; - } else { - array_pop($primes); - $partialkey = serialize(array( - 'primes' => $primes, - 'coefficients' => $coefficients, - 'lcm' => $lcm, - 'exponents' => $exponents - )); - } - - return array( - 'privatekey' => '', - 'publickey' => '', - 'partialkey' => $partialkey - ); + extract(BigInteger::minMaxBits($bits)); + /** @var BigInteger $min + * @var BigInteger $max + */ + list($min) = $min->divide($n); + $min = $min->add(self::$one); + list($max) = $max->divide($n); + $primes[$i] = BigInteger::randomRangePrime($min, $max); } // the first coefficient is calculated differently from the rest @@ -671,24 +369,27 @@ function createKey($bits = 1024, $timeout = false, $partial = array()) $n = $n->multiply($primes[$i]); - $temp = $primes[$i]->subtract($this->one); + $temp = $primes[$i]->subtract(self::$one); // textbook RSA implementations use Euler's totient function instead of the least common multiple. // see http://en.wikipedia.org/wiki/Euler%27s_totient_function $lcm['top'] = $lcm['top']->multiply($temp); $lcm['bottom'] = $lcm['bottom'] === false ? $temp : $lcm['bottom']->gcd($temp); - - $exponents[$i] = $e->modInverse($temp); } list($temp) = $lcm['top']->divide($lcm['bottom']); $gcd = $temp->gcd($e); $i0 = 1; - } while (!$gcd->equals($this->one)); + } while (!$gcd->equals(self::$one)); + + $coefficients[2] = $primes[2]->modInverse($primes[1]); $d = $e->modInverse($temp); - $coefficients[2] = $primes[2]->modInverse($primes[1]); + foreach ($primes as $i => $prime) { + $temp = $prime->subtract(self::$one); + $exponents[$i] = $e->modInverse($temp); + } // from : // RSAPrivateKey ::= SEQUENCE { @@ -703,2211 +404,184 @@ function createKey($bits = 1024, $timeout = false, $partial = array()) // coefficient INTEGER, -- (inverse of q) mod p // otherPrimeInfos OtherPrimeInfos OPTIONAL // } + $privatekey = new PrivateKey(); + $privatekey->modulus = $n; + $privatekey->k = $bits >> 3; + $privatekey->publicExponent = $e; + $privatekey->exponent = $d; + $privatekey->primes = $primes; + $privatekey->exponents = $exponents; + $privatekey->coefficients = $coefficients; + + /* + $publickey = new PublicKey; + $publickey->modulus = $n; + $publickey->k = $bits >> 3; + $publickey->exponent = $e; + $publickey->publicExponent = $e; + $publickey->isPublic = true; + */ - return array( - 'privatekey' => $this->_convertPrivateKey($n, $e, $d, $primes, $exponents, $coefficients), - 'publickey' => $this->_convertPublicKey($n, $e), - 'partialkey' => false - ); + return $privatekey; } /** - * Convert a private key to the appropriate format. - * - * @access private - * @see self::setPrivateKeyFormat() - * @param Math_BigInteger $n - * @param Math_BigInteger $e - * @param Math_BigInteger $d - * @param array $primes - * @param array $exponents - * @param array $coefficients - * @return string + * OnLoad Handler + * + * @return bool */ - function _convertPrivateKey($n, $e, $d, $primes, $exponents, $coefficients) + protected static function onLoad(array $components) { - $signed = $this->privateKeyFormat != self::PRIVATE_FORMAT_XML; - $num_primes = count($primes); - $raw = array( - 'version' => $num_primes == 2 ? chr(0) : chr(1), // two-prime vs. multi - 'modulus' => $n->toBytes($signed), - 'publicExponent' => $e->toBytes($signed), - 'privateExponent' => $d->toBytes($signed), - 'prime1' => $primes[1]->toBytes($signed), - 'prime2' => $primes[2]->toBytes($signed), - 'exponent1' => $exponents[1]->toBytes($signed), - 'exponent2' => $exponents[2]->toBytes($signed), - 'coefficient' => $coefficients[2]->toBytes($signed) - ); - - // if the format in question does not support multi-prime rsa and multi-prime rsa was used, - // call _convertPublicKey() instead. - switch ($this->privateKeyFormat) { - case self::PRIVATE_FORMAT_XML: - if ($num_primes != 2) { - return false; - } - return "\r\n" . - ' ' . base64_encode($raw['modulus']) . "\r\n" . - ' ' . base64_encode($raw['publicExponent']) . "\r\n" . - '

' . base64_encode($raw['prime1']) . "

\r\n" . - ' ' . base64_encode($raw['prime2']) . "\r\n" . - ' ' . base64_encode($raw['exponent1']) . "\r\n" . - ' ' . base64_encode($raw['exponent2']) . "\r\n" . - ' ' . base64_encode($raw['coefficient']) . "\r\n" . - ' ' . base64_encode($raw['privateExponent']) . "\r\n" . - '
'; - break; - case self::PRIVATE_FORMAT_PUTTY: - if ($num_primes != 2) { - return false; - } - $key = "PuTTY-User-Key-File-2: ssh-rsa\r\nEncryption: "; - $encryption = (!empty($this->password) || is_string($this->password)) ? 'aes256-cbc' : 'none'; - $key.= $encryption; - $key.= "\r\nComment: " . $this->comment . "\r\n"; - $public = pack( - 'Na*Na*Na*', - strlen('ssh-rsa'), - 'ssh-rsa', - strlen($raw['publicExponent']), - $raw['publicExponent'], - strlen($raw['modulus']), - $raw['modulus'] - ); - $source = pack( - 'Na*Na*Na*Na*', - strlen('ssh-rsa'), - 'ssh-rsa', - strlen($encryption), - $encryption, - strlen($this->comment), - $this->comment, - strlen($public), - $public - ); - $public = base64_encode($public); - $key.= "Public-Lines: " . ((strlen($public) + 63) >> 6) . "\r\n"; - $key.= chunk_split($public, 64); - $private = pack( - 'Na*Na*Na*Na*', - strlen($raw['privateExponent']), - $raw['privateExponent'], - strlen($raw['prime1']), - $raw['prime1'], - strlen($raw['prime2']), - $raw['prime2'], - strlen($raw['coefficient']), - $raw['coefficient'] - ); - if (empty($this->password) && !is_string($this->password)) { - $source.= pack('Na*', strlen($private), $private); - $hashkey = 'putty-private-key-file-mac-key'; - } else { - $private.= Random::string(16 - (strlen($private) & 15)); - $source.= pack('Na*', strlen($private), $private); - $sequence = 0; - $symkey = ''; - while (strlen($symkey) < 32) { - $temp = pack('Na*', $sequence++, $this->password); - $symkey.= pack('H*', sha1($temp)); - } - $symkey = substr($symkey, 0, 32); - $crypto = new AES(); - - $crypto->setKey($symkey); - $crypto->disablePadding(); - $private = $crypto->encrypt($private); - $hashkey = 'putty-private-key-file-mac-key' . $this->password; - } - - $private = base64_encode($private); - $key.= 'Private-Lines: ' . ((strlen($private) + 63) >> 6) . "\r\n"; - $key.= chunk_split($private, 64); - $hash = new Hash('sha1'); - $hash->setKey(pack('H*', sha1($hashkey))); - $key.= 'Private-MAC: ' . bin2hex($hash->hash($source)) . "\r\n"; - - return $key; - case self::PRIVATE_FORMAT_OPENSSH: - if ($num_primes != 2) { - return false; - } - $publicKey = pack('Na*Na*Na*', strlen('ssh-rsa'), 'ssh-rsa', strlen($raw['publicExponent']), $raw['publicExponent'], strlen($raw['modulus']), $raw['modulus']); - $privateKey = pack( - 'Na*Na*Na*Na*Na*Na*Na*', - strlen('ssh-rsa'), - 'ssh-rsa', - strlen($raw['modulus']), - $raw['modulus'], - strlen($raw['publicExponent']), - $raw['publicExponent'], - strlen($raw['privateExponent']), - $raw['privateExponent'], - strlen($raw['coefficient']), - $raw['coefficient'], - strlen($raw['prime1']), - $raw['prime1'], - strlen($raw['prime2']), - $raw['prime2'] - ); - $checkint = Random::string(4); - $paddedKey = pack( - 'a*Na*', - $checkint . $checkint . $privateKey, - strlen($this->comment), - $this->comment - ); - $paddingLength = (7 * strlen($paddedKey)) % 8; - for ($i = 1; $i <= $paddingLength; $i++) { - $paddedKey.= chr($i); - } - $key = pack( - 'Na*Na*Na*NNa*Na*', - strlen('none'), - 'none', - strlen('none'), - 'none', - 0, - '', - 1, - strlen($publicKey), - $publicKey, - strlen($paddedKey), - $paddedKey - ); - $key = "openssh-key-v1\0$key"; - - return "-----BEGIN OPENSSH PRIVATE KEY-----\r\n" . - chunk_split(base64_encode($key), 70) . - "-----END OPENSSH PRIVATE KEY-----"; - default: // eg. self::PRIVATE_FORMAT_PKCS1 - $components = array(); - foreach ($raw as $name => $value) { - $components[$name] = pack('Ca*a*', self::ASN1_INTEGER, $this->_encodeLength(strlen($value)), $value); - } + $key = $components['isPublicKey'] ? + new PublicKey() : + new PrivateKey(); - $RSAPrivateKey = implode('', $components); - - if ($num_primes > 2) { - $OtherPrimeInfos = ''; - for ($i = 3; $i <= $num_primes; $i++) { - // OtherPrimeInfos ::= SEQUENCE SIZE(1..MAX) OF OtherPrimeInfo - // - // OtherPrimeInfo ::= SEQUENCE { - // prime INTEGER, -- ri - // exponent INTEGER, -- di - // coefficient INTEGER -- ti - // } - $OtherPrimeInfo = pack('Ca*a*', self::ASN1_INTEGER, $this->_encodeLength(strlen($primes[$i]->toBytes(true))), $primes[$i]->toBytes(true)); - $OtherPrimeInfo.= pack('Ca*a*', self::ASN1_INTEGER, $this->_encodeLength(strlen($exponents[$i]->toBytes(true))), $exponents[$i]->toBytes(true)); - $OtherPrimeInfo.= pack('Ca*a*', self::ASN1_INTEGER, $this->_encodeLength(strlen($coefficients[$i]->toBytes(true))), $coefficients[$i]->toBytes(true)); - $OtherPrimeInfos.= pack('Ca*a*', self::ASN1_SEQUENCE, $this->_encodeLength(strlen($OtherPrimeInfo)), $OtherPrimeInfo); - } - $RSAPrivateKey.= pack('Ca*a*', self::ASN1_SEQUENCE, $this->_encodeLength(strlen($OtherPrimeInfos)), $OtherPrimeInfos); - } + $key->modulus = $components['modulus']; + $key->publicExponent = $components['publicExponent']; + $key->k = $key->modulus->getLengthInBytes(); - $RSAPrivateKey = pack('Ca*a*', self::ASN1_SEQUENCE, $this->_encodeLength(strlen($RSAPrivateKey)), $RSAPrivateKey); - - if ($this->privateKeyFormat == self::PRIVATE_FORMAT_PKCS8) { - $rsaOID = pack('H*', '300d06092a864886f70d0101010500'); // hex version of MA0GCSqGSIb3DQEBAQUA - $RSAPrivateKey = pack( - 'Ca*a*Ca*a*', - self::ASN1_INTEGER, - "\01\00", - $rsaOID, - 4, - $this->_encodeLength(strlen($RSAPrivateKey)), - $RSAPrivateKey - ); - $RSAPrivateKey = pack('Ca*a*', self::ASN1_SEQUENCE, $this->_encodeLength(strlen($RSAPrivateKey)), $RSAPrivateKey); - if (!empty($this->password) || is_string($this->password)) { - $salt = Random::string(8); - $iterationCount = 2048; - - $crypto = new DES(); - $crypto->setPassword($this->password, 'pbkdf1', 'md5', $salt, $iterationCount); - $RSAPrivateKey = $crypto->encrypt($RSAPrivateKey); - - $parameters = pack( - 'Ca*a*Ca*N', - self::ASN1_OCTETSTRING, - $this->_encodeLength(strlen($salt)), - $salt, - self::ASN1_INTEGER, - $this->_encodeLength(4), - $iterationCount - ); - $pbeWithMD5AndDES_CBC = "\x2a\x86\x48\x86\xf7\x0d\x01\x05\x03"; - - $encryptionAlgorithm = pack( - 'Ca*a*Ca*a*', - self::ASN1_OBJECT, - $this->_encodeLength(strlen($pbeWithMD5AndDES_CBC)), - $pbeWithMD5AndDES_CBC, - self::ASN1_SEQUENCE, - $this->_encodeLength(strlen($parameters)), - $parameters - ); - - $RSAPrivateKey = pack( - 'Ca*a*Ca*a*', - self::ASN1_SEQUENCE, - $this->_encodeLength(strlen($encryptionAlgorithm)), - $encryptionAlgorithm, - self::ASN1_OCTETSTRING, - $this->_encodeLength(strlen($RSAPrivateKey)), - $RSAPrivateKey - ); - - $RSAPrivateKey = pack('Ca*a*', self::ASN1_SEQUENCE, $this->_encodeLength(strlen($RSAPrivateKey)), $RSAPrivateKey); - - $RSAPrivateKey = "-----BEGIN ENCRYPTED PRIVATE KEY-----\r\n" . - chunk_split(base64_encode($RSAPrivateKey), 64) . - '-----END ENCRYPTED PRIVATE KEY-----'; - } else { - $RSAPrivateKey = "-----BEGIN PRIVATE KEY-----\r\n" . - chunk_split(base64_encode($RSAPrivateKey), 64) . - '-----END PRIVATE KEY-----'; - } - return $RSAPrivateKey; - } + if ($components['isPublicKey'] || !isset($components['privateExponent'])) { + $key->exponent = $key->publicExponent; + } else { + $key->privateExponent = $components['privateExponent']; + $key->exponent = $key->privateExponent; + $key->primes = $components['primes']; + $key->exponents = $components['exponents']; + $key->coefficients = $components['coefficients']; + } + + if ($components['format'] == PSS::class) { + // in the X509 world RSA keys are assumed to use PKCS1 padding by default. only if the key is + // explicitly a PSS key is the use of PSS assumed. phpseclib does not work like this. phpseclib + // uses PSS padding by default. it assumes the more secure method by default and altho it provides + // for the less secure PKCS1 method you have to go out of your way to use it. this is consistent + // with the latest trends in crypto. libsodium (NaCl) is actually a little more extreme in that + // not only does it defaults to the most secure methods - it doesn't even let you choose less + // secure methods + //$key = $key->withPadding(self::SIGNATURE_PSS); + if (isset($components['hash'])) { + $key = $key->withHash($components['hash']); + } + if (isset($components['MGFHash'])) { + $key = $key->withMGFHash($components['MGFHash']); + } + if (isset($components['saltLength'])) { + $key = $key->withSaltLength($components['saltLength']); + } + } - if (!empty($this->password) || is_string($this->password)) { - $iv = Random::string(8); - $symkey = pack('H*', md5($this->password . $iv)); // symkey is short for symmetric key - $symkey.= substr(pack('H*', md5($symkey . $this->password . $iv)), 0, 8); - $des = new TripleDES(); - $des->setKey($symkey); - $des->setIV($iv); - $iv = strtoupper(bin2hex($iv)); - $RSAPrivateKey = "-----BEGIN RSA PRIVATE KEY-----\r\n" . - "Proc-Type: 4,ENCRYPTED\r\n" . - "DEK-Info: DES-EDE3-CBC,$iv\r\n" . - "\r\n" . - chunk_split(base64_encode($des->encrypt($RSAPrivateKey)), 64) . - '-----END RSA PRIVATE KEY-----'; - } else { - $RSAPrivateKey = "-----BEGIN RSA PRIVATE KEY-----\r\n" . - chunk_split(base64_encode($RSAPrivateKey), 64) . - '-----END RSA PRIVATE KEY-----'; - } + return $key; + } - return $RSAPrivateKey; + /** + * Initialize static variables + */ + protected static function initialize_static_variables() + { + if (!isset(self::$configFile)) { + self::$configFile = dirname(__FILE__) . '/../openssl.cnf'; } + + parent::initialize_static_variables(); } /** - * Convert a public key to the appropriate format + * Constructor * - * @access private - * @see self::setPublicKeyFormat() - * @param Math_BigInteger $n - * @param Math_BigInteger $e - * @return string|array + * PublicKey and PrivateKey objects can only be created from abstract RSA class */ - function _convertPublicKey($n, $e) + protected function __construct() { - $signed = $this->publicKeyFormat != self::PUBLIC_FORMAT_XML; - - $modulus = $n->toBytes($signed); - $publicExponent = $e->toBytes($signed); - - switch ($this->publicKeyFormat) { - case self::PUBLIC_FORMAT_RAW: - return array('e' => $e->copy(), 'n' => $n->copy()); - case self::PUBLIC_FORMAT_XML: - return "\r\n" . - ' ' . base64_encode($modulus) . "\r\n" . - ' ' . base64_encode($publicExponent) . "\r\n" . - ''; - break; - case self::PUBLIC_FORMAT_OPENSSH: - // from : - // string "ssh-rsa" - // mpint e - // mpint n - $RSAPublicKey = pack('Na*Na*Na*', strlen('ssh-rsa'), 'ssh-rsa', strlen($publicExponent), $publicExponent, strlen($modulus), $modulus); - $RSAPublicKey = 'ssh-rsa ' . base64_encode($RSAPublicKey) . ' ' . $this->comment; - - return $RSAPublicKey; - default: // eg. self::PUBLIC_FORMAT_PKCS1_RAW or self::PUBLIC_FORMAT_PKCS1 - // from : - // RSAPublicKey ::= SEQUENCE { - // modulus INTEGER, -- n - // publicExponent INTEGER -- e - // } - $components = array( - 'modulus' => pack('Ca*a*', self::ASN1_INTEGER, $this->_encodeLength(strlen($modulus)), $modulus), - 'publicExponent' => pack('Ca*a*', self::ASN1_INTEGER, $this->_encodeLength(strlen($publicExponent)), $publicExponent) - ); - - $RSAPublicKey = pack( - 'Ca*a*a*', - self::ASN1_SEQUENCE, - $this->_encodeLength(strlen($components['modulus']) + strlen($components['publicExponent'])), - $components['modulus'], - $components['publicExponent'] - ); - - if ($this->publicKeyFormat == self::PUBLIC_FORMAT_PKCS1_RAW) { - $RSAPublicKey = "-----BEGIN RSA PUBLIC KEY-----\r\n" . - chunk_split(base64_encode($RSAPublicKey), 64) . - '-----END RSA PUBLIC KEY-----'; - } else { - // sequence(oid(1.2.840.113549.1.1.1), null)) = rsaEncryption. - $rsaOID = pack('H*', '300d06092a864886f70d0101010500'); // hex version of MA0GCSqGSIb3DQEBAQUA - $RSAPublicKey = chr(0) . $RSAPublicKey; - $RSAPublicKey = chr(3) . $this->_encodeLength(strlen($RSAPublicKey)) . $RSAPublicKey; - - $RSAPublicKey = pack( - 'Ca*a*', - self::ASN1_SEQUENCE, - $this->_encodeLength(strlen($rsaOID . $RSAPublicKey)), - $rsaOID . $RSAPublicKey - ); - - $RSAPublicKey = "-----BEGIN PUBLIC KEY-----\r\n" . - chunk_split(base64_encode($RSAPublicKey), 64) . - '-----END PUBLIC KEY-----'; - } + parent::__construct(); - return $RSAPublicKey; - } + $this->hLen = $this->hash->getLengthInBytes(); + $this->mgfHash = new Hash('sha256'); + $this->mgfHLen = $this->mgfHash->getLengthInBytes(); } /** - * Break a public or private key down into its constituant components - * - * @access private - * @see self::_convertPublicKey() - * @see self::_convertPrivateKey() - * @param string|array $key - * @param int $type - * @return array|bool + * Integer-to-Octet-String primitive + * + * See {@link http://tools.ietf.org/html/rfc3447#section-4.1 RFC3447#section-4.1}. + * + * @param bool|\phpseclib3\Math\BigInteger $x + * @param int $xLen + * @return bool|string */ - function _parseKey($key, $type) + protected function i2osp($x, $xLen) { - if ($type != self::PUBLIC_FORMAT_RAW && !is_string($key)) { + if ($x === false) { return false; } + $x = $x->toBytes(); + if (strlen($x) > $xLen) { + throw new \OutOfRangeException('Resultant string length out of range'); + } + return str_pad($x, $xLen, chr(0), STR_PAD_LEFT); + } - switch ($type) { - case self::PUBLIC_FORMAT_RAW: - if (!is_array($key)) { - return false; - } - $components = array(); - switch (true) { - case isset($key['e']): - $components['publicExponent'] = $key['e']->copy(); - break; - case isset($key['exponent']): - $components['publicExponent'] = $key['exponent']->copy(); - break; - case isset($key['publicExponent']): - $components['publicExponent'] = $key['publicExponent']->copy(); - break; - case isset($key[0]): - $components['publicExponent'] = $key[0]->copy(); - } - switch (true) { - case isset($key['n']): - $components['modulus'] = $key['n']->copy(); - break; - case isset($key['modulo']): - $components['modulus'] = $key['modulo']->copy(); - break; - case isset($key['modulus']): - $components['modulus'] = $key['modulus']->copy(); - break; - case isset($key[1]): - $components['modulus'] = $key[1]->copy(); - } - return isset($components['modulus']) && isset($components['publicExponent']) ? $components : false; - case self::PRIVATE_FORMAT_PKCS1: - case self::PRIVATE_FORMAT_PKCS8: - case self::PUBLIC_FORMAT_PKCS1: - /* Although PKCS#1 proposes a format that public and private keys can use, encrypting them is - "outside the scope" of PKCS#1. PKCS#1 then refers you to PKCS#12 and PKCS#15 if you're wanting to - protect private keys, however, that's not what OpenSSL* does. OpenSSL protects private keys by adding - two new "fields" to the key - DEK-Info and Proc-Type. These fields are discussed here: - - http://tools.ietf.org/html/rfc1421#section-4.6.1.1 - http://tools.ietf.org/html/rfc1421#section-4.6.1.3 - - DES-EDE3-CBC as an algorithm, however, is not discussed anywhere, near as I can tell. - DES-CBC and DES-EDE are discussed in RFC1423, however, DES-EDE3-CBC isn't, nor is its key derivation - function. As is, the definitive authority on this encoding scheme isn't the IETF but rather OpenSSL's - own implementation. ie. the implementation *is* the standard and any bugs that may exist in that - implementation are part of the standard, as well. - - * OpenSSL is the de facto standard. It's utilized by OpenSSH and other projects */ - if (preg_match('#DEK-Info: (.+),(.+)#', $key, $matches)) { - $iv = pack('H*', trim($matches[2])); - $symkey = pack('H*', md5($this->password . substr($iv, 0, 8))); // symkey is short for symmetric key - $symkey.= pack('H*', md5($symkey . $this->password . substr($iv, 0, 8))); - // remove the Proc-Type / DEK-Info sections as they're no longer needed - $key = preg_replace('#^(?:Proc-Type|DEK-Info): .*#m', '', $key); - $ciphertext = $this->_extractBER($key); - if ($ciphertext === false) { - $ciphertext = $key; - } - switch ($matches[1]) { - case 'AES-256-CBC': - $crypto = new AES(); - break; - case 'AES-128-CBC': - $symkey = substr($symkey, 0, 16); - $crypto = new AES(); - break; - case 'DES-EDE3-CFB': - $crypto = new TripleDES(Base::MODE_CFB); - break; - case 'DES-EDE3-CBC': - $symkey = substr($symkey, 0, 24); - $crypto = new TripleDES(); - break; - case 'DES-CBC': - $crypto = new DES(); - break; - default: - return false; - } - $crypto->setKey($symkey); - $crypto->setIV($iv); - $decoded = $crypto->decrypt($ciphertext); - } else { - $decoded = $this->_extractBER($key); - } - - if ($decoded !== false) { - $key = $decoded; - } - - $components = array(); - - if (ord($this->_string_shift($key)) != self::ASN1_SEQUENCE) { - return false; - } - if ($this->_decodeLength($key) != strlen($key)) { - return false; - } - - $tag = ord($this->_string_shift($key)); - /* intended for keys for which OpenSSL's asn1parse returns the following: - - 0:d=0 hl=4 l= 631 cons: SEQUENCE - 4:d=1 hl=2 l= 1 prim: INTEGER :00 - 7:d=1 hl=2 l= 13 cons: SEQUENCE - 9:d=2 hl=2 l= 9 prim: OBJECT :rsaEncryption - 20:d=2 hl=2 l= 0 prim: NULL - 22:d=1 hl=4 l= 609 prim: OCTET STRING - - ie. PKCS8 keys*/ - - if ($tag == self::ASN1_INTEGER && substr($key, 0, 3) == "\x01\x00\x30") { - $this->_string_shift($key, 3); - $tag = self::ASN1_SEQUENCE; - } + /** + * Octet-String-to-Integer primitive + * + * See {@link http://tools.ietf.org/html/rfc3447#section-4.2 RFC3447#section-4.2}. + * + * @param string $x + * @return \phpseclib3\Math\BigInteger + */ + protected function os2ip($x) + { + return new BigInteger($x, 256); + } - if ($tag == self::ASN1_SEQUENCE) { - $temp = $this->_string_shift($key, $this->_decodeLength($key)); - if (ord($this->_string_shift($temp)) != self::ASN1_OBJECT) { - return false; - } - $length = $this->_decodeLength($temp); - switch ($this->_string_shift($temp, $length)) { - case "\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01": // rsaEncryption - case "\x2A\x86\x48\x86\xF7\x0D\x01\x01\x0A": // rsaPSS - break; - case "\x2a\x86\x48\x86\xf7\x0d\x01\x05\x03": // pbeWithMD5AndDES-CBC - /* - PBEParameter ::= SEQUENCE { - salt OCTET STRING (SIZE(8)), - iterationCount INTEGER } - */ - if (ord($this->_string_shift($temp)) != self::ASN1_SEQUENCE) { - return false; - } - if ($this->_decodeLength($temp) != strlen($temp)) { - return false; - } - $this->_string_shift($temp); // assume it's an octet string - $salt = $this->_string_shift($temp, $this->_decodeLength($temp)); - if (ord($this->_string_shift($temp)) != self::ASN1_INTEGER) { - return false; - } - $this->_decodeLength($temp); - list(, $iterationCount) = unpack('N', str_pad($temp, 4, chr(0), STR_PAD_LEFT)); - $this->_string_shift($key); // assume it's an octet string - $length = $this->_decodeLength($key); - if (strlen($key) != $length) { - return false; - } - - $crypto = new DES(); - $crypto->setPassword($this->password, 'pbkdf1', 'md5', $salt, $iterationCount); - $key = $crypto->decrypt($key); - if ($key === false) { - return false; - } - return $this->_parseKey($key, self::PRIVATE_FORMAT_PKCS1); - default: - return false; - } - /* intended for keys for which OpenSSL's asn1parse returns the following: - - 0:d=0 hl=4 l= 290 cons: SEQUENCE - 4:d=1 hl=2 l= 13 cons: SEQUENCE - 6:d=2 hl=2 l= 9 prim: OBJECT :rsaEncryption - 17:d=2 hl=2 l= 0 prim: NULL - 19:d=1 hl=4 l= 271 prim: BIT STRING */ - $tag = ord($this->_string_shift($key)); // skip over the BIT STRING / OCTET STRING tag - $this->_decodeLength($key); // skip over the BIT STRING / OCTET STRING length - // "The initial octet shall encode, as an unsigned binary integer wtih bit 1 as the least significant bit, the number of - // unused bits in the final subsequent octet. The number shall be in the range zero to seven." - // -- http://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf (section 8.6.2.2) - if ($tag == self::ASN1_BITSTRING) { - $this->_string_shift($key); - } - if (ord($this->_string_shift($key)) != self::ASN1_SEQUENCE) { - return false; - } - if ($this->_decodeLength($key) != strlen($key)) { - return false; - } - $tag = ord($this->_string_shift($key)); - } - if ($tag != self::ASN1_INTEGER) { - return false; - } - - $length = $this->_decodeLength($key); - $temp = $this->_string_shift($key, $length); - if (strlen($temp) != 1 || ord($temp) > 2) { - $components['modulus'] = new BigInteger($temp, 256); - $this->_string_shift($key); // skip over self::ASN1_INTEGER - $length = $this->_decodeLength($key); - $components[$type == self::PUBLIC_FORMAT_PKCS1 ? 'publicExponent' : 'privateExponent'] = new BigInteger($this->_string_shift($key, $length), 256); - - return $components; - } - if (ord($this->_string_shift($key)) != self::ASN1_INTEGER) { - return false; - } - $length = $this->_decodeLength($key); - $components['modulus'] = new BigInteger($this->_string_shift($key, $length), 256); - $this->_string_shift($key); - $length = $this->_decodeLength($key); - $components['publicExponent'] = new BigInteger($this->_string_shift($key, $length), 256); - $this->_string_shift($key); - $length = $this->_decodeLength($key); - $components['privateExponent'] = new BigInteger($this->_string_shift($key, $length), 256); - $this->_string_shift($key); - $length = $this->_decodeLength($key); - $components['primes'] = array(1 => new BigInteger($this->_string_shift($key, $length), 256)); - $this->_string_shift($key); - $length = $this->_decodeLength($key); - $components['primes'][] = new BigInteger($this->_string_shift($key, $length), 256); - $this->_string_shift($key); - $length = $this->_decodeLength($key); - $components['exponents'] = array(1 => new BigInteger($this->_string_shift($key, $length), 256)); - $this->_string_shift($key); - $length = $this->_decodeLength($key); - $components['exponents'][] = new BigInteger($this->_string_shift($key, $length), 256); - $this->_string_shift($key); - $length = $this->_decodeLength($key); - $components['coefficients'] = array(2 => new BigInteger($this->_string_shift($key, $length), 256)); - - if (!empty($key)) { - if (ord($this->_string_shift($key)) != self::ASN1_SEQUENCE) { - return false; - } - $this->_decodeLength($key); - while (!empty($key)) { - if (ord($this->_string_shift($key)) != self::ASN1_SEQUENCE) { - return false; - } - $this->_decodeLength($key); - $key = substr($key, 1); - $length = $this->_decodeLength($key); - $components['primes'][] = new BigInteger($this->_string_shift($key, $length), 256); - $this->_string_shift($key); - $length = $this->_decodeLength($key); - $components['exponents'][] = new BigInteger($this->_string_shift($key, $length), 256); - $this->_string_shift($key); - $length = $this->_decodeLength($key); - $components['coefficients'][] = new BigInteger($this->_string_shift($key, $length), 256); - } - } - - return $components; - case self::PUBLIC_FORMAT_OPENSSH: - $parts = explode(' ', $key, 3); - - $key = isset($parts[1]) ? base64_decode($parts[1]) : false; - if ($key === false) { - return false; - } - - $comment = isset($parts[2]) ? $parts[2] : false; - - $cleanup = substr($key, 0, 11) == "\0\0\0\7ssh-rsa"; - - if (strlen($key) <= 4) { - return false; - } - extract(unpack('Nlength', $this->_string_shift($key, 4))); - $publicExponent = new BigInteger($this->_string_shift($key, $length), -256); - if (strlen($key) <= 4) { - return false; - } - extract(unpack('Nlength', $this->_string_shift($key, 4))); - $modulus = new BigInteger($this->_string_shift($key, $length), -256); - - if ($cleanup && strlen($key)) { - if (strlen($key) <= 4) { - return false; - } - extract(unpack('Nlength', $this->_string_shift($key, 4))); - $realModulus = new BigInteger($this->_string_shift($key, $length), -256); - return strlen($key) ? false : array( - 'modulus' => $realModulus, - 'publicExponent' => $modulus, - 'comment' => $comment - ); - } else { - return strlen($key) ? false : array( - 'modulus' => $modulus, - 'publicExponent' => $publicExponent, - 'comment' => $comment - ); - } - // http://www.w3.org/TR/xmldsig-core/#sec-RSAKeyValue - // http://en.wikipedia.org/wiki/XML_Signature - case self::PRIVATE_FORMAT_XML: - case self::PUBLIC_FORMAT_XML: - $this->components = array(); - - $xml = xml_parser_create('UTF-8'); - xml_set_object($xml, $this); - xml_set_element_handler($xml, '_start_element_handler', '_stop_element_handler'); - xml_set_character_data_handler($xml, '_data_handler'); - // add to account for "dangling" tags like ... that are sometimes added - if (!xml_parse($xml, '' . $key . '')) { - xml_parser_free($xml); - unset($xml); - return false; - } - - xml_parser_free($xml); - unset($xml); - - return isset($this->components['modulus']) && isset($this->components['publicExponent']) ? $this->components : false; - // from PuTTY's SSHPUBK.C - case self::PRIVATE_FORMAT_PUTTY: - $components = array(); - $key = preg_split('#\r\n|\r|\n#', $key); - $type = trim(preg_replace('#PuTTY-User-Key-File-2: (.+)#', '$1', $key[0])); - if ($type != 'ssh-rsa') { - return false; - } - $encryption = trim(preg_replace('#Encryption: (.+)#', '$1', $key[1])); - $comment = trim(preg_replace('#Comment: (.+)#', '$1', $key[2])); - - $publicLength = trim(preg_replace('#Public-Lines: (\d+)#', '$1', $key[3])); - $public = base64_decode(implode('', array_map('trim', array_slice($key, 4, $publicLength)))); - $public = substr($public, 11); - extract(unpack('Nlength', $this->_string_shift($public, 4))); - $components['publicExponent'] = new BigInteger($this->_string_shift($public, $length), -256); - extract(unpack('Nlength', $this->_string_shift($public, 4))); - $components['modulus'] = new BigInteger($this->_string_shift($public, $length), -256); - - $privateLength = trim(preg_replace('#Private-Lines: (\d+)#', '$1', $key[$publicLength + 4])); - $private = base64_decode(implode('', array_map('trim', array_slice($key, $publicLength + 5, $privateLength)))); - - switch ($encryption) { - case 'aes256-cbc': - $symkey = ''; - $sequence = 0; - while (strlen($symkey) < 32) { - $temp = pack('Na*', $sequence++, $this->password); - $symkey.= pack('H*', sha1($temp)); - } - $symkey = substr($symkey, 0, 32); - $crypto = new AES(); - } - - if ($encryption != 'none') { - $crypto->setKey($symkey); - $crypto->disablePadding(); - $private = $crypto->decrypt($private); - if ($private === false) { - return false; - } - } - - extract(unpack('Nlength', $this->_string_shift($private, 4))); - if (strlen($private) < $length) { - return false; - } - $components['privateExponent'] = new BigInteger($this->_string_shift($private, $length), -256); - extract(unpack('Nlength', $this->_string_shift($private, 4))); - if (strlen($private) < $length) { - return false; - } - $components['primes'] = array(1 => new BigInteger($this->_string_shift($private, $length), -256)); - extract(unpack('Nlength', $this->_string_shift($private, 4))); - if (strlen($private) < $length) { - return false; - } - $components['primes'][] = new BigInteger($this->_string_shift($private, $length), -256); - - $temp = $components['primes'][1]->subtract($this->one); - $components['exponents'] = array(1 => $components['publicExponent']->modInverse($temp)); - $temp = $components['primes'][2]->subtract($this->one); - $components['exponents'][] = $components['publicExponent']->modInverse($temp); - - extract(unpack('Nlength', $this->_string_shift($private, 4))); - if (strlen($private) < $length) { - return false; - } - $components['coefficients'] = array(2 => new BigInteger($this->_string_shift($private, $length), -256)); - - return $components; - case self::PRIVATE_FORMAT_OPENSSH: - $components = array(); - $decoded = $this->_extractBER($key); - $magic = $this->_string_shift($decoded, 15); - if ($magic !== "openssh-key-v1\0") { - return false; - } - $options = $this->_string_shift($decoded, 24); - // \0\0\0\4none = ciphername - // \0\0\0\4none = kdfname - // \0\0\0\0 = kdfoptions - // \0\0\0\1 = numkeys - if ($options != "\0\0\0\4none\0\0\0\4none\0\0\0\0\0\0\0\1") { - return false; - } - extract(unpack('Nlength', $this->_string_shift($decoded, 4))); - if (strlen($decoded) < $length) { - return false; - } - $publicKey = $this->_string_shift($decoded, $length); - extract(unpack('Nlength', $this->_string_shift($decoded, 4))); - if (strlen($decoded) < $length) { - return false; - } - $paddedKey = $this->_string_shift($decoded, $length); - - if ($this->_string_shift($publicKey, 11) !== "\0\0\0\7ssh-rsa") { - return false; - } - - $checkint1 = $this->_string_shift($paddedKey, 4); - $checkint2 = $this->_string_shift($paddedKey, 4); - if (strlen($checkint1) != 4 || $checkint1 !== $checkint2) { - return false; - } - - if ($this->_string_shift($paddedKey, 11) !== "\0\0\0\7ssh-rsa") { - return false; - } - - $values = array( - &$components['modulus'], - &$components['publicExponent'], - &$components['privateExponent'], - &$components['coefficients'][2], - &$components['primes'][1], - &$components['primes'][2] - ); - - foreach ($values as &$value) { - extract(unpack('Nlength', $this->_string_shift($paddedKey, 4))); - if (strlen($paddedKey) < $length) { - return false; - } - $value = new BigInteger($this->_string_shift($paddedKey, $length), -256); - } - - extract(unpack('Nlength', $this->_string_shift($paddedKey, 4))); - if (strlen($paddedKey) < $length) { - return false; - } - $components['comment'] = $this->_string_shift($decoded, $length); - - $temp = $components['primes'][1]->subtract($this->one); - $components['exponents'] = array(1 => $components['publicExponent']->modInverse($temp)); - $temp = $components['primes'][2]->subtract($this->one); - $components['exponents'][] = $components['publicExponent']->modInverse($temp); - - return $components; - } - - return false; - } - - /** - * Returns the key size - * - * More specifically, this returns the size of the modulo in bits. - * - * @access public - * @return int - */ - function getSize() - { - return !isset($this->modulus) ? 0 : strlen($this->modulus->toBits()); - } - - /** - * Start Element Handler - * - * Called by xml_set_element_handler() - * - * @access private - * @param resource $parser - * @param string $name - * @param array $attribs - */ - function _start_element_handler($parser, $name, $attribs) - { - //$name = strtoupper($name); - switch ($name) { - case 'MODULUS': - $this->current = &$this->components['modulus']; - break; - case 'EXPONENT': - $this->current = &$this->components['publicExponent']; - break; - case 'P': - $this->current = &$this->components['primes'][1]; - break; - case 'Q': - $this->current = &$this->components['primes'][2]; - break; - case 'DP': - $this->current = &$this->components['exponents'][1]; - break; - case 'DQ': - $this->current = &$this->components['exponents'][2]; - break; - case 'INVERSEQ': - $this->current = &$this->components['coefficients'][2]; - break; - case 'D': - $this->current = &$this->components['privateExponent']; - } - $this->current = ''; - } - - /** - * Stop Element Handler - * - * Called by xml_set_element_handler() - * - * @access private - * @param resource $parser - * @param string $name - */ - function _stop_element_handler($parser, $name) - { - if (isset($this->current)) { - $this->current = new BigInteger(base64_decode($this->current), 256); - unset($this->current); - } - } - - /** - * Data Handler - * - * Called by xml_set_character_data_handler() - * - * @access private - * @param resource $parser - * @param string $data - */ - function _data_handler($parser, $data) - { - if (!isset($this->current) || is_object($this->current)) { - return; - } - $this->current.= trim($data); - } - - /** - * Loads a public or private key - * - * Returns true on success and false on failure (ie. an incorrect password was provided or the key was malformed) - * - * @access public - * @param string|RSA|array $key - * @param bool|int $type optional - * @return bool - */ - function loadKey($key, $type = false) - { - if ($key instanceof RSA) { - $this->privateKeyFormat = $key->privateKeyFormat; - $this->publicKeyFormat = $key->publicKeyFormat; - $this->k = $key->k; - $this->hLen = $key->hLen; - $this->sLen = $key->sLen; - $this->mgfHLen = $key->mgfHLen; - $this->encryptionMode = $key->encryptionMode; - $this->signatureMode = $key->signatureMode; - $this->password = $key->password; - $this->configFile = $key->configFile; - $this->comment = $key->comment; - - if (is_object($key->hash)) { - $this->hash = new Hash($key->hash->getHash()); - } - if (is_object($key->mgfHash)) { - $this->mgfHash = new Hash($key->mgfHash->getHash()); - } - - if (is_object($key->modulus)) { - $this->modulus = $key->modulus->copy(); - } - if (is_object($key->exponent)) { - $this->exponent = $key->exponent->copy(); - } - if (is_object($key->publicExponent)) { - $this->publicExponent = $key->publicExponent->copy(); - } - - $this->primes = array(); - $this->exponents = array(); - $this->coefficients = array(); - - foreach ($this->primes as $prime) { - $this->primes[] = $prime->copy(); - } - foreach ($this->exponents as $exponent) { - $this->exponents[] = $exponent->copy(); - } - foreach ($this->coefficients as $coefficient) { - $this->coefficients[] = $coefficient->copy(); - } - - return true; - } - - if ($type === false) { - $types = array( - self::PUBLIC_FORMAT_RAW, - self::PRIVATE_FORMAT_PKCS1, - self::PRIVATE_FORMAT_XML, - self::PRIVATE_FORMAT_PUTTY, - self::PUBLIC_FORMAT_OPENSSH, - self::PRIVATE_FORMAT_OPENSSH - ); - foreach ($types as $type) { - $components = $this->_parseKey($key, $type); - if ($components !== false) { - break; - } - } - } else { - $components = $this->_parseKey($key, $type); - } - - if ($components === false) { - $this->comment = null; - $this->modulus = null; - $this->k = null; - $this->exponent = null; - $this->primes = null; - $this->exponents = null; - $this->coefficients = null; - $this->publicExponent = null; - - return false; - } - - if (isset($components['comment']) && $components['comment'] !== false) { - $this->comment = $components['comment']; - } - $this->modulus = $components['modulus']; - $this->k = strlen($this->modulus->toBytes()); - $this->exponent = isset($components['privateExponent']) ? $components['privateExponent'] : $components['publicExponent']; - if (isset($components['primes'])) { - $this->primes = $components['primes']; - $this->exponents = $components['exponents']; - $this->coefficients = $components['coefficients']; - $this->publicExponent = $components['publicExponent']; - } else { - $this->primes = array(); - $this->exponents = array(); - $this->coefficients = array(); - $this->publicExponent = false; - } - - switch ($type) { - case self::PUBLIC_FORMAT_OPENSSH: - case self::PUBLIC_FORMAT_RAW: - $this->setPublicKey(); - break; - case self::PRIVATE_FORMAT_PKCS1: - switch (true) { - case strpos($key, '-BEGIN PUBLIC KEY-') !== false: - case strpos($key, '-BEGIN RSA PUBLIC KEY-') !== false: - $this->setPublicKey(); - } - } - - return true; - } - - /** - * Sets the password - * - * Private keys can be encrypted with a password. To unset the password, pass in the empty string or false. - * Or rather, pass in $password such that empty($password) && !is_string($password) is true. - * - * @see self::createKey() - * @see self::loadKey() - * @access public - * @param string $password - */ - function setPassword($password = false) - { - $this->password = $password; - } - - /** - * Defines the public key - * - * Some private key formats define the public exponent and some don't. Those that don't define it are problematic when - * used in certain contexts. For example, in SSH-2, RSA authentication works by sending the public key along with a - * message signed by the private key to the server. The SSH-2 server looks the public key up in an index of public keys - * and if it's present then proceeds to verify the signature. Problem is, if your private key doesn't include the public - * exponent this won't work unless you manually add the public exponent. phpseclib tries to guess if the key being used - * is the public key but in the event that it guesses incorrectly you might still want to explicitly set the key as being - * public. - * - * Do note that when a new key is loaded the index will be cleared. - * - * Returns true on success, false on failure - * - * @see self::getPublicKey() - * @access public - * @param string $key optional - * @param int $type optional - * @return bool - */ - function setPublicKey($key = false, $type = false) - { - // if a public key has already been loaded return false - if (!empty($this->publicExponent)) { - return false; - } - - if ($key === false && !empty($this->modulus)) { - $this->publicExponent = $this->exponent; - return true; - } - - if ($type === false) { - $types = array( - self::PUBLIC_FORMAT_RAW, - self::PUBLIC_FORMAT_PKCS1, - self::PUBLIC_FORMAT_XML, - self::PUBLIC_FORMAT_OPENSSH - ); - foreach ($types as $type) { - $components = $this->_parseKey($key, $type); - if ($components !== false) { - break; - } - } - } else { - $components = $this->_parseKey($key, $type); - } - - if ($components === false) { - return false; - } - - if (empty($this->modulus) || !$this->modulus->equals($components['modulus'])) { - $this->modulus = $components['modulus']; - $this->exponent = $this->publicExponent = $components['publicExponent']; - return true; - } - - $this->publicExponent = $components['publicExponent']; - - return true; - } - - /** - * Defines the private key - * - * If phpseclib guessed a private key was a public key and loaded it as such it might be desirable to force - * phpseclib to treat the key as a private key. This function will do that. - * - * Do note that when a new key is loaded the index will be cleared. - * - * Returns true on success, false on failure - * - * @see self::getPublicKey() - * @access public - * @param string $key optional - * @param int $type optional - * @return bool - */ - function setPrivateKey($key = false, $type = false) - { - if ($key === false && !empty($this->publicExponent)) { - $this->publicExponent = false; - return true; - } - - $rsa = new RSA(); - if (!$rsa->loadKey($key, $type)) { - return false; - } - $rsa->publicExponent = false; - - // don't overwrite the old key if the new key is invalid - $this->loadKey($rsa); - return true; - } - - /** - * Returns the public key - * - * The public key is only returned under two circumstances - if the private key had the public key embedded within it - * or if the public key was set via setPublicKey(). If the currently loaded key is supposed to be the public key this - * function won't return it since this library, for the most part, doesn't distinguish between public and private keys. - * - * @see self::getPublicKey() - * @access public - * @param int $type optional - */ - function getPublicKey($type = self::PUBLIC_FORMAT_PKCS8) - { - if (empty($this->modulus) || empty($this->publicExponent)) { - return false; - } - - $oldFormat = $this->publicKeyFormat; - $this->publicKeyFormat = $type; - $temp = $this->_convertPublicKey($this->modulus, $this->publicExponent); - $this->publicKeyFormat = $oldFormat; - return $temp; - } - - /** - * Returns the public key's fingerprint - * - * The public key's fingerprint is returned, which is equivalent to running `ssh-keygen -lf rsa.pub`. If there is - * no public key currently loaded, false is returned. - * Example output (md5): "c1:b1:30:29:d7:b8:de:6c:97:77:10:d7:46:41:63:87" (as specified by RFC 4716) - * - * @access public - * @param string $algorithm The hashing algorithm to be used. Valid options are 'md5' and 'sha256'. False is returned - * for invalid values. - * @return mixed - */ - function getPublicKeyFingerprint($algorithm = 'md5') - { - if (empty($this->modulus) || empty($this->publicExponent)) { - return false; - } - - $modulus = $this->modulus->toBytes(true); - $publicExponent = $this->publicExponent->toBytes(true); - - $RSAPublicKey = pack('Na*Na*Na*', strlen('ssh-rsa'), 'ssh-rsa', strlen($publicExponent), $publicExponent, strlen($modulus), $modulus); - - switch ($algorithm) { - case 'sha256': - $hash = new Hash('sha256'); - $base = base64_encode($hash->hash($RSAPublicKey)); - return substr($base, 0, strlen($base) - 1); - case 'md5': - return substr(chunk_split(md5($RSAPublicKey), 2, ':'), 0, -1); - default: - return false; - } - } - - /** - * Returns the private key - * - * The private key is only returned if the currently loaded key contains the constituent prime numbers. - * - * @see self::getPublicKey() - * @access public - * @param int $type optional - * @return mixed - */ - function getPrivateKey($type = self::PUBLIC_FORMAT_PKCS1) - { - if (empty($this->primes)) { - return false; - } - - $oldFormat = $this->privateKeyFormat; - $this->privateKeyFormat = $type; - $temp = $this->_convertPrivateKey($this->modulus, $this->publicExponent, $this->exponent, $this->primes, $this->exponents, $this->coefficients); - $this->privateKeyFormat = $oldFormat; - return $temp; - } - - /** - * Returns a minimalistic private key - * - * Returns the private key without the prime number constituants. Structurally identical to a public key that - * hasn't been set as the public key - * - * @see self::getPrivateKey() - * @access private - * @param int $mode optional - */ - function _getPrivatePublicKey($mode = self::PUBLIC_FORMAT_PKCS8) - { - if (empty($this->modulus) || empty($this->exponent)) { - return false; - } - - $oldFormat = $this->publicKeyFormat; - $this->publicKeyFormat = $mode; - $temp = $this->_convertPublicKey($this->modulus, $this->exponent); - $this->publicKeyFormat = $oldFormat; - return $temp; - } - - /** - * __toString() magic method - * - * @access public - * @return string - */ - function __toString() - { - $key = $this->getPrivateKey($this->privateKeyFormat); - if ($key !== false) { - return $key; - } - $key = $this->_getPrivatePublicKey($this->publicKeyFormat); - return $key !== false ? $key : ''; - } - - /** - * __clone() magic method - * - * @access public - * @return Crypt_RSA - */ - function __clone() - { - $key = new RSA(); - $key->loadKey($this); - return $key; - } - - /** - * Generates the smallest and largest numbers requiring $bits bits - * - * @access private - * @param int $bits - * @return array - */ - function _generateMinMax($bits) - { - $bytes = $bits >> 3; - $min = str_repeat(chr(0), $bytes); - $max = str_repeat(chr(0xFF), $bytes); - $msb = $bits & 7; - if ($msb) { - $min = chr(1 << ($msb - 1)) . $min; - $max = chr((1 << $msb) - 1) . $max; - } else { - $min[0] = chr(0x80); - } - - return array( - 'min' => new BigInteger($min, 256), - 'max' => new BigInteger($max, 256) - ); - } - - /** - * DER-decode the length - * - * DER supports lengths up to (2**8)**127, however, we'll only support lengths up to (2**8)**4. See - * {@link http://itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#p=13 X.690 paragraph 8.1.3} for more information. - * - * @access private - * @param string $string - * @return int - */ - function _decodeLength(&$string) - { - $length = ord($this->_string_shift($string)); - if ($length & 0x80) { // definite length, long form - $length&= 0x7F; - $temp = $this->_string_shift($string, $length); - list(, $length) = unpack('N', substr(str_pad($temp, 4, chr(0), STR_PAD_LEFT), -4)); - } - return $length; - } - - /** - * DER-encode the length - * - * DER supports lengths up to (2**8)**127, however, we'll only support lengths up to (2**8)**4. See - * {@link http://itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#p=13 X.690 paragraph 8.1.3} for more information. - * - * @access private - * @param int $length - * @return string - */ - function _encodeLength($length) - { - if ($length <= 0x7F) { - return chr($length); - } - - $temp = ltrim(pack('N', $length), chr(0)); - return pack('Ca*', 0x80 | strlen($temp), $temp); - } - - /** - * String Shift - * - * Inspired by array_shift - * - * @param string $string - * @param int $index - * @return string - * @access private - */ - function _string_shift(&$string, $index = 1) - { - $substr = substr($string, 0, $index); - $string = substr($string, $index); - return $substr; - } - - /** - * Determines the private key format - * - * @see self::createKey() - * @access public - * @param int $format - */ - function setPrivateKeyFormat($format) - { - $this->privateKeyFormat = $format; - } - - /** - * Determines the public key format - * - * @see self::createKey() - * @access public - * @param int $format - */ - function setPublicKeyFormat($format) - { - $this->publicKeyFormat = $format; - } - - /** - * Determines which hashing function should be used - * - * Used with signature production / verification and (if the encryption mode is self::ENCRYPTION_OAEP) encryption and - * decryption. If $hash isn't supported, sha1 is used. - * - * @access public - * @param string $hash - */ - function setHash($hash) - { - // \phpseclib\Crypt\Hash supports algorithms that PKCS#1 doesn't support. md5-96 and sha1-96, for example. - switch ($hash) { - case 'md2': - case 'md5': - case 'sha1': - case 'sha256': - case 'sha384': - case 'sha512': - $this->hash = new Hash($hash); - $this->hashName = $hash; - break; - default: - $this->hash = new Hash('sha1'); - $this->hashName = 'sha1'; - } - $this->hLen = $this->hash->getLength(); - } - - /** - * Determines which hashing function should be used for the mask generation function - * - * The mask generation function is used by self::ENCRYPTION_OAEP and self::SIGNATURE_PSS and although it's - * best if Hash and MGFHash are set to the same thing this is not a requirement. - * - * @access public - * @param string $hash - */ - function setMGFHash($hash) - { - // \phpseclib\Crypt\Hash supports algorithms that PKCS#1 doesn't support. md5-96 and sha1-96, for example. - switch ($hash) { - case 'md2': - case 'md5': - case 'sha1': - case 'sha256': - case 'sha384': - case 'sha512': - $this->mgfHash = new Hash($hash); - break; - default: - $this->mgfHash = new Hash('sha1'); - } - $this->mgfHLen = $this->mgfHash->getLength(); - } - - /** - * Determines the salt length - * - * To quote from {@link http://tools.ietf.org/html/rfc3447#page-38 RFC3447#page-38}: - * - * Typical salt lengths in octets are hLen (the length of the output - * of the hash function Hash) and 0. - * - * @access public - * @param int $sLen - */ - function setSaltLength($sLen) - { - $this->sLen = $sLen; - } - - /** - * Integer-to-Octet-String primitive - * - * See {@link http://tools.ietf.org/html/rfc3447#section-4.1 RFC3447#section-4.1}. - * - * @access private - * @param \phpseclib\Math\BigInteger $x - * @param int $xLen - * @return string - */ - function _i2osp($x, $xLen) - { - $x = $x->toBytes(); - if (strlen($x) > $xLen) { - user_error('Integer too large'); - return false; - } - return str_pad($x, $xLen, chr(0), STR_PAD_LEFT); - } - - /** - * Octet-String-to-Integer primitive - * - * See {@link http://tools.ietf.org/html/rfc3447#section-4.2 RFC3447#section-4.2}. - * - * @access private - * @param int|string|resource $x - * @return \phpseclib\Math\BigInteger - */ - function _os2ip($x) - { - return new BigInteger($x, 256); - } - - /** - * Exponentiate with or without Chinese Remainder Theorem - * - * See {@link http://tools.ietf.org/html/rfc3447#section-5.1.1 RFC3447#section-5.1.2}. - * - * @access private - * @param \phpseclib\Math\BigInteger $x - * @return \phpseclib\Math\BigInteger - */ - function _exponentiate($x) - { - switch (true) { - case empty($this->primes): - case $this->primes[1]->equals($this->zero): - case empty($this->coefficients): - case $this->coefficients[2]->equals($this->zero): - case empty($this->exponents): - case $this->exponents[1]->equals($this->zero): - return $x->modPow($this->exponent, $this->modulus); - } - - $num_primes = count($this->primes); - - if (defined('CRYPT_RSA_DISABLE_BLINDING')) { - $m_i = array( - 1 => $x->modPow($this->exponents[1], $this->primes[1]), - 2 => $x->modPow($this->exponents[2], $this->primes[2]) - ); - $h = $m_i[1]->subtract($m_i[2]); - $h = $h->multiply($this->coefficients[2]); - list(, $h) = $h->divide($this->primes[1]); - $m = $m_i[2]->add($h->multiply($this->primes[2])); - - $r = $this->primes[1]; - for ($i = 3; $i <= $num_primes; $i++) { - $m_i = $x->modPow($this->exponents[$i], $this->primes[$i]); - - $r = $r->multiply($this->primes[$i - 1]); - - $h = $m_i->subtract($m); - $h = $h->multiply($this->coefficients[$i]); - list(, $h) = $h->divide($this->primes[$i]); - - $m = $m->add($r->multiply($h)); - } - } else { - $smallest = $this->primes[1]; - for ($i = 2; $i <= $num_primes; $i++) { - if ($smallest->compare($this->primes[$i]) > 0) { - $smallest = $this->primes[$i]; - } - } - - $one = new BigInteger(1); - - $r = $one->random($one, $smallest->subtract($one)); - - $m_i = array( - 1 => $this->_blind($x, $r, 1), - 2 => $this->_blind($x, $r, 2) - ); - $h = $m_i[1]->subtract($m_i[2]); - $h = $h->multiply($this->coefficients[2]); - list(, $h) = $h->divide($this->primes[1]); - $m = $m_i[2]->add($h->multiply($this->primes[2])); - - $r = $this->primes[1]; - for ($i = 3; $i <= $num_primes; $i++) { - $m_i = $this->_blind($x, $r, $i); - - $r = $r->multiply($this->primes[$i - 1]); - - $h = $m_i->subtract($m); - $h = $h->multiply($this->coefficients[$i]); - list(, $h) = $h->divide($this->primes[$i]); - - $m = $m->add($r->multiply($h)); - } - } - - return $m; - } - - /** - * Performs RSA Blinding - * - * Protects against timing attacks by employing RSA Blinding. - * Returns $x->modPow($this->exponents[$i], $this->primes[$i]) - * - * @access private - * @param \phpseclib\Math\BigInteger $x - * @param \phpseclib\Math\BigInteger $r - * @param int $i - * @return \phpseclib\Math\BigInteger - */ - function _blind($x, $r, $i) - { - $x = $x->multiply($r->modPow($this->publicExponent, $this->primes[$i])); - $x = $x->modPow($this->exponents[$i], $this->primes[$i]); - - $r = $r->modInverse($this->primes[$i]); - $x = $x->multiply($r); - list(, $x) = $x->divide($this->primes[$i]); - - return $x; - } - - /** - * Performs blinded RSA equality testing - * - * Protects against a particular type of timing attack described. - * - * See {@link http://codahale.com/a-lesson-in-timing-attacks/ A Lesson In Timing Attacks (or, Don't use MessageDigest.isEquals)} - * - * Thanks for the heads up singpolyma! - * - * @access private - * @param string $x - * @param string $y - * @return bool - */ - function _equals($x, $y) - { - if (function_exists('hash_equals')) { - return hash_equals($x, $y); - } - - if (strlen($x) != strlen($y)) { - return false; - } - - $result = "\0"; - $x^= $y; - for ($i = 0; $i < strlen($x); $i++) { - $result|= $x[$i]; - } - - return $result === "\0"; - } - - /** - * RSAEP - * - * See {@link http://tools.ietf.org/html/rfc3447#section-5.1.1 RFC3447#section-5.1.1}. - * - * @access private - * @param \phpseclib\Math\BigInteger $m - * @return \phpseclib\Math\BigInteger - */ - function _rsaep($m) - { - if ($m->compare($this->zero) < 0 || $m->compare($this->modulus) > 0) { - user_error('Message representative out of range'); - return false; - } - return $this->_exponentiate($m); - } - - /** - * RSADP - * - * See {@link http://tools.ietf.org/html/rfc3447#section-5.1.2 RFC3447#section-5.1.2}. - * - * @access private - * @param \phpseclib\Math\BigInteger $c - * @return \phpseclib\Math\BigInteger - */ - function _rsadp($c) - { - if ($c->compare($this->zero) < 0 || $c->compare($this->modulus) > 0) { - user_error('Ciphertext representative out of range'); - return false; - } - return $this->_exponentiate($c); - } - - /** - * RSASP1 - * - * See {@link http://tools.ietf.org/html/rfc3447#section-5.2.1 RFC3447#section-5.2.1}. - * - * @access private - * @param \phpseclib\Math\BigInteger $m - * @return \phpseclib\Math\BigInteger - */ - function _rsasp1($m) - { - if ($m->compare($this->zero) < 0 || $m->compare($this->modulus) > 0) { - user_error('Message representative out of range'); - return false; - } - return $this->_exponentiate($m); - } - - /** - * RSAVP1 - * - * See {@link http://tools.ietf.org/html/rfc3447#section-5.2.2 RFC3447#section-5.2.2}. - * - * @access private - * @param \phpseclib\Math\BigInteger $s - * @return \phpseclib\Math\BigInteger - */ - function _rsavp1($s) - { - if ($s->compare($this->zero) < 0 || $s->compare($this->modulus) > 0) { - user_error('Signature representative out of range'); - return false; - } - return $this->_exponentiate($s); - } - - /** - * MGF1 - * - * See {@link http://tools.ietf.org/html/rfc3447#appendix-B.2.1 RFC3447#appendix-B.2.1}. - * - * @access private - * @param string $mgfSeed - * @param int $maskLen - * @return string - */ - function _mgf1($mgfSeed, $maskLen) - { - // if $maskLen would yield strings larger than 4GB, PKCS#1 suggests a "Mask too long" error be output. - - $t = ''; - $count = ceil($maskLen / $this->mgfHLen); - for ($i = 0; $i < $count; $i++) { - $c = pack('N', $i); - $t.= $this->mgfHash->hash($mgfSeed . $c); - } - - return substr($t, 0, $maskLen); - } - - /** - * RSAES-OAEP-ENCRYPT - * - * See {@link http://tools.ietf.org/html/rfc3447#section-7.1.1 RFC3447#section-7.1.1} and - * {http://en.wikipedia.org/wiki/Optimal_Asymmetric_Encryption_Padding OAES}. - * - * @access private - * @param string $m - * @param string $l - * @return string - */ - function _rsaes_oaep_encrypt($m, $l = '') - { - $mLen = strlen($m); - - // Length checking - - // if $l is larger than two million terrabytes and you're using sha1, PKCS#1 suggests a "Label too long" error - // be output. - - if ($mLen > $this->k - 2 * $this->hLen - 2) { - user_error('Message too long'); - return false; - } - - // EME-OAEP encoding - - $lHash = $this->hash->hash($l); - $ps = str_repeat(chr(0), $this->k - $mLen - 2 * $this->hLen - 2); - $db = $lHash . $ps . chr(1) . $m; - $seed = Random::string($this->hLen); - $dbMask = $this->_mgf1($seed, $this->k - $this->hLen - 1); - $maskedDB = $db ^ $dbMask; - $seedMask = $this->_mgf1($maskedDB, $this->hLen); - $maskedSeed = $seed ^ $seedMask; - $em = chr(0) . $maskedSeed . $maskedDB; - - // RSA encryption - - $m = $this->_os2ip($em); - $c = $this->_rsaep($m); - $c = $this->_i2osp($c, $this->k); - - // Output the ciphertext C - - return $c; - } - - /** - * RSAES-OAEP-DECRYPT - * - * See {@link http://tools.ietf.org/html/rfc3447#section-7.1.2 RFC3447#section-7.1.2}. The fact that the error - * messages aren't distinguishable from one another hinders debugging, but, to quote from RFC3447#section-7.1.2: - * - * Note. Care must be taken to ensure that an opponent cannot - * distinguish the different error conditions in Step 3.g, whether by - * error message or timing, or, more generally, learn partial - * information about the encoded message EM. Otherwise an opponent may - * be able to obtain useful information about the decryption of the - * ciphertext C, leading to a chosen-ciphertext attack such as the one - * observed by Manger [36]. - * - * As for $l... to quote from {@link http://tools.ietf.org/html/rfc3447#page-17 RFC3447#page-17}: - * - * Both the encryption and the decryption operations of RSAES-OAEP take - * the value of a label L as input. In this version of PKCS #1, L is - * the empty string; other uses of the label are outside the scope of - * this document. - * - * @access private - * @param string $c - * @param string $l - * @return string - */ - function _rsaes_oaep_decrypt($c, $l = '') - { - // Length checking - - // if $l is larger than two million terrabytes and you're using sha1, PKCS#1 suggests a "Label too long" error - // be output. - - if (strlen($c) != $this->k || $this->k < 2 * $this->hLen + 2) { - user_error('Decryption error'); - return false; - } - - // RSA decryption - - $c = $this->_os2ip($c); - $m = $this->_rsadp($c); - if ($m === false) { - user_error('Decryption error'); - return false; - } - $em = $this->_i2osp($m, $this->k); - - // EME-OAEP decoding - - $lHash = $this->hash->hash($l); - $y = ord($em[0]); - $maskedSeed = substr($em, 1, $this->hLen); - $maskedDB = substr($em, $this->hLen + 1); - $seedMask = $this->_mgf1($maskedDB, $this->hLen); - $seed = $maskedSeed ^ $seedMask; - $dbMask = $this->_mgf1($seed, $this->k - $this->hLen - 1); - $db = $maskedDB ^ $dbMask; - $lHash2 = substr($db, 0, $this->hLen); - $m = substr($db, $this->hLen); - $hashesMatch = $this->_equals($lHash, $lHash2); - $leadingZeros = 1; - $patternMatch = 0; - $offset = 0; - for ($i = 0; $i < strlen($m); $i++) { - $patternMatch|= $leadingZeros & ($m[$i] === "\1"); - $leadingZeros&= $m[$i] === "\0"; - $offset+= $patternMatch ? 0 : 1; - } - - // we do & instead of && to avoid https://en.wikipedia.org/wiki/Short-circuit_evaluation - // to protect against timing attacks - if (!$hashesMatch & !$patternMatch) { - user_error('Decryption error'); - return false; - } - - // Output the message M - - return substr($m, $offset + 1); - } - - /** - * Raw Encryption / Decryption - * - * Doesn't use padding and is not recommended. - * - * @access private - * @param string $m - * @return string - */ - function _raw_encrypt($m) - { - $temp = $this->_os2ip($m); - $temp = $this->_rsaep($temp); - return $this->_i2osp($temp, $this->k); - } - - /** - * RSAES-PKCS1-V1_5-ENCRYPT - * - * See {@link http://tools.ietf.org/html/rfc3447#section-7.2.1 RFC3447#section-7.2.1}. - * - * @access private - * @param string $m - * @return string - */ - function _rsaes_pkcs1_v1_5_encrypt($m) - { - $mLen = strlen($m); - - // Length checking - - if ($mLen > $this->k - 11) { - user_error('Message too long'); - return false; - } - - // EME-PKCS1-v1_5 encoding - - $psLen = $this->k - $mLen - 3; - $ps = ''; - while (strlen($ps) != $psLen) { - $temp = Random::string($psLen - strlen($ps)); - $temp = str_replace("\x00", '', $temp); - $ps.= $temp; - } - $type = 2; - // see the comments of _rsaes_pkcs1_v1_5_decrypt() to understand why this is being done - if (defined('CRYPT_RSA_PKCS15_COMPAT') && (!isset($this->publicExponent) || $this->exponent !== $this->publicExponent)) { - $type = 1; - // "The padding string PS shall consist of k-3-||D|| octets. ... for block type 01, they shall have value FF" - $ps = str_repeat("\xFF", $psLen); - } - $em = chr(0) . chr($type) . $ps . chr(0) . $m; - - // RSA encryption - $m = $this->_os2ip($em); - $c = $this->_rsaep($m); - $c = $this->_i2osp($c, $this->k); - - // Output the ciphertext C - - return $c; - } - - /** - * RSAES-PKCS1-V1_5-DECRYPT - * - * See {@link http://tools.ietf.org/html/rfc3447#section-7.2.2 RFC3447#section-7.2.2}. - * - * For compatibility purposes, this function departs slightly from the description given in RFC3447. - * The reason being that RFC2313#section-8.1 (PKCS#1 v1.5) states that ciphertext's encrypted by the - * private key should have the second byte set to either 0 or 1 and that ciphertext's encrypted by the - * public key should have the second byte set to 2. In RFC3447 (PKCS#1 v2.1), the second byte is supposed - * to be 2 regardless of which key is used. For compatibility purposes, we'll just check to make sure the - * second byte is 2 or less. If it is, we'll accept the decrypted string as valid. - * - * As a consequence of this, a private key encrypted ciphertext produced with \phpseclib\Crypt\RSA may not decrypt - * with a strictly PKCS#1 v1.5 compliant RSA implementation. Public key encrypted ciphertext's should but - * not private key encrypted ciphertext's. - * - * @access private - * @param string $c - * @return string - */ - function _rsaes_pkcs1_v1_5_decrypt($c) - { - // Length checking - - if (strlen($c) != $this->k) { // or if k < 11 - user_error('Decryption error'); - return false; - } - - // RSA decryption - - $c = $this->_os2ip($c); - $m = $this->_rsadp($c); - - if ($m === false) { - user_error('Decryption error'); - return false; - } - $em = $this->_i2osp($m, $this->k); - - // EME-PKCS1-v1_5 decoding - - if (ord($em[0]) != 0 || ord($em[1]) > 2) { - user_error('Decryption error'); - return false; - } - - $ps = substr($em, 2, strpos($em, chr(0), 2) - 2); - $m = substr($em, strlen($ps) + 3); - - if (strlen($ps) < 8) { - user_error('Decryption error'); - return false; - } - - // Output M - - return $m; - } - - /** - * EMSA-PSS-ENCODE - * - * See {@link http://tools.ietf.org/html/rfc3447#section-9.1.1 RFC3447#section-9.1.1}. - * - * @access private - * @param string $m - * @param int $emBits - */ - function _emsa_pss_encode($m, $emBits) - { - // if $m is larger than two million terrabytes and you're using sha1, PKCS#1 suggests a "Label too long" error - // be output. - - $emLen = ($emBits + 1) >> 3; // ie. ceil($emBits / 8) - $sLen = $this->sLen !== null ? $this->sLen : $this->hLen; - - $mHash = $this->hash->hash($m); - if ($emLen < $this->hLen + $sLen + 2) { - user_error('Encoding error'); - return false; - } - - $salt = Random::string($sLen); - $m2 = "\0\0\0\0\0\0\0\0" . $mHash . $salt; - $h = $this->hash->hash($m2); - $ps = str_repeat(chr(0), $emLen - $sLen - $this->hLen - 2); - $db = $ps . chr(1) . $salt; - $dbMask = $this->_mgf1($h, $emLen - $this->hLen - 1); - $maskedDB = $db ^ $dbMask; - $maskedDB[0] = ~chr(0xFF << ($emBits & 7)) & $maskedDB[0]; - $em = $maskedDB . $h . chr(0xBC); - - return $em; - } - - /** - * EMSA-PSS-VERIFY - * - * See {@link http://tools.ietf.org/html/rfc3447#section-9.1.2 RFC3447#section-9.1.2}. - * - * @access private - * @param string $m - * @param string $em - * @param int $emBits - * @return string - */ - function _emsa_pss_verify($m, $em, $emBits) - { - // if $m is larger than two million terrabytes and you're using sha1, PKCS#1 suggests a "Label too long" error - // be output. - - $emLen = ($emBits + 7) >> 3; // ie. ceil($emBits / 8); - $sLen = $this->sLen !== null ? $this->sLen : $this->hLen; - - $mHash = $this->hash->hash($m); - if ($emLen < $this->hLen + $sLen + 2) { - return false; - } - - if ($em[strlen($em) - 1] != chr(0xBC)) { - return false; - } - - $maskedDB = substr($em, 0, -$this->hLen - 1); - $h = substr($em, -$this->hLen - 1, $this->hLen); - $temp = chr(0xFF << ($emBits & 7)); - if ((~$maskedDB[0] & $temp) != $temp) { - return false; - } - $dbMask = $this->_mgf1($h, $emLen - $this->hLen - 1); - $db = $maskedDB ^ $dbMask; - $db[0] = ~chr(0xFF << ($emBits & 7)) & $db[0]; - $temp = $emLen - $this->hLen - $sLen - 2; - if (substr($db, 0, $temp) != str_repeat(chr(0), $temp) || ord($db[$temp]) != 1) { - return false; - } - $salt = substr($db, $temp + 1); // should be $sLen long - $m2 = "\0\0\0\0\0\0\0\0" . $mHash . $salt; - $h2 = $this->hash->hash($m2); - return $this->_equals($h, $h2); - } - - /** - * RSASSA-PSS-SIGN - * - * See {@link http://tools.ietf.org/html/rfc3447#section-8.1.1 RFC3447#section-8.1.1}. - * - * @access private - * @param string $m - * @return string - */ - function _rsassa_pss_sign($m) - { - // EMSA-PSS encoding - - $em = $this->_emsa_pss_encode($m, 8 * $this->k - 1); - - // RSA signature - - $m = $this->_os2ip($em); - $s = $this->_rsasp1($m); - $s = $this->_i2osp($s, $this->k); - - // Output the signature S - - return $s; - } - - /** - * RSASSA-PSS-VERIFY - * - * See {@link http://tools.ietf.org/html/rfc3447#section-8.1.2 RFC3447#section-8.1.2}. - * - * @access private - * @param string $m - * @param string $s - * @return string - */ - function _rsassa_pss_verify($m, $s) - { - // Length checking - - if (strlen($s) != $this->k) { - user_error('Invalid signature'); - return false; - } - - // RSA verification - - $modBits = strlen($this->modulus->toBits()); - - $s2 = $this->_os2ip($s); - $m2 = $this->_rsavp1($s2); - if ($m2 === false) { - user_error('Invalid signature'); - return false; - } - $em = $this->_i2osp($m2, $this->k); - if ($em === false) { - user_error('Invalid signature'); - return false; - } - - // EMSA-PSS verification - - return $this->_emsa_pss_verify($m, $em, $modBits - 1); - } - - /** - * EMSA-PKCS1-V1_5-ENCODE - * - * See {@link http://tools.ietf.org/html/rfc3447#section-9.2 RFC3447#section-9.2}. - * - * @access private - * @param string $m - * @param int $emLen - * @return string - */ - function _emsa_pkcs1_v1_5_encode($m, $emLen) - { - $h = $this->hash->hash($m); - if ($h === false) { - return false; - } + /** + * EMSA-PKCS1-V1_5-ENCODE + * + * See {@link http://tools.ietf.org/html/rfc3447#section-9.2 RFC3447#section-9.2}. + * + * @param string $m + * @param int $emLen + * @throws \LengthException if the intended encoded message length is too short + * @return string + */ + protected function emsa_pkcs1_v1_5_encode($m, $emLen) + { + $h = $this->hash->hash($m); // see http://tools.ietf.org/html/rfc3447#page-43 - switch ($this->hashName) { + switch ($this->hash->getHash()) { case 'md2': - $t = pack('H*', '3020300c06082a864886f70d020205000410'); + $t = "\x30\x20\x30\x0c\x06\x08\x2a\x86\x48\x86\xf7\x0d\x02\x02\x05\x00\x04\x10"; break; case 'md5': - $t = pack('H*', '3020300c06082a864886f70d020505000410'); + $t = "\x30\x20\x30\x0c\x06\x08\x2a\x86\x48\x86\xf7\x0d\x02\x05\x05\x00\x04\x10"; break; case 'sha1': - $t = pack('H*', '3021300906052b0e03021a05000414'); + $t = "\x30\x21\x30\x09\x06\x05\x2b\x0e\x03\x02\x1a\x05\x00\x04\x14"; break; case 'sha256': - $t = pack('H*', '3031300d060960864801650304020105000420'); + $t = "\x30\x31\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x01\x05\x00\x04\x20"; break; case 'sha384': - $t = pack('H*', '3041300d060960864801650304020205000430'); + $t = "\x30\x41\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x02\x05\x00\x04\x30"; break; case 'sha512': - $t = pack('H*', '3051300d060960864801650304020305000440'); + $t = "\x30\x51\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x03\x05\x00\x04\x40"; + break; + // from https://www.emc.com/collateral/white-papers/h11300-pkcs-1v2-2-rsa-cryptography-standard-wp.pdf#page=40 + case 'sha224': + $t = "\x30\x2d\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x04\x05\x00\x04\x1c"; + break; + case 'sha512/224': + $t = "\x30\x2d\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x05\x05\x00\x04\x1c"; + break; + case 'sha512/256': + $t = "\x30\x31\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x06\x05\x00\x04\x20"; } - $t.= $h; + $t .= $h; $tLen = strlen($t); if ($emLen < $tLen + 11) { - user_error('Intended encoded message length too short'); - return false; + throw new \LengthException('Intended encoded message length too short'); } $ps = str_repeat(chr(0xFF), $emLen - $tLen - 3); @@ -2927,40 +601,46 @@ function _emsa_pkcs1_v1_5_encode($m, $emLen) * generally be omitted, but if present, it shall have a value of type * NULL" * - * @access private * @param string $m * @param int $emLen * @return string */ - function _emsa_pkcs1_v1_5_encode_without_null($m, $emLen) + protected function emsa_pkcs1_v1_5_encode_without_null($m, $emLen) { $h = $this->hash->hash($m); - if ($h === false) { - return false; - } - switch ($this->hashName) { + // see http://tools.ietf.org/html/rfc3447#page-43 + switch ($this->hash->getHash()) { case 'sha1': - $t = pack('H*', '301f300706052b0e03021a0414'); + $t = "\x30\x1f\x30\x07\x06\x05\x2b\x0e\x03\x02\x1a\x04\x14"; break; case 'sha256': - $t = pack('H*', '302f300b06096086480165030402010420'); + $t = "\x30\x2f\x30\x0b\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x01\x04\x20"; break; case 'sha384': - $t = pack('H*', '303f300b06096086480165030402020430'); + $t = "\x30\x3f\x30\x0b\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x02\x04\x30"; break; case 'sha512': - $t = pack('H*', '304f300b06096086480165030402030440'); + $t = "\x30\x4f\x30\x0b\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x03\x04\x40"; + break; + // from https://www.emc.com/collateral/white-papers/h11300-pkcs-1v2-2-rsa-cryptography-standard-wp.pdf#page=40 + case 'sha224': + $t = "\x30\x2b\x30\x0b\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x04\x04\x1c"; + break; + case 'sha512/224': + $t = "\x30\x2b\x30\x0b\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x05\x04\x1c"; + break; + case 'sha512/256': + $t = "\x30\x2f\x30\x0b\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x06\x04\x20"; break; default: - return false; + throw new UnsupportedAlgorithmException('md2 and md5 require NULLs'); } - $t.= $h; + $t .= $h; $tLen = strlen($t); if ($emLen < $tLen + 11) { - user_error('Intended encoded message length too short'); - return false; + throw new \LengthException('Intended encoded message length too short'); } $ps = str_repeat(chr(0xFF), $emLen - $tLen - 3); @@ -2971,295 +651,274 @@ function _emsa_pkcs1_v1_5_encode_without_null($m, $emLen) } /** - * RSASSA-PKCS1-V1_5-SIGN + * MGF1 * - * See {@link http://tools.ietf.org/html/rfc3447#section-8.2.1 RFC3447#section-8.2.1}. + * See {@link http://tools.ietf.org/html/rfc3447#appendix-B.2.1 RFC3447#appendix-B.2.1}. * - * @access private - * @param string $m + * @param string $mgfSeed + * @param int $maskLen * @return string */ - function _rsassa_pkcs1_v1_5_sign($m) + protected function mgf1($mgfSeed, $maskLen) { - // EMSA-PKCS1-v1_5 encoding + // if $maskLen would yield strings larger than 4GB, PKCS#1 suggests a "Mask too long" error be output. - $em = $this->_emsa_pkcs1_v1_5_encode($m, $this->k); - if ($em === false) { - user_error('RSA modulus too short'); - return false; + $t = ''; + $count = ceil($maskLen / $this->mgfHLen); + for ($i = 0; $i < $count; $i++) { + $c = pack('N', $i); + $t .= $this->mgfHash->hash($mgfSeed . $c); } - // RSA signature - - $m = $this->_os2ip($em); - $s = $this->_rsasp1($m); - $s = $this->_i2osp($s, $this->k); - - // Output the signature S - - return $s; + return substr($t, 0, $maskLen); } /** - * RSASSA-PKCS1-V1_5-VERIFY + * Returns the key size * - * See {@link http://tools.ietf.org/html/rfc3447#section-8.2.2 RFC3447#section-8.2.2}. + * More specifically, this returns the size of the modulo in bits. * - * @access private - * @param string $m - * @param string $s - * @return string + * @return int */ - function _rsassa_pkcs1_v1_5_verify($m, $s) + public function getLength() { - // Length checking - - if (strlen($s) != $this->k) { - user_error('Invalid signature'); - return false; - } + return !isset($this->modulus) ? 0 : $this->modulus->getLength(); + } - // RSA verification + /** + * Determines which hashing function should be used + * + * Used with signature production / verification and (if the encryption mode is self::PADDING_OAEP) encryption and + * decryption. + * + * @param string $hash + */ + public function withHash($hash) + { + $new = clone $this; - $s = $this->_os2ip($s); - $m2 = $this->_rsavp1($s); - if ($m2 === false) { - user_error('Invalid signature'); - return false; - } - $em = $this->_i2osp($m2, $this->k); - if ($em === false) { - user_error('Invalid signature'); - return false; + // \phpseclib3\Crypt\Hash supports algorithms that PKCS#1 doesn't support. md5-96 and sha1-96, for example. + switch (strtolower($hash)) { + case 'md2': + case 'md5': + case 'sha1': + case 'sha256': + case 'sha384': + case 'sha512': + case 'sha224': + case 'sha512/224': + case 'sha512/256': + $new->hash = new Hash($hash); + break; + default: + throw new UnsupportedAlgorithmException( + 'The only supported hash algorithms are: md2, md5, sha1, sha256, sha384, sha512, sha224, sha512/224, sha512/256' + ); } + $new->hLen = $new->hash->getLengthInBytes(); - // EMSA-PKCS1-v1_5 encoding + return $new; + } - $em2 = $this->_emsa_pkcs1_v1_5_encode($m, $this->k); - $em3 = $this->_emsa_pkcs1_v1_5_encode_without_null($m, $this->k); + /** + * Determines which hashing function should be used for the mask generation function + * + * The mask generation function is used by self::PADDING_OAEP and self::PADDING_PSS and although it's + * best if Hash and MGFHash are set to the same thing this is not a requirement. + * + * @param string $hash + */ + public function withMGFHash($hash) + { + $new = clone $this; - if ($em2 === false && $em3 === false) { - user_error('RSA modulus too short'); - return false; + // \phpseclib3\Crypt\Hash supports algorithms that PKCS#1 doesn't support. md5-96 and sha1-96, for example. + switch (strtolower($hash)) { + case 'md2': + case 'md5': + case 'sha1': + case 'sha256': + case 'sha384': + case 'sha512': + case 'sha224': + case 'sha512/224': + case 'sha512/256': + $new->mgfHash = new Hash($hash); + break; + default: + throw new UnsupportedAlgorithmException( + 'The only supported hash algorithms are: md2, md5, sha1, sha256, sha384, sha512, sha224, sha512/224, sha512/256' + ); } + $new->mgfHLen = $new->mgfHash->getLengthInBytes(); - // Compare - - return ($em2 !== false && $this->_equals($em, $em2)) || - ($em3 !== false && $this->_equals($em, $em3)); + return $new; } /** - * Set Encryption Mode + * Returns the MGF hash algorithm currently being used * - * Valid values include self::ENCRYPTION_OAEP and self::ENCRYPTION_PKCS1. - * - * @access public - * @param int $mode */ - function setEncryptionMode($mode) + public function getMGFHash() { - $this->encryptionMode = $mode; + return clone $this->mgfHash; } /** - * Set Signature Mode + * Determines the salt length * - * Valid values include self::SIGNATURE_PSS and self::SIGNATURE_PKCS1 + * Used by RSA::PADDING_PSS + * + * To quote from {@link http://tools.ietf.org/html/rfc3447#page-38 RFC3447#page-38}: + * + * Typical salt lengths in octets are hLen (the length of the output + * of the hash function Hash) and 0. * - * @access public - * @param int $mode + * @param int $sLen */ - function setSignatureMode($mode) + public function withSaltLength($sLen) { - $this->signatureMode = $mode; + $new = clone $this; + $new->sLen = $sLen; + return $new; } /** - * Set public key comment. + * Returns the salt length currently being used * - * @access public - * @param string $comment */ - function setComment($comment) + public function getSaltLength() { - $this->comment = $comment; + return $this->sLen !== null ? $this->sLen : $this->hLen; } /** - * Get public key comment. + * Determines the label * - * @access public - * @return string + * Used by RSA::PADDING_OAEP + * + * To quote from {@link http://tools.ietf.org/html/rfc3447#page-17 RFC3447#page-17}: + * + * Both the encryption and the decryption operations of RSAES-OAEP take + * the value of a label L as input. In this version of PKCS #1, L is + * the empty string; other uses of the label are outside the scope of + * this document. + * + * @param string $label */ - function getComment() + public function withLabel($label) { - return $this->comment; + $new = clone $this; + $new->label = $label; + return $new; } /** - * Encryption + * Returns the label currently being used * - * Both self::ENCRYPTION_OAEP and self::ENCRYPTION_PKCS1 both place limits on how long $plaintext can be. - * If $plaintext exceeds those limits it will be broken up so that it does and the resultant ciphertext's will - * be concatenated together. - * - * @see self::decrypt() - * @access public - * @param string $plaintext - * @return string */ - function encrypt($plaintext) + public function getLabel() { - switch ($this->encryptionMode) { - case self::ENCRYPTION_NONE: - $plaintext = str_split($plaintext, $this->k); - $ciphertext = ''; - foreach ($plaintext as $m) { - $ciphertext.= $this->_raw_encrypt($m); - } - return $ciphertext; - case self::ENCRYPTION_PKCS1: - $length = $this->k - 11; - if ($length <= 0) { - return false; - } - - $plaintext = str_split($plaintext, $length); - $ciphertext = ''; - foreach ($plaintext as $m) { - $ciphertext.= $this->_rsaes_pkcs1_v1_5_encrypt($m); - } - return $ciphertext; - //case self::ENCRYPTION_OAEP: - default: - $length = $this->k - 2 * $this->hLen - 2; - if ($length <= 0) { - return false; - } - - $plaintext = str_split($plaintext, $length); - $ciphertext = ''; - foreach ($plaintext as $m) { - $ciphertext.= $this->_rsaes_oaep_encrypt($m); - } - return $ciphertext; - } + return $this->label; } /** - * Decryption + * Determines the padding modes * - * @see self::encrypt() - * @access public - * @param string $ciphertext - * @return string + * Example: $key->withPadding(RSA::ENCRYPTION_PKCS1 | RSA::SIGNATURE_PKCS1); + * + * @param int $padding */ - function decrypt($ciphertext) + public function withPadding($padding) { - if ($this->k <= 0) { - return false; - } - - $ciphertext = str_split($ciphertext, $this->k); - $ciphertext[count($ciphertext) - 1] = str_pad($ciphertext[count($ciphertext) - 1], $this->k, chr(0), STR_PAD_LEFT); - - $plaintext = ''; - - switch ($this->encryptionMode) { - case self::ENCRYPTION_NONE: - $decrypt = '_raw_encrypt'; - break; - case self::ENCRYPTION_PKCS1: - $decrypt = '_rsaes_pkcs1_v1_5_decrypt'; - break; - //case self::ENCRYPTION_OAEP: - default: - $decrypt = '_rsaes_oaep_decrypt'; + $masks = [ + self::ENCRYPTION_OAEP, + self::ENCRYPTION_PKCS1, + self::ENCRYPTION_NONE + ]; + $numSelected = 0; + $selected = 0; + foreach ($masks as $mask) { + if ($padding & $mask) { + $selected = $mask; + $numSelected++; + } } - - foreach ($ciphertext as $c) { - $temp = $this->$decrypt($c); - if ($temp === false) { - return false; + if ($numSelected > 1) { + throw new InconsistentSetupException('Multiple encryption padding modes have been selected; at most only one should be selected'); + } + $encryptionPadding = $selected; + + $masks = [ + self::SIGNATURE_PSS, + self::SIGNATURE_RELAXED_PKCS1, + self::SIGNATURE_PKCS1 + ]; + $numSelected = 0; + $selected = 0; + foreach ($masks as $mask) { + if ($padding & $mask) { + $selected = $mask; + $numSelected++; } - $plaintext.= $temp; } + if ($numSelected > 1) { + throw new InconsistentSetupException('Multiple signature padding modes have been selected; at most only one should be selected'); + } + $signaturePadding = $selected; - return $plaintext; + $new = clone $this; + $new->encryptionPadding = $encryptionPadding; + $new->signaturePadding = $signaturePadding; + return $new; } /** - * Create a signature + * Returns the padding currently being used * - * @see self::verify() - * @access public - * @param string $message - * @return string */ - function sign($message) + public function getPadding() { - if (empty($this->modulus) || empty($this->exponent)) { - return false; - } - - switch ($this->signatureMode) { - case self::SIGNATURE_PKCS1: - return $this->_rsassa_pkcs1_v1_5_sign($message); - //case self::SIGNATURE_PSS: - default: - return $this->_rsassa_pss_sign($message); - } + return $this->signaturePadding | $this->encryptionPadding; } /** - * Verifies a signature + * Returns the current engine being used * - * @see self::sign() - * @access public - * @param string $message - * @param string $signature - * @return bool + * OpenSSL is only used in this class (and it's subclasses) for key generation + * Even then it depends on the parameters you're using. It's not used for + * multi-prime RSA nor is it used if the key length is outside of the range + * supported by OpenSSL + * + * @see self::useInternalEngine() + * @see self::useBestEngine() + * @return string */ - function verify($message, $signature) + public function getEngine() { - if (empty($this->modulus) || empty($this->exponent)) { - return false; + if (!isset(self::$engines['PHP'])) { + self::useBestEngine(); } + return self::$engines['OpenSSL'] && self::$defaultExponent == 65537 ? + 'OpenSSL' : + 'PHP'; + } - switch ($this->signatureMode) { - case self::SIGNATURE_PKCS1: - return $this->_rsassa_pkcs1_v1_5_verify($message, $signature); - //case self::SIGNATURE_PSS: - default: - return $this->_rsassa_pss_verify($message, $signature); - } + /** + * Enable RSA Blinding + * + */ + public static function enableBlinding() + { + static::$enableBlinding = true; } /** - * Extract raw BER from Base64 encoding + * Disable RSA Blinding * - * @access private - * @param string $str - * @return string */ - function _extractBER($str) + public static function disableBlinding() { - /* X.509 certs are assumed to be base64 encoded but sometimes they'll have additional things in them - * above and beyond the ceritificate. - * ie. some may have the following preceding the -----BEGIN CERTIFICATE----- line: - * - * Bag Attributes - * localKeyID: 01 00 00 00 - * subject=/O=organization/OU=org unit/CN=common name - * issuer=/O=organization/CN=common name - */ - $temp = preg_replace('#.*?^-+[^-]+-+[\r\n ]*$#ms', '', $str, 1); - // remove the -----BEGIN CERTIFICATE----- and -----END CERTIFICATE----- stuff - $temp = preg_replace('#-+[^-]+-+#', '', $temp); - // remove new lines - $temp = str_replace(array("\r", "\n", ' '), '', $temp); - $temp = preg_match('#^[a-zA-Z\d/+]*={0,2}$#', $temp) ? base64_decode($temp) : false; - return $temp != false ? $temp : $str; + static::$enableBlinding = false; } } diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/JWK.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/JWK.php new file mode 100644 index 00000000..87f543de --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/JWK.php @@ -0,0 +1,142 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\Crypt\RSA\Formats\Keys; + +use phpseclib3\Common\Functions\Strings; +use phpseclib3\Crypt\Common\Formats\Keys\JWK as Progenitor; +use phpseclib3\Math\BigInteger; + +/** + * JWK Formatted RSA Handler + * + * @author Jim Wigginton + */ +abstract class JWK extends Progenitor +{ + /** + * Break a public or private key down into its constituent components + * + * @param string $key + * @param string $password optional + * @return array + */ + public static function load($key, $password = '') + { + $key = parent::load($key, $password); + + if ($key->kty != 'RSA') { + throw new \RuntimeException('Only RSA JWK keys are supported'); + } + + $count = $publicCount = 0; + $vars = ['n', 'e', 'd', 'p', 'q', 'dp', 'dq', 'qi']; + foreach ($vars as $var) { + if (!isset($key->$var) || !is_string($key->$var)) { + continue; + } + $count++; + $value = new BigInteger(Strings::base64url_decode($key->$var), 256); + switch ($var) { + case 'n': + $publicCount++; + $components['modulus'] = $value; + break; + case 'e': + $publicCount++; + $components['publicExponent'] = $value; + break; + case 'd': + $components['privateExponent'] = $value; + break; + case 'p': + $components['primes'][1] = $value; + break; + case 'q': + $components['primes'][2] = $value; + break; + case 'dp': + $components['exponents'][1] = $value; + break; + case 'dq': + $components['exponents'][2] = $value; + break; + case 'qi': + $components['coefficients'][2] = $value; + } + } + + if ($count == count($vars)) { + return $components + ['isPublicKey' => false]; + } + + if ($count == 2 && $publicCount == 2) { + return $components + ['isPublicKey' => true]; + } + + throw new \UnexpectedValueException('Key does not have an appropriate number of RSA parameters'); + } + + /** + * Convert a private key to the appropriate format. + * + * @param \phpseclib3\Math\BigInteger $n + * @param \phpseclib3\Math\BigInteger $e + * @param \phpseclib3\Math\BigInteger $d + * @param array $primes + * @param array $exponents + * @param array $coefficients + * @param string $password optional + * @param array $options optional + * @return string + */ + public static function savePrivateKey(BigInteger $n, BigInteger $e, BigInteger $d, array $primes, array $exponents, array $coefficients, $password = '', array $options = []) + { + if (count($primes) != 2) { + throw new \InvalidArgumentException('JWK does not support multi-prime RSA keys'); + } + + $key = [ + 'kty' => 'RSA', + 'n' => Strings::base64url_encode($n->toBytes()), + 'e' => Strings::base64url_encode($e->toBytes()), + 'd' => Strings::base64url_encode($d->toBytes()), + 'p' => Strings::base64url_encode($primes[1]->toBytes()), + 'q' => Strings::base64url_encode($primes[2]->toBytes()), + 'dp' => Strings::base64url_encode($exponents[1]->toBytes()), + 'dq' => Strings::base64url_encode($exponents[2]->toBytes()), + 'qi' => Strings::base64url_encode($coefficients[2]->toBytes()) + ]; + + return self::wrapKey($key, $options); + } + + /** + * Convert a public key to the appropriate format + * + * @param \phpseclib3\Math\BigInteger $n + * @param \phpseclib3\Math\BigInteger $e + * @param array $options optional + * @return string + */ + public static function savePublicKey(BigInteger $n, BigInteger $e, array $options = []) + { + $key = [ + 'kty' => 'RSA', + 'n' => Strings::base64url_encode($n->toBytes()), + 'e' => Strings::base64url_encode($e->toBytes()) + ]; + + return self::wrapKey($key, $options); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/MSBLOB.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/MSBLOB.php new file mode 100644 index 00000000..e9a0c4f3 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/MSBLOB.php @@ -0,0 +1,228 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\Crypt\RSA\Formats\Keys; + +use phpseclib3\Common\Functions\Strings; +use phpseclib3\Exception\UnsupportedFormatException; +use phpseclib3\Math\BigInteger; + +/** + * Microsoft BLOB Formatted RSA Key Handler + * + * @author Jim Wigginton + */ +abstract class MSBLOB +{ + /** + * Public/Private Key Pair + * + */ + const PRIVATEKEYBLOB = 0x7; + /** + * Public Key + * + */ + const PUBLICKEYBLOB = 0x6; + /** + * Public Key + * + */ + const PUBLICKEYBLOBEX = 0xA; + /** + * RSA public key exchange algorithm + * + */ + const CALG_RSA_KEYX = 0x0000A400; + /** + * RSA public key exchange algorithm + * + */ + const CALG_RSA_SIGN = 0x00002400; + /** + * Public Key + * + */ + const RSA1 = 0x31415352; + /** + * Private Key + * + */ + const RSA2 = 0x32415352; + + /** + * Break a public or private key down into its constituent components + * + * @param string $key + * @param string $password optional + * @return array + */ + public static function load($key, $password = '') + { + if (!Strings::is_stringable($key)) { + throw new \UnexpectedValueException('Key should be a string - not a ' . gettype($key)); + } + + $key = Strings::base64_decode($key); + + if (!is_string($key)) { + throw new \UnexpectedValueException('Base64 decoding produced an error'); + } + if (strlen($key) < 20) { + throw new \UnexpectedValueException('Key appears to be malformed'); + } + + // PUBLICKEYSTRUC publickeystruc + // https://msdn.microsoft.com/en-us/library/windows/desktop/aa387453(v=vs.85).aspx + extract(unpack('atype/aversion/vreserved/Valgo', Strings::shift($key, 8))); + /** + * @var string $type + * @var string $version + * @var integer $reserved + * @var integer $algo + */ + switch (ord($type)) { + case self::PUBLICKEYBLOB: + case self::PUBLICKEYBLOBEX: + $publickey = true; + break; + case self::PRIVATEKEYBLOB: + $publickey = false; + break; + default: + throw new \UnexpectedValueException('Key appears to be malformed'); + } + + $components = ['isPublicKey' => $publickey]; + + // https://msdn.microsoft.com/en-us/library/windows/desktop/aa375549(v=vs.85).aspx + switch ($algo) { + case self::CALG_RSA_KEYX: + case self::CALG_RSA_SIGN: + break; + default: + throw new \UnexpectedValueException('Key appears to be malformed'); + } + + // RSAPUBKEY rsapubkey + // https://msdn.microsoft.com/en-us/library/windows/desktop/aa387685(v=vs.85).aspx + // could do V for pubexp but that's unsigned 32-bit whereas some PHP installs only do signed 32-bit + extract(unpack('Vmagic/Vbitlen/a4pubexp', Strings::shift($key, 12))); + /** + * @var integer $magic + * @var integer $bitlen + * @var string $pubexp + */ + switch ($magic) { + case self::RSA2: + $components['isPublicKey'] = false; + // fall-through + case self::RSA1: + break; + default: + throw new \UnexpectedValueException('Key appears to be malformed'); + } + + $baseLength = $bitlen / 16; + if (strlen($key) != 2 * $baseLength && strlen($key) != 9 * $baseLength) { + throw new \UnexpectedValueException('Key appears to be malformed'); + } + + $components[$components['isPublicKey'] ? 'publicExponent' : 'privateExponent'] = new BigInteger(strrev($pubexp), 256); + // BYTE modulus[rsapubkey.bitlen/8] + $components['modulus'] = new BigInteger(strrev(Strings::shift($key, $bitlen / 8)), 256); + + if ($publickey) { + return $components; + } + + $components['isPublicKey'] = false; + + // BYTE prime1[rsapubkey.bitlen/16] + $components['primes'] = [1 => new BigInteger(strrev(Strings::shift($key, $bitlen / 16)), 256)]; + // BYTE prime2[rsapubkey.bitlen/16] + $components['primes'][] = new BigInteger(strrev(Strings::shift($key, $bitlen / 16)), 256); + // BYTE exponent1[rsapubkey.bitlen/16] + $components['exponents'] = [1 => new BigInteger(strrev(Strings::shift($key, $bitlen / 16)), 256)]; + // BYTE exponent2[rsapubkey.bitlen/16] + $components['exponents'][] = new BigInteger(strrev(Strings::shift($key, $bitlen / 16)), 256); + // BYTE coefficient[rsapubkey.bitlen/16] + $components['coefficients'] = [2 => new BigInteger(strrev(Strings::shift($key, $bitlen / 16)), 256)]; + if (isset($components['privateExponent'])) { + $components['publicExponent'] = $components['privateExponent']; + } + // BYTE privateExponent[rsapubkey.bitlen/8] + $components['privateExponent'] = new BigInteger(strrev(Strings::shift($key, $bitlen / 8)), 256); + + return $components; + } + + /** + * Convert a private key to the appropriate format. + * + * @param \phpseclib3\Math\BigInteger $n + * @param \phpseclib3\Math\BigInteger $e + * @param \phpseclib3\Math\BigInteger $d + * @param array $primes + * @param array $exponents + * @param array $coefficients + * @param string $password optional + * @return string + */ + public static function savePrivateKey(BigInteger $n, BigInteger $e, BigInteger $d, array $primes, array $exponents, array $coefficients, $password = '') + { + if (count($primes) != 2) { + throw new \InvalidArgumentException('MSBLOB does not support multi-prime RSA keys'); + } + + if (!empty($password) && is_string($password)) { + throw new UnsupportedFormatException('MSBLOB private keys do not support encryption'); + } + + $n = strrev($n->toBytes()); + $e = str_pad(strrev($e->toBytes()), 4, "\0"); + $key = pack('aavV', chr(self::PRIVATEKEYBLOB), chr(2), 0, self::CALG_RSA_KEYX); + $key .= pack('VVa*', self::RSA2, 8 * strlen($n), $e); + $key .= $n; + $key .= strrev($primes[1]->toBytes()); + $key .= strrev($primes[2]->toBytes()); + $key .= strrev($exponents[1]->toBytes()); + $key .= strrev($exponents[2]->toBytes()); + $key .= strrev($coefficients[2]->toBytes()); + $key .= strrev($d->toBytes()); + + return Strings::base64_encode($key); + } + + /** + * Convert a public key to the appropriate format + * + * @param \phpseclib3\Math\BigInteger $n + * @param \phpseclib3\Math\BigInteger $e + * @return string + */ + public static function savePublicKey(BigInteger $n, BigInteger $e) + { + $n = strrev($n->toBytes()); + $e = str_pad(strrev($e->toBytes()), 4, "\0"); + $key = pack('aavV', chr(self::PUBLICKEYBLOB), chr(2), 0, self::CALG_RSA_KEYX); + $key .= pack('VVa*', self::RSA1, 8 * strlen($n), $e); + $key .= $n; + + return Strings::base64_encode($key); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/OpenSSH.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/OpenSSH.php new file mode 100644 index 00000000..2367810a --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/OpenSSH.php @@ -0,0 +1,132 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\Crypt\RSA\Formats\Keys; + +use phpseclib3\Common\Functions\Strings; +use phpseclib3\Crypt\Common\Formats\Keys\OpenSSH as Progenitor; +use phpseclib3\Math\BigInteger; + +/** + * OpenSSH Formatted RSA Key Handler + * + * @author Jim Wigginton + */ +abstract class OpenSSH extends Progenitor +{ + /** + * Supported Key Types + * + * @var array + */ + protected static $types = ['ssh-rsa']; + + /** + * Break a public or private key down into its constituent components + * + * @param string $key + * @param string $password optional + * @return array + */ + public static function load($key, $password = '') + { + static $one; + if (!isset($one)) { + $one = new BigInteger(1); + } + + $parsed = parent::load($key, $password); + + if (isset($parsed['paddedKey'])) { + list($type) = Strings::unpackSSH2('s', $parsed['paddedKey']); + if ($type != $parsed['type']) { + throw new \RuntimeException("The public and private keys are not of the same type ($type vs $parsed[type])"); + } + + $primes = $coefficients = []; + + list( + $modulus, + $publicExponent, + $privateExponent, + $coefficients[2], + $primes[1], + $primes[2], + $comment, + ) = Strings::unpackSSH2('i6s', $parsed['paddedKey']); + + $temp = $primes[1]->subtract($one); + $exponents = [1 => $publicExponent->modInverse($temp)]; + $temp = $primes[2]->subtract($one); + $exponents[] = $publicExponent->modInverse($temp); + + $isPublicKey = false; + + return compact('publicExponent', 'modulus', 'privateExponent', 'primes', 'coefficients', 'exponents', 'comment', 'isPublicKey'); + } + + list($publicExponent, $modulus) = Strings::unpackSSH2('ii', $parsed['publicKey']); + + return [ + 'isPublicKey' => true, + 'modulus' => $modulus, + 'publicExponent' => $publicExponent, + 'comment' => $parsed['comment'] + ]; + } + + /** + * Convert a public key to the appropriate format + * + * @param \phpseclib3\Math\BigInteger $n + * @param \phpseclib3\Math\BigInteger $e + * @param array $options optional + * @return string + */ + public static function savePublicKey(BigInteger $n, BigInteger $e, array $options = []) + { + $RSAPublicKey = Strings::packSSH2('sii', 'ssh-rsa', $e, $n); + + if (isset($options['binary']) ? $options['binary'] : self::$binary) { + return $RSAPublicKey; + } + + $comment = isset($options['comment']) ? $options['comment'] : self::$comment; + $RSAPublicKey = 'ssh-rsa ' . base64_encode($RSAPublicKey) . ' ' . $comment; + + return $RSAPublicKey; + } + + /** + * Convert a private key to the appropriate format. + * + * @param \phpseclib3\Math\BigInteger $n + * @param \phpseclib3\Math\BigInteger $e + * @param \phpseclib3\Math\BigInteger $d + * @param array $primes + * @param array $exponents + * @param array $coefficients + * @param string $password optional + * @param array $options optional + * @return string + */ + public static function savePrivateKey(BigInteger $n, BigInteger $e, BigInteger $d, array $primes, array $exponents, array $coefficients, $password = '', array $options = []) + { + $publicKey = self::savePublicKey($n, $e, ['binary' => true]); + $privateKey = Strings::packSSH2('si6', 'ssh-rsa', $n, $e, $d, $coefficients[2], $primes[1], $primes[2]); + + return self::wrapPrivateKey($publicKey, $privateKey, $password, $options); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/PKCS1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/PKCS1.php new file mode 100644 index 00000000..276c6024 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/PKCS1.php @@ -0,0 +1,160 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\Crypt\RSA\Formats\Keys; + +use phpseclib3\Common\Functions\Strings; +use phpseclib3\Crypt\Common\Formats\Keys\PKCS1 as Progenitor; +use phpseclib3\File\ASN1; +use phpseclib3\File\ASN1\Maps; +use phpseclib3\Math\BigInteger; + +/** + * PKCS#1 Formatted RSA Key Handler + * + * @author Jim Wigginton + */ +abstract class PKCS1 extends Progenitor +{ + /** + * Break a public or private key down into its constituent components + * + * @param string $key + * @param string $password optional + * @return array + */ + public static function load($key, $password = '') + { + if (!Strings::is_stringable($key)) { + throw new \UnexpectedValueException('Key should be a string - not a ' . gettype($key)); + } + + if (strpos($key, 'PUBLIC') !== false) { + $components = ['isPublicKey' => true]; + } elseif (strpos($key, 'PRIVATE') !== false) { + $components = ['isPublicKey' => false]; + } else { + $components = []; + } + + $key = parent::load($key, $password); + + $decoded = ASN1::decodeBER($key); + if (!$decoded) { + throw new \RuntimeException('Unable to decode BER'); + } + + $key = ASN1::asn1map($decoded[0], Maps\RSAPrivateKey::MAP); + if (is_array($key)) { + $components += [ + 'modulus' => $key['modulus'], + 'publicExponent' => $key['publicExponent'], + 'privateExponent' => $key['privateExponent'], + 'primes' => [1 => $key['prime1'], $key['prime2']], + 'exponents' => [1 => $key['exponent1'], $key['exponent2']], + 'coefficients' => [2 => $key['coefficient']] + ]; + if ($key['version'] == 'multi') { + foreach ($key['otherPrimeInfos'] as $primeInfo) { + $components['primes'][] = $primeInfo['prime']; + $components['exponents'][] = $primeInfo['exponent']; + $components['coefficients'][] = $primeInfo['coefficient']; + } + } + if (!isset($components['isPublicKey'])) { + $components['isPublicKey'] = false; + } + return $components; + } + + $key = ASN1::asn1map($decoded[0], Maps\RSAPublicKey::MAP); + + if (!is_array($key)) { + throw new \RuntimeException('Unable to perform ASN1 mapping'); + } + + if (!isset($components['isPublicKey'])) { + $components['isPublicKey'] = true; + } + + return $components + $key; + } + + /** + * Convert a private key to the appropriate format. + * + * @param \phpseclib3\Math\BigInteger $n + * @param \phpseclib3\Math\BigInteger $e + * @param \phpseclib3\Math\BigInteger $d + * @param array $primes + * @param array $exponents + * @param array $coefficients + * @param string $password optional + * @param array $options optional + * @return string + */ + public static function savePrivateKey(BigInteger $n, BigInteger $e, BigInteger $d, array $primes, array $exponents, array $coefficients, $password = '', array $options = []) + { + $num_primes = count($primes); + $key = [ + 'version' => $num_primes == 2 ? 'two-prime' : 'multi', + 'modulus' => $n, + 'publicExponent' => $e, + 'privateExponent' => $d, + 'prime1' => $primes[1], + 'prime2' => $primes[2], + 'exponent1' => $exponents[1], + 'exponent2' => $exponents[2], + 'coefficient' => $coefficients[2] + ]; + for ($i = 3; $i <= $num_primes; $i++) { + $key['otherPrimeInfos'][] = [ + 'prime' => $primes[$i], + 'exponent' => $exponents[$i], + 'coefficient' => $coefficients[$i] + ]; + } + + $key = ASN1::encodeDER($key, Maps\RSAPrivateKey::MAP); + + return self::wrapPrivateKey($key, 'RSA', $password, $options); + } + + /** + * Convert a public key to the appropriate format + * + * @param \phpseclib3\Math\BigInteger $n + * @param \phpseclib3\Math\BigInteger $e + * @return string + */ + public static function savePublicKey(BigInteger $n, BigInteger $e) + { + $key = [ + 'modulus' => $n, + 'publicExponent' => $e + ]; + + $key = ASN1::encodeDER($key, Maps\RSAPublicKey::MAP); + + return self::wrapPublicKey($key, 'RSA'); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/PKCS8.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/PKCS8.php new file mode 100644 index 00000000..7ff9a199 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/PKCS8.php @@ -0,0 +1,139 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\Crypt\RSA\Formats\Keys; + +use phpseclib3\Common\Functions\Strings; +use phpseclib3\Crypt\Common\Formats\Keys\PKCS8 as Progenitor; +use phpseclib3\File\ASN1; +use phpseclib3\Math\BigInteger; + +/** + * PKCS#8 Formatted RSA Key Handler + * + * @author Jim Wigginton + */ +abstract class PKCS8 extends Progenitor +{ + /** + * OID Name + * + * @var string + */ + const OID_NAME = 'rsaEncryption'; + + /** + * OID Value + * + * @var string + */ + const OID_VALUE = '1.2.840.113549.1.1.1'; + + /** + * Child OIDs loaded + * + * @var bool + */ + protected static $childOIDsLoaded = false; + + /** + * Break a public or private key down into its constituent components + * + * @param string $key + * @param string $password optional + * @return array + */ + public static function load($key, $password = '') + { + if (!Strings::is_stringable($key)) { + throw new \UnexpectedValueException('Key should be a string - not a ' . gettype($key)); + } + + if (strpos($key, 'PUBLIC') !== false) { + $components = ['isPublicKey' => true]; + } elseif (strpos($key, 'PRIVATE') !== false) { + $components = ['isPublicKey' => false]; + } else { + $components = []; + } + + $key = parent::load($key, $password); + + if (isset($key['privateKey'])) { + if (!isset($components['isPublicKey'])) { + $components['isPublicKey'] = false; + } + $type = 'private'; + } else { + if (!isset($components['isPublicKey'])) { + $components['isPublicKey'] = true; + } + $type = 'public'; + } + + $result = $components + PKCS1::load($key[$type . 'Key']); + + if (isset($key['meta'])) { + $result['meta'] = $key['meta']; + } + + return $result; + } + + /** + * Convert a private key to the appropriate format. + * + * @param \phpseclib3\Math\BigInteger $n + * @param \phpseclib3\Math\BigInteger $e + * @param \phpseclib3\Math\BigInteger $d + * @param array $primes + * @param array $exponents + * @param array $coefficients + * @param string $password optional + * @param array $options optional + * @return string + */ + public static function savePrivateKey(BigInteger $n, BigInteger $e, BigInteger $d, array $primes, array $exponents, array $coefficients, $password = '', array $options = []) + { + $key = PKCS1::savePrivateKey($n, $e, $d, $primes, $exponents, $coefficients); + $key = ASN1::extractBER($key); + return self::wrapPrivateKey($key, [], null, $password, null, '', $options); + } + + /** + * Convert a public key to the appropriate format + * + * @param \phpseclib3\Math\BigInteger $n + * @param \phpseclib3\Math\BigInteger $e + * @param array $options optional + * @return string + */ + public static function savePublicKey(BigInteger $n, BigInteger $e, array $options = []) + { + $key = PKCS1::savePublicKey($n, $e); + $key = ASN1::extractBER($key); + return self::wrapPublicKey($key, null); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/PSS.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/PSS.php new file mode 100644 index 00000000..ed75b9b7 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/PSS.php @@ -0,0 +1,238 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\Crypt\RSA\Formats\Keys; + +use phpseclib3\Common\Functions\Strings; +use phpseclib3\Crypt\Common\Formats\Keys\PKCS8 as Progenitor; +use phpseclib3\File\ASN1; +use phpseclib3\File\ASN1\Maps; +use phpseclib3\Math\BigInteger; + +/** + * PKCS#8 Formatted RSA-PSS Key Handler + * + * @author Jim Wigginton + */ +abstract class PSS extends Progenitor +{ + /** + * OID Name + * + * @var string + */ + const OID_NAME = 'id-RSASSA-PSS'; + + /** + * OID Value + * + * @var string + */ + const OID_VALUE = '1.2.840.113549.1.1.10'; + + /** + * OIDs loaded + * + * @var bool + */ + private static $oidsLoaded = false; + + /** + * Child OIDs loaded + * + * @var bool + */ + protected static $childOIDsLoaded = false; + + /** + * Initialize static variables + */ + private static function initialize_static_variables() + { + if (!self::$oidsLoaded) { + ASN1::loadOIDs([ + 'md2' => '1.2.840.113549.2.2', + 'md4' => '1.2.840.113549.2.4', + 'md5' => '1.2.840.113549.2.5', + 'id-sha1' => '1.3.14.3.2.26', + 'id-sha256' => '2.16.840.1.101.3.4.2.1', + 'id-sha384' => '2.16.840.1.101.3.4.2.2', + 'id-sha512' => '2.16.840.1.101.3.4.2.3', + 'id-sha224' => '2.16.840.1.101.3.4.2.4', + 'id-sha512/224' => '2.16.840.1.101.3.4.2.5', + 'id-sha512/256' => '2.16.840.1.101.3.4.2.6', + + 'id-mgf1' => '1.2.840.113549.1.1.8' + ]); + self::$oidsLoaded = true; + } + } + + /** + * Break a public or private key down into its constituent components + * + * @param string $key + * @param string $password optional + * @return array + */ + public static function load($key, $password = '') + { + self::initialize_static_variables(); + + if (!Strings::is_stringable($key)) { + throw new \UnexpectedValueException('Key should be a string - not a ' . gettype($key)); + } + + $components = ['isPublicKey' => strpos($key, 'PUBLIC') !== false]; + + $key = parent::load($key, $password); + + $type = isset($key['privateKey']) ? 'private' : 'public'; + + $result = $components + PKCS1::load($key[$type . 'Key']); + + if (isset($key[$type . 'KeyAlgorithm']['parameters'])) { + $decoded = ASN1::decodeBER($key[$type . 'KeyAlgorithm']['parameters']); + if ($decoded === false) { + throw new \UnexpectedValueException('Unable to decode parameters'); + } + $params = ASN1::asn1map($decoded[0], Maps\RSASSA_PSS_params::MAP); + } else { + $params = []; + } + + if (isset($params['maskGenAlgorithm']['parameters'])) { + $decoded = ASN1::decodeBER($params['maskGenAlgorithm']['parameters']); + if ($decoded === false) { + throw new \UnexpectedValueException('Unable to decode parameters'); + } + $params['maskGenAlgorithm']['parameters'] = ASN1::asn1map($decoded[0], Maps\HashAlgorithm::MAP); + } else { + $params['maskGenAlgorithm'] = [ + 'algorithm' => 'id-mgf1', + 'parameters' => ['algorithm' => 'id-sha1'] + ]; + } + + if (!isset($params['hashAlgorithm']['algorithm'])) { + $params['hashAlgorithm']['algorithm'] = 'id-sha1'; + } + + $result['hash'] = str_replace('id-', '', $params['hashAlgorithm']['algorithm']); + $result['MGFHash'] = str_replace('id-', '', $params['maskGenAlgorithm']['parameters']['algorithm']); + if (isset($params['saltLength'])) { + $result['saltLength'] = (int) $params['saltLength']->toString(); + } + + if (isset($key['meta'])) { + $result['meta'] = $key['meta']; + } + + return $result; + } + + /** + * Convert a private key to the appropriate format. + * + * @param \phpseclib3\Math\BigInteger $n + * @param \phpseclib3\Math\BigInteger $e + * @param \phpseclib3\Math\BigInteger $d + * @param array $primes + * @param array $exponents + * @param array $coefficients + * @param string $password optional + * @param array $options optional + * @return string + */ + public static function savePrivateKey(BigInteger $n, BigInteger $e, BigInteger $d, array $primes, array $exponents, array $coefficients, $password = '', array $options = []) + { + self::initialize_static_variables(); + + $key = PKCS1::savePrivateKey($n, $e, $d, $primes, $exponents, $coefficients); + $key = ASN1::extractBER($key); + $params = self::savePSSParams($options); + return self::wrapPrivateKey($key, [], $params, $password, null, '', $options); + } + + /** + * Convert a public key to the appropriate format + * + * @param \phpseclib3\Math\BigInteger $n + * @param \phpseclib3\Math\BigInteger $e + * @param array $options optional + * @return string + */ + public static function savePublicKey(BigInteger $n, BigInteger $e, array $options = []) + { + self::initialize_static_variables(); + + $key = PKCS1::savePublicKey($n, $e); + $key = ASN1::extractBER($key); + $params = self::savePSSParams($options); + return self::wrapPublicKey($key, $params); + } + + /** + * Encodes PSS parameters + * + * @param array $options + * @return string + */ + public static function savePSSParams(array $options) + { + /* + The trailerField field is an integer. It provides + compatibility with IEEE Std 1363a-2004 [P1363A]. The value + MUST be 1, which represents the trailer field with hexadecimal + value 0xBC. Other trailer fields, including the trailer field + composed of HashID concatenated with 0xCC that is specified in + IEEE Std 1363a, are not supported. Implementations that + perform signature generation MUST omit the trailerField field, + indicating that the default trailer field value was used. + Implementations that perform signature validation MUST + recognize both a present trailerField field with value 1 and an + absent trailerField field. + + source: https://tools.ietf.org/html/rfc4055#page-9 + */ + $params = [ + 'trailerField' => new BigInteger(1) + ]; + if (isset($options['hash'])) { + $params['hashAlgorithm']['algorithm'] = 'id-' . $options['hash']; + } + if (isset($options['MGFHash'])) { + $temp = ['algorithm' => 'id-' . $options['MGFHash']]; + $temp = ASN1::encodeDER($temp, Maps\HashAlgorithm::MAP); + $params['maskGenAlgorithm'] = [ + 'algorithm' => 'id-mgf1', + 'parameters' => new ASN1\Element($temp) + ]; + } + if (isset($options['saltLength'])) { + $params['saltLength'] = new BigInteger($options['saltLength']); + } + + return new ASN1\Element(ASN1::encodeDER($params, Maps\RSASSA_PSS_params::MAP)); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/PuTTY.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/PuTTY.php new file mode 100644 index 00000000..fe35717b --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/PuTTY.php @@ -0,0 +1,121 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\Crypt\RSA\Formats\Keys; + +use phpseclib3\Common\Functions\Strings; +use phpseclib3\Crypt\Common\Formats\Keys\PuTTY as Progenitor; +use phpseclib3\Math\BigInteger; + +/** + * PuTTY Formatted RSA Key Handler + * + * @author Jim Wigginton + */ +abstract class PuTTY extends Progenitor +{ + /** + * Public Handler + * + * @var string + */ + const PUBLIC_HANDLER = 'phpseclib3\Crypt\RSA\Formats\Keys\OpenSSH'; + + /** + * Algorithm Identifier + * + * @var array + */ + protected static $types = ['ssh-rsa']; + + /** + * Break a public or private key down into its constituent components + * + * @param string $key + * @param string $password optional + * @return array + */ + public static function load($key, $password = '') + { + static $one; + if (!isset($one)) { + $one = new BigInteger(1); + } + + $components = parent::load($key, $password); + if (!isset($components['private'])) { + return $components; + } + extract($components); + unset($components['public'], $components['private']); + + $isPublicKey = false; + + $result = Strings::unpackSSH2('ii', $public); + if ($result === false) { + throw new \UnexpectedValueException('Key appears to be malformed'); + } + list($publicExponent, $modulus) = $result; + + $result = Strings::unpackSSH2('iiii', $private); + if ($result === false) { + throw new \UnexpectedValueException('Key appears to be malformed'); + } + $primes = $coefficients = []; + list($privateExponent, $primes[1], $primes[2], $coefficients[2]) = $result; + + $temp = $primes[1]->subtract($one); + $exponents = [1 => $publicExponent->modInverse($temp)]; + $temp = $primes[2]->subtract($one); + $exponents[] = $publicExponent->modInverse($temp); + + return compact('publicExponent', 'modulus', 'privateExponent', 'primes', 'coefficients', 'exponents', 'comment', 'isPublicKey'); + } + + /** + * Convert a private key to the appropriate format. + * + * @param \phpseclib3\Math\BigInteger $n + * @param \phpseclib3\Math\BigInteger $e + * @param \phpseclib3\Math\BigInteger $d + * @param array $primes + * @param array $exponents + * @param array $coefficients + * @param string $password optional + * @param array $options optional + * @return string + */ + public static function savePrivateKey(BigInteger $n, BigInteger $e, BigInteger $d, array $primes, array $exponents, array $coefficients, $password = '', array $options = []) + { + if (count($primes) != 2) { + throw new \InvalidArgumentException('PuTTY does not support multi-prime RSA keys'); + } + + $public = Strings::packSSH2('ii', $e, $n); + $private = Strings::packSSH2('iiii', $d, $primes[1], $primes[2], $coefficients[2]); + + return self::wrapPrivateKey($public, $private, 'ssh-rsa', $password, $options); + } + + /** + * Convert a public key to the appropriate format + * + * @param \phpseclib3\Math\BigInteger $n + * @param \phpseclib3\Math\BigInteger $e + * @return string + */ + public static function savePublicKey(BigInteger $n, BigInteger $e) + { + return self::wrapPublicKey(Strings::packSSH2('ii', $e, $n), 'ssh-rsa'); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/Raw.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/Raw.php new file mode 100644 index 00000000..db728784 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/Raw.php @@ -0,0 +1,184 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\Crypt\RSA\Formats\Keys; + +use phpseclib3\Math\BigInteger; + +/** + * Raw RSA Key Handler + * + * @author Jim Wigginton + */ +abstract class Raw +{ + /** + * Break a public or private key down into its constituent components + * + * @param string $key + * @param string $password optional + * @return array + */ + public static function load($key, $password = '') + { + if (!is_array($key)) { + throw new \UnexpectedValueException('Key should be a array - not a ' . gettype($key)); + } + + $key = array_change_key_case($key, CASE_LOWER); + + $components = ['isPublicKey' => false]; + + foreach (['e', 'exponent', 'publicexponent', 0, 'privateexponent', 'd'] as $index) { + if (isset($key[$index])) { + $components['publicExponent'] = $key[$index]; + break; + } + } + + foreach (['n', 'modulo', 'modulus', 1] as $index) { + if (isset($key[$index])) { + $components['modulus'] = $key[$index]; + break; + } + } + + if (!isset($components['publicExponent']) || !isset($components['modulus'])) { + throw new \UnexpectedValueException('Modulus / exponent not present'); + } + + if (isset($key['primes'])) { + $components['primes'] = $key['primes']; + } elseif (isset($key['p']) && isset($key['q'])) { + $indices = [ + ['p', 'q'], + ['prime1', 'prime2'] + ]; + foreach ($indices as $index) { + list($i0, $i1) = $index; + if (isset($key[$i0]) && isset($key[$i1])) { + $components['primes'] = [1 => $key[$i0], $key[$i1]]; + } + } + } + + if (isset($key['exponents'])) { + $components['exponents'] = $key['exponents']; + } else { + $indices = [ + ['dp', 'dq'], + ['exponent1', 'exponent2'] + ]; + foreach ($indices as $index) { + list($i0, $i1) = $index; + if (isset($key[$i0]) && isset($key[$i1])) { + $components['exponents'] = [1 => $key[$i0], $key[$i1]]; + } + } + } + + if (isset($key['coefficients'])) { + $components['coefficients'] = $key['coefficients']; + } else { + foreach (['inverseq', 'q\'', 'coefficient'] as $index) { + if (isset($key[$index])) { + $components['coefficients'] = [2 => $key[$index]]; + } + } + } + + if (!isset($components['primes'])) { + $components['isPublicKey'] = true; + return $components; + } + + if (!isset($components['exponents'])) { + $one = new BigInteger(1); + $temp = $components['primes'][1]->subtract($one); + $exponents = [1 => $components['publicExponent']->modInverse($temp)]; + $temp = $components['primes'][2]->subtract($one); + $exponents[] = $components['publicExponent']->modInverse($temp); + $components['exponents'] = $exponents; + } + + if (!isset($components['coefficients'])) { + $components['coefficients'] = [2 => $components['primes'][2]->modInverse($components['primes'][1])]; + } + + foreach (['privateexponent', 'd'] as $index) { + if (isset($key[$index])) { + $components['privateExponent'] = $key[$index]; + break; + } + } + + return $components; + } + + /** + * Convert a private key to the appropriate format. + * + * @param \phpseclib3\Math\BigInteger $n + * @param \phpseclib3\Math\BigInteger $e + * @param \phpseclib3\Math\BigInteger $d + * @param array $primes + * @param array $exponents + * @param array $coefficients + * @param string $password optional + * @param array $options optional + * @return array + */ + public static function savePrivateKey(BigInteger $n, BigInteger $e, BigInteger $d, array $primes, array $exponents, array $coefficients, $password = '', array $options = []) + { + if (!empty($password) && is_string($password)) { + throw new UnsupportedFormatException('Raw private keys do not support encryption'); + } + + return [ + 'e' => clone $e, + 'n' => clone $n, + 'd' => clone $d, + 'primes' => array_map(function ($var) { + return clone $var; + }, $primes), + 'exponents' => array_map(function ($var) { + return clone $var; + }, $exponents), + 'coefficients' => array_map(function ($var) { + return clone $var; + }, $coefficients) + ]; + } + + /** + * Convert a public key to the appropriate format + * + * @param \phpseclib3\Math\BigInteger $n + * @param \phpseclib3\Math\BigInteger $e + * @return array + */ + public static function savePublicKey(BigInteger $n, BigInteger $e) + { + return ['e' => clone $e, 'n' => clone $n]; + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/XML.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/XML.php new file mode 100644 index 00000000..280048cc --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/XML.php @@ -0,0 +1,171 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\Crypt\RSA\Formats\Keys; + +use phpseclib3\Common\Functions\Strings; +use phpseclib3\Exception\BadConfigurationException; +use phpseclib3\Exception\UnsupportedFormatException; +use phpseclib3\Math\BigInteger; + +/** + * XML Formatted RSA Key Handler + * + * @author Jim Wigginton + */ +abstract class XML +{ + /** + * Break a public or private key down into its constituent components + * + * @param string $key + * @param string $password optional + * @return array + */ + public static function load($key, $password = '') + { + if (!Strings::is_stringable($key)) { + throw new \UnexpectedValueException('Key should be a string - not a ' . gettype($key)); + } + + if (!class_exists('DOMDocument')) { + throw new BadConfigurationException('The dom extension is not setup correctly on this system'); + } + + $components = [ + 'isPublicKey' => false, + 'primes' => [], + 'exponents' => [], + 'coefficients' => [] + ]; + + $use_errors = libxml_use_internal_errors(true); + + $dom = new \DOMDocument(); + if (substr($key, 0, 5) != '' . $key . ''; + } + if (!$dom->loadXML($key)) { + libxml_use_internal_errors($use_errors); + throw new \UnexpectedValueException('Key does not appear to contain XML'); + } + $xpath = new \DOMXPath($dom); + $keys = ['modulus', 'exponent', 'p', 'q', 'dp', 'dq', 'inverseq', 'd']; + foreach ($keys as $key) { + // $dom->getElementsByTagName($key) is case-sensitive + $temp = $xpath->query("//*[translate(local-name(), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz')='$key']"); + if (!$temp->length) { + continue; + } + $value = new BigInteger(Strings::base64_decode($temp->item(0)->nodeValue), 256); + switch ($key) { + case 'modulus': + $components['modulus'] = $value; + break; + case 'exponent': + $components['publicExponent'] = $value; + break; + case 'p': + $components['primes'][1] = $value; + break; + case 'q': + $components['primes'][2] = $value; + break; + case 'dp': + $components['exponents'][1] = $value; + break; + case 'dq': + $components['exponents'][2] = $value; + break; + case 'inverseq': + $components['coefficients'][2] = $value; + break; + case 'd': + $components['privateExponent'] = $value; + } + } + + libxml_use_internal_errors($use_errors); + + foreach ($components as $key => $value) { + if (is_array($value) && !count($value)) { + unset($components[$key]); + } + } + + if (isset($components['modulus']) && isset($components['publicExponent'])) { + if (count($components) == 3) { + $components['isPublicKey'] = true; + } + return $components; + } + + throw new \UnexpectedValueException('Modulus / exponent not present'); + } + + /** + * Convert a private key to the appropriate format. + * + * @param \phpseclib3\Math\BigInteger $n + * @param \phpseclib3\Math\BigInteger $e + * @param \phpseclib3\Math\BigInteger $d + * @param array $primes + * @param array $exponents + * @param array $coefficients + * @param string $password optional + * @return string + */ + public static function savePrivateKey(BigInteger $n, BigInteger $e, BigInteger $d, array $primes, array $exponents, array $coefficients, $password = '') + { + if (count($primes) != 2) { + throw new \InvalidArgumentException('XML does not support multi-prime RSA keys'); + } + + if (!empty($password) && is_string($password)) { + throw new UnsupportedFormatException('XML private keys do not support encryption'); + } + + return "\r\n" . + ' ' . Strings::base64_encode($n->toBytes()) . "\r\n" . + ' ' . Strings::base64_encode($e->toBytes()) . "\r\n" . + '

' . Strings::base64_encode($primes[1]->toBytes()) . "

\r\n" . + ' ' . Strings::base64_encode($primes[2]->toBytes()) . "\r\n" . + ' ' . Strings::base64_encode($exponents[1]->toBytes()) . "\r\n" . + ' ' . Strings::base64_encode($exponents[2]->toBytes()) . "\r\n" . + ' ' . Strings::base64_encode($coefficients[2]->toBytes()) . "\r\n" . + ' ' . Strings::base64_encode($d->toBytes()) . "\r\n" . + '
'; + } + + /** + * Convert a public key to the appropriate format + * + * @param \phpseclib3\Math\BigInteger $n + * @param \phpseclib3\Math\BigInteger $e + * @return string + */ + public static function savePublicKey(BigInteger $n, BigInteger $e) + { + return "\r\n" . + ' ' . Strings::base64_encode($n->toBytes()) . "\r\n" . + ' ' . Strings::base64_encode($e->toBytes()) . "\r\n" . + ''; + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/PrivateKey.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/PrivateKey.php new file mode 100644 index 00000000..84cede8e --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/PrivateKey.php @@ -0,0 +1,530 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\Crypt\RSA; + +use phpseclib3\Crypt\Common; +use phpseclib3\Crypt\Random; +use phpseclib3\Crypt\RSA; +use phpseclib3\Crypt\RSA\Formats\Keys\PSS; +use phpseclib3\Exception\UnsupportedFormatException; +use phpseclib3\Math\BigInteger; + +/** + * Raw RSA Key Handler + * + * @author Jim Wigginton + */ +class PrivateKey extends RSA implements Common\PrivateKey +{ + use Common\Traits\PasswordProtected; + + /** + * Primes for Chinese Remainder Theorem (ie. p and q) + * + * @var array + */ + protected $primes; + + /** + * Exponents for Chinese Remainder Theorem (ie. dP and dQ) + * + * @var array + */ + protected $exponents; + + /** + * Coefficients for Chinese Remainder Theorem (ie. qInv) + * + * @var array + */ + protected $coefficients; + + /** + * Private Exponent + * + * @var \phpseclib3\Math\BigInteger + */ + protected $privateExponent; + + /** + * RSADP + * + * See {@link http://tools.ietf.org/html/rfc3447#section-5.1.2 RFC3447#section-5.1.2}. + * + * @return bool|\phpseclib3\Math\BigInteger + */ + private function rsadp(BigInteger $c) + { + if ($c->compare(self::$zero) < 0 || $c->compare($this->modulus) > 0) { + throw new \OutOfRangeException('Ciphertext representative out of range'); + } + return $this->exponentiate($c); + } + + /** + * RSASP1 + * + * See {@link http://tools.ietf.org/html/rfc3447#section-5.2.1 RFC3447#section-5.2.1}. + * + * @return bool|\phpseclib3\Math\BigInteger + */ + private function rsasp1(BigInteger $m) + { + if ($m->compare(self::$zero) < 0 || $m->compare($this->modulus) > 0) { + throw new \OutOfRangeException('Signature representative out of range'); + } + return $this->exponentiate($m); + } + + /** + * Exponentiate + * + * @param \phpseclib3\Math\BigInteger $x + * @return \phpseclib3\Math\BigInteger + */ + protected function exponentiate(BigInteger $x) + { + switch (true) { + case empty($this->primes): + case $this->primes[1]->equals(self::$zero): + case empty($this->coefficients): + case $this->coefficients[2]->equals(self::$zero): + case empty($this->exponents): + case $this->exponents[1]->equals(self::$zero): + return $x->modPow($this->exponent, $this->modulus); + } + + $num_primes = count($this->primes); + + if (!static::$enableBlinding) { + $m_i = [ + 1 => $x->modPow($this->exponents[1], $this->primes[1]), + 2 => $x->modPow($this->exponents[2], $this->primes[2]) + ]; + $h = $m_i[1]->subtract($m_i[2]); + $h = $h->multiply($this->coefficients[2]); + list(, $h) = $h->divide($this->primes[1]); + $m = $m_i[2]->add($h->multiply($this->primes[2])); + + $r = $this->primes[1]; + for ($i = 3; $i <= $num_primes; $i++) { + $m_i = $x->modPow($this->exponents[$i], $this->primes[$i]); + + $r = $r->multiply($this->primes[$i - 1]); + + $h = $m_i->subtract($m); + $h = $h->multiply($this->coefficients[$i]); + list(, $h) = $h->divide($this->primes[$i]); + + $m = $m->add($r->multiply($h)); + } + } else { + $smallest = $this->primes[1]; + for ($i = 2; $i <= $num_primes; $i++) { + if ($smallest->compare($this->primes[$i]) > 0) { + $smallest = $this->primes[$i]; + } + } + + $r = BigInteger::randomRange(self::$one, $smallest->subtract(self::$one)); + + $m_i = [ + 1 => $this->blind($x, $r, 1), + 2 => $this->blind($x, $r, 2) + ]; + $h = $m_i[1]->subtract($m_i[2]); + $h = $h->multiply($this->coefficients[2]); + list(, $h) = $h->divide($this->primes[1]); + $m = $m_i[2]->add($h->multiply($this->primes[2])); + + $r = $this->primes[1]; + for ($i = 3; $i <= $num_primes; $i++) { + $m_i = $this->blind($x, $r, $i); + + $r = $r->multiply($this->primes[$i - 1]); + + $h = $m_i->subtract($m); + $h = $h->multiply($this->coefficients[$i]); + list(, $h) = $h->divide($this->primes[$i]); + + $m = $m->add($r->multiply($h)); + } + } + + return $m; + } + + /** + * Performs RSA Blinding + * + * Protects against timing attacks by employing RSA Blinding. + * Returns $x->modPow($this->exponents[$i], $this->primes[$i]) + * + * @param \phpseclib3\Math\BigInteger $x + * @param \phpseclib3\Math\BigInteger $r + * @param int $i + * @return \phpseclib3\Math\BigInteger + */ + private function blind(BigInteger $x, BigInteger $r, $i) + { + $x = $x->multiply($r->modPow($this->publicExponent, $this->primes[$i])); + $x = $x->modPow($this->exponents[$i], $this->primes[$i]); + + $r = $r->modInverse($this->primes[$i]); + $x = $x->multiply($r); + list(, $x) = $x->divide($this->primes[$i]); + + return $x; + } + + /** + * EMSA-PSS-ENCODE + * + * See {@link http://tools.ietf.org/html/rfc3447#section-9.1.1 RFC3447#section-9.1.1}. + * + * @return string + * @param string $m + * @throws \RuntimeException on encoding error + * @param int $emBits + */ + private function emsa_pss_encode($m, $emBits) + { + // if $m is larger than two million terrabytes and you're using sha1, PKCS#1 suggests a "Label too long" error + // be output. + + $emLen = ($emBits + 1) >> 3; // ie. ceil($emBits / 8) + $sLen = $this->sLen !== null ? $this->sLen : $this->hLen; + + $mHash = $this->hash->hash($m); + if ($emLen < $this->hLen + $sLen + 2) { + throw new \LengthException('RSA modulus too short'); + } + + $salt = Random::string($sLen); + $m2 = "\0\0\0\0\0\0\0\0" . $mHash . $salt; + $h = $this->hash->hash($m2); + $ps = str_repeat(chr(0), $emLen - $sLen - $this->hLen - 2); + $db = $ps . chr(1) . $salt; + $dbMask = $this->mgf1($h, $emLen - $this->hLen - 1); // ie. stlren($db) + $maskedDB = $db ^ $dbMask; + $maskedDB[0] = ~chr(0xFF << ($emBits & 7)) & $maskedDB[0]; + $em = $maskedDB . $h . chr(0xBC); + + return $em; + } + + /** + * RSASSA-PSS-SIGN + * + * See {@link http://tools.ietf.org/html/rfc3447#section-8.1.1 RFC3447#section-8.1.1}. + * + * @param string $m + * @return bool|string + */ + private function rsassa_pss_sign($m) + { + // EMSA-PSS encoding + + $em = $this->emsa_pss_encode($m, 8 * $this->k - 1); + + // RSA signature + + $m = $this->os2ip($em); + $s = $this->rsasp1($m); + $s = $this->i2osp($s, $this->k); + + // Output the signature S + + return $s; + } + + /** + * RSASSA-PKCS1-V1_5-SIGN + * + * See {@link http://tools.ietf.org/html/rfc3447#section-8.2.1 RFC3447#section-8.2.1}. + * + * @param string $m + * @throws \LengthException if the RSA modulus is too short + * @return bool|string + */ + private function rsassa_pkcs1_v1_5_sign($m) + { + // EMSA-PKCS1-v1_5 encoding + + // If the encoding operation outputs "intended encoded message length too short," output "RSA modulus + // too short" and stop. + try { + $em = $this->emsa_pkcs1_v1_5_encode($m, $this->k); + } catch (\LengthException $e) { + throw new \LengthException('RSA modulus too short'); + } + + // RSA signature + + $m = $this->os2ip($em); + $s = $this->rsasp1($m); + $s = $this->i2osp($s, $this->k); + + // Output the signature S + + return $s; + } + + /** + * Create a signature + * + * @see self::verify() + * @param string $message + * @return string + */ + public function sign($message) + { + switch ($this->signaturePadding) { + case self::SIGNATURE_PKCS1: + case self::SIGNATURE_RELAXED_PKCS1: + return $this->rsassa_pkcs1_v1_5_sign($message); + //case self::SIGNATURE_PSS: + default: + return $this->rsassa_pss_sign($message); + } + } + + /** + * RSAES-PKCS1-V1_5-DECRYPT + * + * See {@link http://tools.ietf.org/html/rfc3447#section-7.2.2 RFC3447#section-7.2.2}. + * + * @param string $c + * @return bool|string + */ + private function rsaes_pkcs1_v1_5_decrypt($c) + { + // Length checking + + if (strlen($c) != $this->k) { // or if k < 11 + throw new \LengthException('Ciphertext representative too long'); + } + + // RSA decryption + + $c = $this->os2ip($c); + $m = $this->rsadp($c); + $em = $this->i2osp($m, $this->k); + + // EME-PKCS1-v1_5 decoding + + if (ord($em[0]) != 0 || ord($em[1]) > 2) { + throw new \RuntimeException('Decryption error'); + } + + $ps = substr($em, 2, strpos($em, chr(0), 2) - 2); + $m = substr($em, strlen($ps) + 3); + + if (strlen($ps) < 8) { + throw new \RuntimeException('Decryption error'); + } + + // Output M + + return $m; + } + + /** + * RSAES-OAEP-DECRYPT + * + * See {@link http://tools.ietf.org/html/rfc3447#section-7.1.2 RFC3447#section-7.1.2}. The fact that the error + * messages aren't distinguishable from one another hinders debugging, but, to quote from RFC3447#section-7.1.2: + * + * Note. Care must be taken to ensure that an opponent cannot + * distinguish the different error conditions in Step 3.g, whether by + * error message or timing, or, more generally, learn partial + * information about the encoded message EM. Otherwise an opponent may + * be able to obtain useful information about the decryption of the + * ciphertext C, leading to a chosen-ciphertext attack such as the one + * observed by Manger [36]. + * + * @param string $c + * @return bool|string + */ + private function rsaes_oaep_decrypt($c) + { + // Length checking + + // if $l is larger than two million terrabytes and you're using sha1, PKCS#1 suggests a "Label too long" error + // be output. + + if (strlen($c) != $this->k || $this->k < 2 * $this->hLen + 2) { + throw new \LengthException('Ciphertext representative too long'); + } + + // RSA decryption + + $c = $this->os2ip($c); + $m = $this->rsadp($c); + $em = $this->i2osp($m, $this->k); + + // EME-OAEP decoding + + $lHash = $this->hash->hash($this->label); + $y = ord($em[0]); + $maskedSeed = substr($em, 1, $this->hLen); + $maskedDB = substr($em, $this->hLen + 1); + $seedMask = $this->mgf1($maskedDB, $this->hLen); + $seed = $maskedSeed ^ $seedMask; + $dbMask = $this->mgf1($seed, $this->k - $this->hLen - 1); + $db = $maskedDB ^ $dbMask; + $lHash2 = substr($db, 0, $this->hLen); + $m = substr($db, $this->hLen); + $hashesMatch = hash_equals($lHash, $lHash2); + $leadingZeros = 1; + $patternMatch = 0; + $offset = 0; + for ($i = 0; $i < strlen($m); $i++) { + $patternMatch |= $leadingZeros & ($m[$i] === "\1"); + $leadingZeros &= $m[$i] === "\0"; + $offset += $patternMatch ? 0 : 1; + } + + // we do | instead of || to avoid https://en.wikipedia.org/wiki/Short-circuit_evaluation + // to protect against timing attacks + if (!$hashesMatch | !$patternMatch) { + throw new \RuntimeException('Decryption error'); + } + + // Output the message M + + return substr($m, $offset + 1); + } + + /** + * Raw Encryption / Decryption + * + * Doesn't use padding and is not recommended. + * + * @param string $m + * @return bool|string + * @throws \LengthException if strlen($m) > $this->k + */ + private function raw_encrypt($m) + { + if (strlen($m) > $this->k) { + throw new \LengthException('Ciphertext representative too long'); + } + + $temp = $this->os2ip($m); + $temp = $this->rsadp($temp); + return $this->i2osp($temp, $this->k); + } + + /** + * Decryption + * + * @see self::encrypt() + * @param string $ciphertext + * @return bool|string + */ + public function decrypt($ciphertext) + { + switch ($this->encryptionPadding) { + case self::ENCRYPTION_NONE: + return $this->raw_encrypt($ciphertext); + case self::ENCRYPTION_PKCS1: + return $this->rsaes_pkcs1_v1_5_decrypt($ciphertext); + //case self::ENCRYPTION_OAEP: + default: + return $this->rsaes_oaep_decrypt($ciphertext); + } + } + + /** + * Returns the public key + * + * @return mixed + */ + public function getPublicKey() + { + $type = self::validatePlugin('Keys', 'PKCS8', 'savePublicKey'); + if (empty($this->modulus) || empty($this->publicExponent)) { + throw new \RuntimeException('Public key components not found'); + } + + $key = $type::savePublicKey($this->modulus, $this->publicExponent); + return RSA::loadFormat('PKCS8', $key) + ->withHash($this->hash->getHash()) + ->withMGFHash($this->mgfHash->getHash()) + ->withSaltLength($this->sLen) + ->withLabel($this->label) + ->withPadding($this->signaturePadding | $this->encryptionPadding); + } + + /** + * Returns the private key + * + * @param string $type + * @param array $options optional + * @return string + */ + public function toString($type, array $options = []) + { + $type = self::validatePlugin( + 'Keys', + $type, + empty($this->primes) ? 'savePublicKey' : 'savePrivateKey' + ); + + if ($type == PSS::class) { + if ($this->signaturePadding == self::SIGNATURE_PSS) { + $options += [ + 'hash' => $this->hash->getHash(), + 'MGFHash' => $this->mgfHash->getHash(), + 'saltLength' => $this->getSaltLength() + ]; + } else { + throw new UnsupportedFormatException('The PSS format can only be used when the signature method has been explicitly set to PSS'); + } + } + + if (empty($this->primes)) { + return $type::savePublicKey($this->modulus, $this->exponent, $options); + } + + return $type::savePrivateKey($this->modulus, $this->publicExponent, $this->exponent, $this->primes, $this->exponents, $this->coefficients, $this->password, $options); + + /* + $key = $type::savePrivateKey($this->modulus, $this->publicExponent, $this->exponent, $this->primes, $this->exponents, $this->coefficients, $this->password, $options); + if ($key !== false || count($this->primes) == 2) { + return $key; + } + + $nSize = $this->getSize() >> 1; + + $primes = [1 => clone self::$one, clone self::$one]; + $i = 1; + foreach ($this->primes as $prime) { + $primes[$i] = $primes[$i]->multiply($prime); + if ($primes[$i]->getLength() >= $nSize) { + $i++; + } + } + + $exponents = []; + $coefficients = [2 => $primes[2]->modInverse($primes[1])]; + + foreach ($primes as $i => $prime) { + $temp = $prime->subtract(self::$one); + $exponents[$i] = $this->modulus->modInverse($temp); + } + + return $type::savePrivateKey($this->modulus, $this->publicExponent, $this->exponent, $primes, $exponents, $coefficients, $this->password, $options); + */ + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/PublicKey.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/PublicKey.php new file mode 100644 index 00000000..89408792 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/PublicKey.php @@ -0,0 +1,513 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\Crypt\RSA; + +use phpseclib3\Common\Functions\Strings; +use phpseclib3\Crypt\Common; +use phpseclib3\Crypt\Hash; +use phpseclib3\Crypt\Random; +use phpseclib3\Crypt\RSA; +use phpseclib3\Crypt\RSA\Formats\Keys\PSS; +use phpseclib3\Exception\UnsupportedAlgorithmException; +use phpseclib3\Exception\UnsupportedFormatException; +use phpseclib3\File\ASN1; +use phpseclib3\File\ASN1\Maps\DigestInfo; +use phpseclib3\Math\BigInteger; + +/** + * Raw RSA Key Handler + * + * @author Jim Wigginton + */ +class PublicKey extends RSA implements Common\PublicKey +{ + use Common\Traits\Fingerprint; + + /** + * Exponentiate + * + * @param \phpseclib3\Math\BigInteger $x + * @return \phpseclib3\Math\BigInteger + */ + private function exponentiate(BigInteger $x) + { + return $x->modPow($this->exponent, $this->modulus); + } + + /** + * RSAVP1 + * + * See {@link http://tools.ietf.org/html/rfc3447#section-5.2.2 RFC3447#section-5.2.2}. + * + * @param \phpseclib3\Math\BigInteger $s + * @return bool|\phpseclib3\Math\BigInteger + */ + private function rsavp1($s) + { + if ($s->compare(self::$zero) < 0 || $s->compare($this->modulus) > 0) { + return false; + } + return $this->exponentiate($s); + } + + /** + * RSASSA-PKCS1-V1_5-VERIFY + * + * See {@link http://tools.ietf.org/html/rfc3447#section-8.2.2 RFC3447#section-8.2.2}. + * + * @param string $m + * @param string $s + * @throws \LengthException if the RSA modulus is too short + * @return bool + */ + private function rsassa_pkcs1_v1_5_verify($m, $s) + { + // Length checking + + if (strlen($s) != $this->k) { + return false; + } + + // RSA verification + + $s = $this->os2ip($s); + $m2 = $this->rsavp1($s); + if ($m2 === false) { + return false; + } + $em = $this->i2osp($m2, $this->k); + if ($em === false) { + return false; + } + + // EMSA-PKCS1-v1_5 encoding + + $exception = false; + + // If the encoding operation outputs "intended encoded message length too short," output "RSA modulus + // too short" and stop. + try { + $em2 = $this->emsa_pkcs1_v1_5_encode($m, $this->k); + $r1 = hash_equals($em, $em2); + } catch (\LengthException $e) { + $exception = true; + } + + try { + $em3 = $this->emsa_pkcs1_v1_5_encode_without_null($m, $this->k); + $r2 = hash_equals($em, $em3); + } catch (\LengthException $e) { + $exception = true; + } catch (UnsupportedAlgorithmException $e) { + $r2 = false; + } + + if ($exception) { + throw new \LengthException('RSA modulus too short'); + } + + // Compare + return $r1 || $r2; + } + + /** + * RSASSA-PKCS1-V1_5-VERIFY (relaxed matching) + * + * Per {@link http://tools.ietf.org/html/rfc3447#page-43 RFC3447#page-43} PKCS1 v1.5 + * specified the use BER encoding rather than DER encoding that PKCS1 v2.0 specified. + * This means that under rare conditions you can have a perfectly valid v1.5 signature + * that fails to validate with _rsassa_pkcs1_v1_5_verify(). PKCS1 v2.1 also recommends + * that if you're going to validate these types of signatures you "should indicate + * whether the underlying BER encoding is a DER encoding and hence whether the signature + * is valid with respect to the specification given in [PKCS1 v2.0+]". so if you do + * $rsa->getLastPadding() and get RSA::PADDING_RELAXED_PKCS1 back instead of + * RSA::PADDING_PKCS1... that means BER encoding was used. + * + * @param string $m + * @param string $s + * @return bool + */ + private function rsassa_pkcs1_v1_5_relaxed_verify($m, $s) + { + // Length checking + + if (strlen($s) != $this->k) { + return false; + } + + // RSA verification + + $s = $this->os2ip($s); + $m2 = $this->rsavp1($s); + if ($m2 === false) { + return false; + } + $em = $this->i2osp($m2, $this->k); + if ($em === false) { + return false; + } + + if (Strings::shift($em, 2) != "\0\1") { + return false; + } + + $em = ltrim($em, "\xFF"); + if (Strings::shift($em) != "\0") { + return false; + } + + $decoded = ASN1::decodeBER($em); + if (!is_array($decoded) || empty($decoded[0]) || strlen($em) > $decoded[0]['length']) { + return false; + } + + static $oids; + if (!isset($oids)) { + $oids = [ + 'md2' => '1.2.840.113549.2.2', + 'md4' => '1.2.840.113549.2.4', // from PKCS1 v1.5 + 'md5' => '1.2.840.113549.2.5', + 'id-sha1' => '1.3.14.3.2.26', + 'id-sha256' => '2.16.840.1.101.3.4.2.1', + 'id-sha384' => '2.16.840.1.101.3.4.2.2', + 'id-sha512' => '2.16.840.1.101.3.4.2.3', + // from PKCS1 v2.2 + 'id-sha224' => '2.16.840.1.101.3.4.2.4', + 'id-sha512/224' => '2.16.840.1.101.3.4.2.5', + 'id-sha512/256' => '2.16.840.1.101.3.4.2.6', + ]; + ASN1::loadOIDs($oids); + } + + $decoded = ASN1::asn1map($decoded[0], DigestInfo::MAP); + if (!isset($decoded) || $decoded === false) { + return false; + } + + if (!isset($oids[$decoded['digestAlgorithm']['algorithm']])) { + return false; + } + + if (isset($decoded['digestAlgorithm']['parameters']) && $decoded['digestAlgorithm']['parameters'] !== ['null' => '']) { + return false; + } + + $hash = $decoded['digestAlgorithm']['algorithm']; + $hash = substr($hash, 0, 3) == 'id-' ? + substr($hash, 3) : + $hash; + $hash = new Hash($hash); + $em = $hash->hash($m); + $em2 = $decoded['digest']; + + return hash_equals($em, $em2); + } + + /** + * EMSA-PSS-VERIFY + * + * See {@link http://tools.ietf.org/html/rfc3447#section-9.1.2 RFC3447#section-9.1.2}. + * + * @param string $m + * @param string $em + * @param int $emBits + * @return string + */ + private function emsa_pss_verify($m, $em, $emBits) + { + // if $m is larger than two million terrabytes and you're using sha1, PKCS#1 suggests a "Label too long" error + // be output. + + $emLen = ($emBits + 7) >> 3; // ie. ceil($emBits / 8); + $sLen = $this->sLen !== null ? $this->sLen : $this->hLen; + + $mHash = $this->hash->hash($m); + if ($emLen < $this->hLen + $sLen + 2) { + return false; + } + + if ($em[strlen($em) - 1] != chr(0xBC)) { + return false; + } + + $maskedDB = substr($em, 0, -$this->hLen - 1); + $h = substr($em, -$this->hLen - 1, $this->hLen); + $temp = chr(0xFF << ($emBits & 7)); + if ((~$maskedDB[0] & $temp) != $temp) { + return false; + } + $dbMask = $this->mgf1($h, $emLen - $this->hLen - 1); + $db = $maskedDB ^ $dbMask; + $db[0] = ~chr(0xFF << ($emBits & 7)) & $db[0]; + $temp = $emLen - $this->hLen - $sLen - 2; + if (substr($db, 0, $temp) != str_repeat(chr(0), $temp) || ord($db[$temp]) != 1) { + return false; + } + $salt = substr($db, $temp + 1); // should be $sLen long + $m2 = "\0\0\0\0\0\0\0\0" . $mHash . $salt; + $h2 = $this->hash->hash($m2); + return hash_equals($h, $h2); + } + + /** + * RSASSA-PSS-VERIFY + * + * See {@link http://tools.ietf.org/html/rfc3447#section-8.1.2 RFC3447#section-8.1.2}. + * + * @param string $m + * @param string $s + * @return bool|string + */ + private function rsassa_pss_verify($m, $s) + { + // Length checking + + if (strlen($s) != $this->k) { + return false; + } + + // RSA verification + + $modBits = strlen($this->modulus->toBits()); + + $s2 = $this->os2ip($s); + $m2 = $this->rsavp1($s2); + $em = $this->i2osp($m2, $this->k); + if ($em === false) { + return false; + } + + // EMSA-PSS verification + + return $this->emsa_pss_verify($m, $em, $modBits - 1); + } + + /** + * Verifies a signature + * + * @see self::sign() + * @param string $message + * @param string $signature + * @return bool + */ + public function verify($message, $signature) + { + switch ($this->signaturePadding) { + case self::SIGNATURE_RELAXED_PKCS1: + return $this->rsassa_pkcs1_v1_5_relaxed_verify($message, $signature); + case self::SIGNATURE_PKCS1: + return $this->rsassa_pkcs1_v1_5_verify($message, $signature); + //case self::SIGNATURE_PSS: + default: + return $this->rsassa_pss_verify($message, $signature); + } + } + + /** + * RSAES-PKCS1-V1_5-ENCRYPT + * + * See {@link http://tools.ietf.org/html/rfc3447#section-7.2.1 RFC3447#section-7.2.1}. + * + * @param string $m + * @param bool $pkcs15_compat optional + * @throws \LengthException if strlen($m) > $this->k - 11 + * @return bool|string + */ + private function rsaes_pkcs1_v1_5_encrypt($m, $pkcs15_compat = false) + { + $mLen = strlen($m); + + // Length checking + + if ($mLen > $this->k - 11) { + throw new \LengthException('Message too long'); + } + + // EME-PKCS1-v1_5 encoding + + $psLen = $this->k - $mLen - 3; + $ps = ''; + while (strlen($ps) != $psLen) { + $temp = Random::string($psLen - strlen($ps)); + $temp = str_replace("\x00", '', $temp); + $ps .= $temp; + } + $type = 2; + $em = chr(0) . chr($type) . $ps . chr(0) . $m; + + // RSA encryption + $m = $this->os2ip($em); + $c = $this->rsaep($m); + $c = $this->i2osp($c, $this->k); + + // Output the ciphertext C + + return $c; + } + + /** + * RSAES-OAEP-ENCRYPT + * + * See {@link http://tools.ietf.org/html/rfc3447#section-7.1.1 RFC3447#section-7.1.1} and + * {http://en.wikipedia.org/wiki/Optimal_Asymmetric_Encryption_Padding OAES}. + * + * @param string $m + * @throws \LengthException if strlen($m) > $this->k - 2 * $this->hLen - 2 + * @return string + */ + private function rsaes_oaep_encrypt($m) + { + $mLen = strlen($m); + + // Length checking + + // if $l is larger than two million terrabytes and you're using sha1, PKCS#1 suggests a "Label too long" error + // be output. + + if ($mLen > $this->k - 2 * $this->hLen - 2) { + throw new \LengthException('Message too long'); + } + + // EME-OAEP encoding + + $lHash = $this->hash->hash($this->label); + $ps = str_repeat(chr(0), $this->k - $mLen - 2 * $this->hLen - 2); + $db = $lHash . $ps . chr(1) . $m; + $seed = Random::string($this->hLen); + $dbMask = $this->mgf1($seed, $this->k - $this->hLen - 1); + $maskedDB = $db ^ $dbMask; + $seedMask = $this->mgf1($maskedDB, $this->hLen); + $maskedSeed = $seed ^ $seedMask; + $em = chr(0) . $maskedSeed . $maskedDB; + + // RSA encryption + + $m = $this->os2ip($em); + $c = $this->rsaep($m); + $c = $this->i2osp($c, $this->k); + + // Output the ciphertext C + + return $c; + } + + /** + * RSAEP + * + * See {@link http://tools.ietf.org/html/rfc3447#section-5.1.1 RFC3447#section-5.1.1}. + * + * @param \phpseclib3\Math\BigInteger $m + * @return bool|\phpseclib3\Math\BigInteger + */ + private function rsaep($m) + { + if ($m->compare(self::$zero) < 0 || $m->compare($this->modulus) > 0) { + throw new \OutOfRangeException('Message representative out of range'); + } + return $this->exponentiate($m); + } + + /** + * Raw Encryption / Decryption + * + * Doesn't use padding and is not recommended. + * + * @param string $m + * @return bool|string + * @throws \LengthException if strlen($m) > $this->k + */ + private function raw_encrypt($m) + { + if (strlen($m) > $this->k) { + throw new \LengthException('Message too long'); + } + + $temp = $this->os2ip($m); + $temp = $this->rsaep($temp); + return $this->i2osp($temp, $this->k); + } + + /** + * Encryption + * + * Both self::PADDING_OAEP and self::PADDING_PKCS1 both place limits on how long $plaintext can be. + * If $plaintext exceeds those limits it will be broken up so that it does and the resultant ciphertext's will + * be concatenated together. + * + * @see self::decrypt() + * @param string $plaintext + * @return bool|string + * @throws \LengthException if the RSA modulus is too short + */ + public function encrypt($plaintext) + { + switch ($this->encryptionPadding) { + case self::ENCRYPTION_NONE: + return $this->raw_encrypt($plaintext); + case self::ENCRYPTION_PKCS1: + return $this->rsaes_pkcs1_v1_5_encrypt($plaintext); + //case self::ENCRYPTION_OAEP: + default: + return $this->rsaes_oaep_encrypt($plaintext); + } + } + + /** + * Returns the public key + * + * The public key is only returned under two circumstances - if the private key had the public key embedded within it + * or if the public key was set via setPublicKey(). If the currently loaded key is supposed to be the public key this + * function won't return it since this library, for the most part, doesn't distinguish between public and private keys. + * + * @param string $type + * @param array $options optional + * @return mixed + */ + public function toString($type, array $options = []) + { + $type = self::validatePlugin('Keys', $type, 'savePublicKey'); + + if ($type == PSS::class) { + if ($this->signaturePadding == self::SIGNATURE_PSS) { + $options += [ + 'hash' => $this->hash->getHash(), + 'MGFHash' => $this->mgfHash->getHash(), + 'saltLength' => $this->getSaltLength() + ]; + } else { + throw new UnsupportedFormatException('The PSS format can only be used when the signature method has been explicitly set to PSS'); + } + } + + return $type::savePublicKey($this->modulus, $this->publicExponent, $options); + } + + /** + * Converts a public key to a private key + * + * @return RSA + */ + public function asPrivateKey() + { + $new = new PrivateKey(); + $new->exponent = $this->exponent; + $new->modulus = $this->modulus; + $new->k = $this->k; + $new->format = $this->format; + return $new + ->withHash($this->hash->getHash()) + ->withMGFHash($this->mgfHash->getHash()) + ->withSaltLength($this->sLen) + ->withLabel($this->label) + ->withPadding($this->signaturePadding | $this->encryptionPadding); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/Random.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Random.php index 8f53eb31..e2c3cb59 100644 --- a/vendor/phpseclib/phpseclib/phpseclib/Crypt/Random.php +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Random.php @@ -10,28 +10,24 @@ * * * - * @category Crypt - * @package Random * @author Jim Wigginton * @copyright 2007 Jim Wigginton * @license http://www.opensource.org/licenses/mit-license.html MIT License * @link http://phpseclib.sourceforge.net */ -namespace phpseclib\Crypt; +namespace phpseclib3\Crypt; /** * Pure-PHP Random Number Generator * - * @package Random * @author Jim Wigginton - * @access public */ -class Random +abstract class Random { /** * Generate a random string. @@ -41,75 +37,26 @@ class Random * eg. for RSA key generation. * * @param int $length + * @throws \RuntimeException if a symmetric cipher is needed but not loaded * @return string */ - static function string($length) + public static function string($length) { if (!$length) { return ''; } - if (version_compare(PHP_VERSION, '7.0.0', '>=')) { - try { - return \random_bytes($length); - } catch (\Throwable $e) { - // If a sufficient source of randomness is unavailable, random_bytes() will throw an - // object that implements the Throwable interface (Exception, TypeError, Error). - // We don't actually need to do anything here. The string() method should just continue - // as normal. Note, however, that if we don't have a sufficient source of randomness for - // random_bytes(), most of the other calls here will fail too, so we'll end up using - // the PHP implementation. - } - } - - if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { - // method 1. prior to PHP 5.3 this would call rand() on windows hence the function_exists('class_alias') call. - // ie. class_alias is a function that was introduced in PHP 5.3 - if (extension_loaded('mcrypt') && function_exists('class_alias')) { - return @mcrypt_create_iv($length); - } - // method 2. openssl_random_pseudo_bytes was introduced in PHP 5.3.0 but prior to PHP 5.3.4 there was, - // to quote , "possible blocking behavior". as of 5.3.4 - // openssl_random_pseudo_bytes and mcrypt_create_iv do the exact same thing on Windows. ie. they both - // call php_win32_get_random_bytes(): - // - // https://github.com/php/php-src/blob/7014a0eb6d1611151a286c0ff4f2238f92c120d6/ext/openssl/openssl.c#L5008 - // https://github.com/php/php-src/blob/7014a0eb6d1611151a286c0ff4f2238f92c120d6/ext/mcrypt/mcrypt.c#L1392 - // - // php_win32_get_random_bytes() is defined thusly: - // - // https://github.com/php/php-src/blob/7014a0eb6d1611151a286c0ff4f2238f92c120d6/win32/winutil.c#L80 - // - // we're calling it, all the same, in the off chance that the mcrypt extension is not available - if (extension_loaded('openssl') && version_compare(PHP_VERSION, '5.3.4', '>=')) { - return openssl_random_pseudo_bytes($length); - } - } else { - // method 1. the fastest - if (extension_loaded('openssl')) { - return openssl_random_pseudo_bytes($length); - } - // method 2 - static $fp = true; - if ($fp === true) { - // warning's will be output unles the error suppression operator is used. errors such as - // "open_basedir restriction in effect", "Permission denied", "No such file or directory", etc. - $fp = @fopen('/dev/urandom', 'rb'); - } - if ($fp !== true && $fp !== false) { // surprisingly faster than !is_bool() or is_resource() - $temp = fread($fp, $length); - if (strlen($temp) == $length) { - return $temp; - } - } - // method 3. pretty much does the same thing as method 2 per the following url: - // https://github.com/php/php-src/blob/7014a0eb6d1611151a286c0ff4f2238f92c120d6/ext/mcrypt/mcrypt.c#L1391 - // surprisingly slower than method 2. maybe that's because mcrypt_create_iv does a bunch of error checking that we're - // not doing. regardless, this'll only be called if this PHP script couldn't open /dev/urandom due to open_basedir - // restrictions or some such - if (extension_loaded('mcrypt')) { - return @mcrypt_create_iv($length, MCRYPT_DEV_URANDOM); - } + try { + return random_bytes($length); + } catch (\Exception $e) { + // random_compat will throw an Exception, which in PHP 5 does not implement Throwable + } catch (\Throwable $e) { + // If a sufficient source of randomness is unavailable, random_bytes() will throw an + // object that implements the Throwable interface (Exception, TypeError, Error). + // We don't actually need to do anything here. The string() method should just continue + // as normal. Note, however, that if we don't have a sufficient source of randomness for + // random_bytes(), most of the other calls here will fail too, so we'll end up using + // the PHP implementation. } // at this point we have no choice but to use a pure-PHP CSPRNG @@ -122,11 +69,11 @@ static function string($length) // PHP isn't low level to be able to use those as sources and on a web server there's not likely // going to be a ton of keyboard or mouse action. web servers do have one thing that we can use // however, a ton of people visiting the website. obviously you don't want to base your seeding - // soley on parameters a potential attacker sends but (1) not everything in $_SERVER is controlled + // solely on parameters a potential attacker sends but (1) not everything in $_SERVER is controlled // by the user and (2) this isn't just looking at the data sent by the current user - it's based // on the data sent by all users. one user requests the page and a hash of their info is saved. // another user visits the page and the serialization of their data is utilized along with the - // server envirnment stuff and a hash of the previous http request data (which itself utilizes + // server environment stuff and a hash of the previous http request data (which itself utilizes // a hash of the session data before that). certainly an attacker should be assumed to have // full control over his own http requests. he, however, is not going to have control over // everyone's http requests. @@ -146,15 +93,14 @@ static function string($length) session_cache_limiter(''); session_start(); - $v = $seed = $_SESSION['seed'] = pack('H*', sha1( - (isset($_SERVER) ? phpseclib_safe_serialize($_SERVER) : '') . - (isset($_POST) ? phpseclib_safe_serialize($_POST) : '') . - (isset($_GET) ? phpseclib_safe_serialize($_GET) : '') . - (isset($_COOKIE) ? phpseclib_safe_serialize($_COOKIE) : '') . - phpseclib_safe_serialize($GLOBALS) . - phpseclib_safe_serialize($_SESSION) . - phpseclib_safe_serialize($_OLD_SESSION) - )); + $v = (isset($_SERVER) ? self::safe_serialize($_SERVER) : '') . + (isset($_POST) ? self::safe_serialize($_POST) : '') . + (isset($_GET) ? self::safe_serialize($_GET) : '') . + (isset($_COOKIE) ? self::safe_serialize($_COOKIE) : '') . + self::safe_serialize($GLOBALS) . + self::safe_serialize($_SESSION) . + self::safe_serialize($_OLD_SESSION); + $v = $seed = $_SESSION['seed'] = sha1($v, true); if (!isset($_SESSION['count'])) { $_SESSION['count'] = 0; } @@ -185,38 +131,37 @@ static function string($length) // http://tools.ietf.org/html/rfc4253#section-7.2 // // see the is_string($crypto) part for an example of how to expand the keys - $key = pack('H*', sha1($seed . 'A')); - $iv = pack('H*', sha1($seed . 'C')); + $key = sha1($seed . 'A', true); + $iv = sha1($seed . 'C', true); // ciphers are used as per the nist.gov link below. also, see this link: // // http://en.wikipedia.org/wiki/Cryptographically_secure_pseudorandom_number_generator#Designs_based_on_cryptographic_primitives switch (true) { - case class_exists('\phpseclib\Crypt\AES'): - $crypto = new AES(Base::MODE_CTR); + case class_exists('\phpseclib3\Crypt\AES'): + $crypto = new AES('ctr'); break; - case class_exists('\phpseclib\Crypt\Twofish'): - $crypto = new Twofish(Base::MODE_CTR); + case class_exists('\phpseclib3\Crypt\Twofish'): + $crypto = new Twofish('ctr'); break; - case class_exists('\phpseclib\Crypt\Blowfish'): - $crypto = new Blowfish(Base::MODE_CTR); + case class_exists('\phpseclib3\Crypt\Blowfish'): + $crypto = new Blowfish('ctr'); break; - case class_exists('\phpseclib\Crypt\TripleDES'): - $crypto = new TripleDES(Base::MODE_CTR); + case class_exists('\phpseclib3\Crypt\TripleDES'): + $crypto = new TripleDES('ctr'); break; - case class_exists('\phpseclib\Crypt\DES'): - $crypto = new DES(Base::MODE_CTR); + case class_exists('\phpseclib3\Crypt\DES'): + $crypto = new DES('ctr'); break; - case class_exists('\phpseclib\Crypt\RC4'): + case class_exists('\phpseclib3\Crypt\RC4'): $crypto = new RC4(); break; default: - user_error(__CLASS__ . ' requires at least one symmetric cipher be loaded'); - return false; + throw new \RuntimeException(__CLASS__ . ' requires at least one symmetric cipher be loaded'); } - $crypto->setKey($key); - $crypto->setIV($iv); + $crypto->setKey(substr($key, 0, $crypto->getKeyLength() >> 3)); + $crypto->setIV(substr($iv, 0, $crypto->getBlockLength() >> 3)); $crypto->enableContinuousBuffer(); } @@ -235,23 +180,20 @@ static function string($length) $i = $crypto->encrypt(microtime()); // strlen(microtime()) == 21 $r = $crypto->encrypt($i ^ $v); // strlen($v) == 20 $v = $crypto->encrypt($r ^ $i); // strlen($r) == 20 - $result.= $r; + $result .= $r; } + return substr($result, 0, $length); } -} -if (!function_exists('phpseclib_safe_serialize')) { /** * Safely serialize variables * - * If a class has a private __sleep() method it'll give a fatal error on PHP 5.2 and earlier. - * PHP 5.3 will emit a warning. - * + * If a class has a private __sleep() it'll emit a warning + * @return mixed * @param mixed $arr - * @access public */ - function phpseclib_safe_serialize(&$arr) + private static function safe_serialize(&$arr) { if (is_object($arr)) { return ''; @@ -263,12 +205,12 @@ function phpseclib_safe_serialize(&$arr) if (isset($arr['__phpseclib_marker'])) { return ''; } - $safearr = array(); + $safearr = []; $arr['__phpseclib_marker'] = true; foreach (array_keys($arr) as $key) { // do not recurse on the '__phpseclib_marker' key itself, for smaller memory usage if ($key !== '__phpseclib_marker') { - $safearr[$key] = phpseclib_safe_serialize($arr[$key]); + $safearr[$key] = self::safe_serialize($arr[$key]); } } unset($arr['__phpseclib_marker']); diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/Rijndael.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Rijndael.php index 3648a197..cd8b7627 100644 --- a/vendor/phpseclib/phpseclib/phpseclib/Crypt/Rijndael.php +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Rijndael.php @@ -30,7 +30,7 @@ * setKey('abcdefghijklmnop'); * @@ -44,135 +44,136 @@ * ?> * * - * @category Crypt - * @package Rijndael * @author Jim Wigginton * @copyright 2008 Jim Wigginton * @license http://www.opensource.org/licenses/mit-license.html MIT License * @link http://phpseclib.sourceforge.net */ -namespace phpseclib\Crypt; +namespace phpseclib3\Crypt; + +use phpseclib3\Common\Functions\Strings; +use phpseclib3\Crypt\Common\BlockCipher; +use phpseclib3\Exception\BadDecryptionException; +use phpseclib3\Exception\BadModeException; +use phpseclib3\Exception\InconsistentSetupException; +use phpseclib3\Exception\InsufficientSetupException; /** * Pure-PHP implementation of Rijndael. * - * @package Rijndael * @author Jim Wigginton - * @access public */ -class Rijndael extends Base +class Rijndael extends BlockCipher { /** * The mcrypt specific name of the cipher * * Mcrypt is useable for 128/192/256-bit $block_size/$key_length. For 160/224 not. - * \phpseclib\Crypt\Rijndael determines automatically whether mcrypt is useable + * \phpseclib3\Crypt\Rijndael determines automatically whether mcrypt is useable * or not for the current $block_size/$key_length. * In case of, $cipher_name_mcrypt will be set dynamically at run time accordingly. * - * @see \phpseclib\Crypt\Base::cipher_name_mcrypt - * @see \phpseclib\Crypt\Base::engine + * @see \phpseclib3\Crypt\Common\SymmetricKey::cipher_name_mcrypt + * @see \phpseclib3\Crypt\Common\SymmetricKey::engine * @see self::isValidEngine() * @var string - * @access private */ - var $cipher_name_mcrypt = 'rijndael-128'; - - /** - * The default salt used by setPassword() - * - * @see \phpseclib\Crypt\Base::password_default_salt - * @see \phpseclib\Crypt\Base::setPassword() - * @var string - * @access private - */ - var $password_default_salt = 'phpseclib'; + protected $cipher_name_mcrypt = 'rijndael-128'; /** * The Key Schedule * - * @see self::_setup() + * @see self::setup() * @var array - * @access private */ - var $w; + private $w; /** * The Inverse Key Schedule * - * @see self::_setup() + * @see self::setup() * @var array - * @access private */ - var $dw; + private $dw; /** * The Block Length divided by 32 * - * @see self::setBlockLength() - * @var int - * @access private - * @internal The max value is 256 / 32 = 8, the min value is 128 / 32 = 4. Exists in conjunction with $block_size + * {@internal The max value is 256 / 32 = 8, the min value is 128 / 32 = 4. Exists in conjunction with $block_size * because the encryption / decryption / key schedule creation requires this number and not $block_size. We could * derive this from $block_size or vice versa, but that'd mean we'd have to do multiple shift operations, so in lieu - * of that, we'll just precompute it once. + * of that, we'll just precompute it once.} + * + * @see self::setBlockLength() + * @var int */ - var $Nb = 4; + private $Nb = 4; /** * The Key Length (in bytes) * - * @see self::setKeyLength() - * @var int - * @access private - * @internal The max value is 256 / 8 = 32, the min value is 128 / 8 = 16. Exists in conjunction with $Nk + * {@internal The max value is 256 / 8 = 32, the min value is 128 / 8 = 16. Exists in conjunction with $Nk * because the encryption / decryption / key schedule creation requires this number and not $key_length. We could * derive this from $key_length or vice versa, but that'd mean we'd have to do multiple shift operations, so in lieu - * of that, we'll just precompute it once. + * of that, we'll just precompute it once.} + * + * @see self::setKeyLength() + * @var int */ - var $key_length = 16; + protected $key_length = 16; /** * The Key Length divided by 32 * * @see self::setKeyLength() * @var int - * @access private * @internal The max value is 256 / 32 = 8, the min value is 128 / 32 = 4 */ - var $Nk = 4; + private $Nk = 4; /** * The Number of Rounds * + * {@internal The max value is 14, the min value is 10.} + * * @var int - * @access private - * @internal The max value is 14, the min value is 10. */ - var $Nr; + private $Nr; /** * Shift offsets * * @var array - * @access private */ - var $c; + private $c; /** * Holds the last used key- and block_size information * * @var array - * @access private */ - var $kl; + private $kl; + + /** + * Default Constructor. + * + * @param string $mode + * @throws \InvalidArgumentException if an invalid / unsupported mode is provided + */ + public function __construct($mode) + { + parent::__construct($mode); + + if ($this->mode == self::MODE_STREAM) { + throw new BadModeException('Block ciphers cannot be ran in stream mode'); + } + } /** * Sets the key length. * - * Valid key lengths are 128, 160, 192, 224, and 256. If the length is less than 128, it will be rounded up to - * 128. If the length is greater than 128 and invalid, it will be rounded down to the closest valid amount. + * Valid key lengths are 128, 160, 192, 224, and 256. * * Note: phpseclib extends Rijndael (and AES) for using 160- and 224-bit keys but they are officially not defined * and the most (if not all) implementations are not able using 160/224-bit keys but round/pad them up to @@ -185,73 +186,111 @@ class Rijndael extends Base * the mcrypt php extension, even if available. * This results then in slower encryption. * - * @access public + * @throws \LengthException if the key length is invalid * @param int $length */ - function setKeyLength($length) + public function setKeyLength($length) { - switch (true) { - case $length <= 128: - $this->key_length = 16; - break; - case $length <= 160: - $this->key_length = 20; - break; - case $length <= 192: - $this->key_length = 24; - break; - case $length <= 224: - $this->key_length = 28; + switch ($length) { + case 128: + case 160: + case 192: + case 224: + case 256: + $this->key_length = $length >> 3; break; default: - $this->key_length = 32; + throw new \LengthException('Key size of ' . $length . ' bits is not supported by this algorithm. Only keys of sizes 128, 160, 192, 224 or 256 bits are supported'); } parent::setKeyLength($length); } + /** + * Sets the key. + * + * Rijndael supports five different key lengths + * + * @see setKeyLength() + * @param string $key + * @throws \LengthException if the key length isn't supported + */ + public function setKey($key) + { + switch (strlen($key)) { + case 16: + case 20: + case 24: + case 28: + case 32: + break; + default: + throw new \LengthException('Key of size ' . strlen($key) . ' not supported by this algorithm. Only keys of sizes 16, 20, 24, 28 or 32 are supported'); + } + + parent::setKey($key); + } + /** * Sets the block length * - * Valid block lengths are 128, 160, 192, 224, and 256. If the length is less than 128, it will be rounded up to - * 128. If the length is greater than 128 and invalid, it will be rounded down to the closest valid amount. + * Valid block lengths are 128, 160, 192, 224, and 256. * - * @access public * @param int $length */ - function setBlockLength($length) + public function setBlockLength($length) { - $length >>= 5; - if ($length > 8) { - $length = 8; - } elseif ($length < 4) { - $length = 4; - } - $this->Nb = $length; - $this->block_size = $length << 2; - $this->changed = true; - $this->_setEngine(); + switch ($length) { + case 128: + case 160: + case 192: + case 224: + case 256: + break; + default: + throw new \LengthException('Key size of ' . $length . ' bits is not supported by this algorithm. Only keys of sizes 128, 160, 192, 224 or 256 bits are supported'); + } + + $this->Nb = $length >> 5; + $this->block_size = $length >> 3; + $this->changed = $this->nonIVChanged = true; + $this->setEngine(); } /** * Test for engine validity * - * This is mainly just a wrapper to set things up for \phpseclib\Crypt\Base::isValidEngine() + * This is mainly just a wrapper to set things up for \phpseclib3\Crypt\Common\SymmetricKey::isValidEngine() * - * @see \phpseclib\Crypt\Base::__construct() + * @see \phpseclib3\Crypt\Common\SymmetricKey::__construct() * @param int $engine - * @access public * @return bool */ - function isValidEngine($engine) + protected function isValidEngineHelper($engine) { switch ($engine) { + case self::ENGINE_LIBSODIUM: + return function_exists('sodium_crypto_aead_aes256gcm_is_available') && + sodium_crypto_aead_aes256gcm_is_available() && + $this->mode == self::MODE_GCM && + $this->key_length == 32 && + $this->nonce && strlen($this->nonce) == 12 && + $this->block_size == 16; + case self::ENGINE_OPENSSL_GCM: + if (!extension_loaded('openssl')) { + return false; + } + $methods = openssl_get_cipher_methods(); + return $this->mode == self::MODE_GCM && + version_compare(PHP_VERSION, '7.1.0', '>=') && + in_array('aes-' . $this->getKeyLength() . '-gcm', $methods) && + $this->block_size == 16; case self::ENGINE_OPENSSL: if ($this->block_size != 16) { return false; } $this->cipher_name_openssl_ecb = 'aes-' . ($this->key_length << 3) . '-ecb'; - $this->cipher_name_openssl = 'aes-' . ($this->key_length << 3) . '-' . $this->_openssl_translate_mode(); + $this->cipher_name_openssl = 'aes-' . ($this->key_length << 3) . '-' . $this->openssl_translate_mode(); break; case self::ENGINE_MCRYPT: $this->cipher_name_mcrypt = 'rijndael-' . ($this->block_size << 3); @@ -261,21 +300,20 @@ function isValidEngine($engine) } } - return parent::isValidEngine($engine); + return parent::isValidEngineHelper($engine); } /** * Encrypts a block * - * @access private * @param string $in * @return string */ - function _encryptBlock($in) + protected function encryptBlock($in) { static $tables; if (empty($tables)) { - $tables = &$this->_getTables(); + $tables = &$this->getTables(); } $t0 = $tables[0]; $t1 = $tables[1]; @@ -283,7 +321,7 @@ function _encryptBlock($in) $t3 = $tables[3]; $sbox = $tables[4]; - $state = array(); + $state = []; $words = unpack('N*', $in); $c = $this->c; @@ -305,7 +343,7 @@ function _encryptBlock($in) // equation (7.4.7) is supposed to use addition instead of subtraction, so we'll do that here, as well. // [1] http://fp.gladman.plus.com/cryptography_technology/rijndael/aes.spec.v316.pdf - $temp = array(); + $temp = []; for ($round = 1; $round < $Nr; ++$round) { $i = 0; // $c[0] == 0 $j = $c[1]; @@ -340,7 +378,7 @@ function _encryptBlock($in) $k = $c[2]; $l = $c[3]; while ($i < $Nb) { - $temp[$i] = ($state[$i] & 0xFF000000) ^ + $temp[$i] = ($state[$i] & intval(0xFF000000)) ^ ($state[$j] & 0x00FF0000) ^ ($state[$k] & 0x0000FF00) ^ ($state[$l] & 0x000000FF) ^ @@ -351,32 +389,20 @@ function _encryptBlock($in) $l = ($l + 1) % $Nb; } - switch ($Nb) { - case 8: - return pack('N*', $temp[0], $temp[1], $temp[2], $temp[3], $temp[4], $temp[5], $temp[6], $temp[7]); - case 7: - return pack('N*', $temp[0], $temp[1], $temp[2], $temp[3], $temp[4], $temp[5], $temp[6]); - case 6: - return pack('N*', $temp[0], $temp[1], $temp[2], $temp[3], $temp[4], $temp[5]); - case 5: - return pack('N*', $temp[0], $temp[1], $temp[2], $temp[3], $temp[4]); - default: - return pack('N*', $temp[0], $temp[1], $temp[2], $temp[3]); - } + return pack('N*', ...$temp); } /** * Decrypts a block * - * @access private * @param string $in * @return string */ - function _decryptBlock($in) + protected function decryptBlock($in) { static $invtables; if (empty($invtables)) { - $invtables = &$this->_getInvTables(); + $invtables = &$this->getInvTables(); } $dt0 = $invtables[0]; $dt1 = $invtables[1]; @@ -384,7 +410,7 @@ function _decryptBlock($in) $dt3 = $invtables[3]; $isbox = $invtables[4]; - $state = array(); + $state = []; $words = unpack('N*', $in); $c = $this->c; @@ -398,7 +424,7 @@ function _decryptBlock($in) $state[] = $word ^ $dw[++$wc]; } - $temp = array(); + $temp = []; for ($round = $Nr - 1; $round > 0; --$round) { $i = 0; // $c[0] == 0 $j = $Nb - $c[1]; @@ -426,7 +452,7 @@ function _decryptBlock($in) $l = $Nb - $c[3]; while ($i < $Nb) { - $word = ($state[$i] & 0xFF000000) | + $word = ($state[$i] & intval(0xFF000000)) | ($state[$j] & 0x00FF0000) | ($state[$k] & 0x0000FF00) | ($state[$l] & 0x000000FF); @@ -441,44 +467,75 @@ function _decryptBlock($in) $l = ($l + 1) % $Nb; } - switch ($Nb) { - case 8: - return pack('N*', $temp[0], $temp[1], $temp[2], $temp[3], $temp[4], $temp[5], $temp[6], $temp[7]); - case 7: - return pack('N*', $temp[0], $temp[1], $temp[2], $temp[3], $temp[4], $temp[5], $temp[6]); - case 6: - return pack('N*', $temp[0], $temp[1], $temp[2], $temp[3], $temp[4], $temp[5]); - case 5: - return pack('N*', $temp[0], $temp[1], $temp[2], $temp[3], $temp[4]); - default: - return pack('N*', $temp[0], $temp[1], $temp[2], $temp[3]); + return pack('N*', ...$temp); + } + + /** + * Setup the self::ENGINE_INTERNAL $engine + * + * (re)init, if necessary, the internal cipher $engine and flush all $buffers + * Used (only) if $engine == self::ENGINE_INTERNAL + * + * _setup() will be called each time if $changed === true + * typically this happens when using one or more of following public methods: + * + * - setKey() + * + * - setIV() + * + * - disableContinuousBuffer() + * + * - First run of encrypt() / decrypt() with no init-settings + * + * {@internal setup() is always called before en/decryption.} + * + * {@internal Could, but not must, extend by the child Crypt_* class} + * + * @see self::setKey() + * @see self::setIV() + * @see self::disableContinuousBuffer() + */ + protected function setup() + { + if (!$this->changed) { + return; + } + + parent::setup(); + + if (is_string($this->iv) && strlen($this->iv) != $this->block_size) { + throw new InconsistentSetupException('The IV length (' . strlen($this->iv) . ') does not match the block size (' . $this->block_size . ')'); } } /** * Setup the key (expansion) * - * @see \phpseclib\Crypt\Base::_setupKey() - * @access private + * @see \phpseclib3\Crypt\Common\SymmetricKey::setupKey() */ - function _setupKey() + protected function setupKey() { // Each number in $rcon is equal to the previous number multiplied by two in Rijndael's finite field. // See http://en.wikipedia.org/wiki/Finite_field_arithmetic#Multiplicative_inverse - static $rcon = array(0, - 0x01000000, 0x02000000, 0x04000000, 0x08000000, 0x10000000, - 0x20000000, 0x40000000, 0x80000000, 0x1B000000, 0x36000000, - 0x6C000000, 0xD8000000, 0xAB000000, 0x4D000000, 0x9A000000, - 0x2F000000, 0x5E000000, 0xBC000000, 0x63000000, 0xC6000000, - 0x97000000, 0x35000000, 0x6A000000, 0xD4000000, 0xB3000000, - 0x7D000000, 0xFA000000, 0xEF000000, 0xC5000000, 0x91000000 - ); + static $rcon; + + if (!isset($rcon)) { + $rcon = [0, + 0x01000000, 0x02000000, 0x04000000, 0x08000000, 0x10000000, + 0x20000000, 0x40000000, 0x80000000, 0x1B000000, 0x36000000, + 0x6C000000, 0xD8000000, 0xAB000000, 0x4D000000, 0x9A000000, + 0x2F000000, 0x5E000000, 0xBC000000, 0x63000000, 0xC6000000, + 0x97000000, 0x35000000, 0x6A000000, 0xD4000000, 0xB3000000, + 0x7D000000, 0xFA000000, 0xEF000000, 0xC5000000, 0x91000000 + ]; + $rcon = array_map('intval', $rcon); + } if (isset($this->kl['key']) && $this->key === $this->kl['key'] && $this->key_length === $this->kl['key_length'] && $this->block_size === $this->kl['block_size']) { // already expanded return; } - $this->kl = array('key' => $this->key, 'key_length' => $this->key_length, 'block_size' => $this->block_size); + $this->kl = ['key' => $this->key, 'key_length' => $this->key_length, 'block_size' => $this->block_size]; $this->Nk = $this->key_length >> 2; // see Rijndael-ammended.pdf#page=44 @@ -492,13 +549,13 @@ function _setupKey() case 4: case 5: case 6: - $this->c = array(0, 1, 2, 3); + $this->c = [0, 1, 2, 3]; break; case 7: - $this->c = array(0, 1, 2, 4); + $this->c = [0, 1, 2, 4]; break; case 8: - $this->c = array(0, 1, 3, 4); + $this->c = [0, 1, 3, 4]; } $w = array_values(unpack('N*words', $this->key)); @@ -511,10 +568,10 @@ function _setupKey() // on a 32-bit machine, it's 32-bits, and on a 64-bit machine, it's 64-bits. on a 32-bit machine, // 0xFFFFFFFF << 8 == 0xFFFFFF00, but on a 64-bit machine, it equals 0xFFFFFFFF00. as such, doing 'and' // with 0xFFFFFFFF (or 0xFFFFFF00) on a 32-bit machine is unnecessary, but on a 64-bit machine, it is. - $temp = (($temp << 8) & 0xFFFFFF00) | (($temp >> 24) & 0x000000FF); // rotWord - $temp = $this->_subWord($temp) ^ $rcon[$i / $this->Nk]; + $temp = (($temp << 8) & intval(0xFFFFFF00)) | (($temp >> 24) & 0x000000FF); // rotWord + $temp = $this->subWord($temp) ^ $rcon[$i / $this->Nk]; } elseif ($this->Nk > 6 && $i % $this->Nk == 4) { - $temp = $this->_subWord($temp); + $temp = $this->subWord($temp); } $w[$i] = $w[$i - $this->Nk] ^ $temp; } @@ -526,8 +583,8 @@ function _setupKey() // 1. Apply the Key Expansion. // 2. Apply InvMixColumn to all Round Keys except the first and the last one." // also, see fips-197.pdf#page=27, "5.3.5 Equivalent Inverse Cipher" - list($dt0, $dt1, $dt2, $dt3) = $this->_getInvTables(); - $temp = $this->w = $this->dw = array(); + list($dt0, $dt1, $dt2, $dt3) = $this->getInvTables(); + $temp = $this->w = $this->dw = []; for ($i = $row = $col = 0; $i < $length; $i++, $col++) { if ($col == $this->Nb) { if ($row == 0) { @@ -536,7 +593,7 @@ function _setupKey() // subWord + invMixColumn + invSubWord = invMixColumn $j = 0; while ($j < $this->Nb) { - $dw = $this->_subWord($this->w[$row][$j]); + $dw = $this->subWord($this->w[$row][$j]); $temp[$j] = $dt0[$dw >> 24 & 0x000000FF] ^ $dt1[$dw >> 16 & 0x000000FF] ^ $dt2[$dw >> 8 & 0x000000FF] ^ @@ -571,14 +628,14 @@ function _setupKey() /** * Performs S-Box substitutions * - * @access private + * @return array * @param int $word */ - function _subWord($word) + private function subWord($word) { static $sbox; if (empty($sbox)) { - list(, , , , $sbox) = $this->_getTables(); + list(, , , , $sbox) = self::getTables(); } return $sbox[$word & 0x000000FF] | @@ -590,20 +647,19 @@ function _subWord($word) /** * Provides the mixColumns and sboxes tables * - * @see self::_encryptBlock() - * @see self::_setupInlineCrypt() - * @see self::_subWord() - * @access private + * @see self::encryptBlock() + * @see self::setupInlineCrypt() + * @see self::subWord() * @return array &$tables */ - function &_getTables() + protected function &getTables() { static $tables; if (empty($tables)) { // according to (section 5.2.1), // precomputed tables can be used in the mixColumns phase. in that example, they're assigned t0...t3, so // those are the names we'll use. - $t3 = array_map('intval', array( + $t3 = array_map('intval', [ // with array_map('intval', ...) we ensure we have only int's and not // some slower floats converted by php automatically on high values 0x6363A5C6, 0x7C7C84F8, 0x777799EE, 0x7B7B8DF6, 0xF2F20DFF, 0x6B6BBDD6, 0x6F6FB1DE, 0xC5C55491, @@ -638,22 +694,22 @@ function &_getTables() 0x9B9BB62D, 0x1E1E223C, 0x87879215, 0xE9E920C9, 0xCECE4987, 0x5555FFAA, 0x28287850, 0xDFDF7AA5, 0x8C8C8F03, 0xA1A1F859, 0x89898009, 0x0D0D171A, 0xBFBFDA65, 0xE6E631D7, 0x4242C684, 0x6868B8D0, 0x4141C382, 0x9999B029, 0x2D2D775A, 0x0F0F111E, 0xB0B0CB7B, 0x5454FCA8, 0xBBBBD66D, 0x16163A2C - )); + ]); foreach ($t3 as $t3i) { - $t0[] = (($t3i << 24) & 0xFF000000) | (($t3i >> 8) & 0x00FFFFFF); - $t1[] = (($t3i << 16) & 0xFFFF0000) | (($t3i >> 16) & 0x0000FFFF); - $t2[] = (($t3i << 8) & 0xFFFFFF00) | (($t3i >> 24) & 0x000000FF); + $t0[] = (($t3i << 24) & intval(0xFF000000)) | (($t3i >> 8) & 0x00FFFFFF); + $t1[] = (($t3i << 16) & intval(0xFFFF0000)) | (($t3i >> 16) & 0x0000FFFF); + $t2[] = (($t3i << 8) & intval(0xFFFFFF00)) | (($t3i >> 24) & 0x000000FF); } - $tables = array( + $tables = [ // The Precomputed mixColumns tables t0 - t3 $t0, $t1, $t2, $t3, // The SubByte S-Box - array( + [ 0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76, 0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0, 0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15, @@ -670,8 +726,8 @@ function &_getTables() 0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E, 0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF, 0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16 - ) - ); + ] + ]; } return $tables; } @@ -679,17 +735,16 @@ function &_getTables() /** * Provides the inverse mixColumns and inverse sboxes tables * - * @see self::_decryptBlock() - * @see self::_setupInlineCrypt() - * @see self::_setupKey() - * @access private + * @see self::decryptBlock() + * @see self::setupInlineCrypt() + * @see self::setupKey() * @return array &$tables */ - function &_getInvTables() + protected function &getInvTables() { static $tables; if (empty($tables)) { - $dt3 = array_map('intval', array( + $dt3 = array_map('intval', [ 0xF4A75051, 0x4165537E, 0x17A4C31A, 0x275E963A, 0xAB6BCB3B, 0x9D45F11F, 0xFA58ABAC, 0xE303934B, 0x30FA5520, 0x766DF6AD, 0xCC769188, 0x024C25F5, 0xE5D7FC4F, 0x2ACBD7C5, 0x35448026, 0x62A38FB5, 0xB15A49DE, 0xBA1B6725, 0xEA0E9845, 0xFEC0E15D, 0x2F7502C3, 0x4CF01281, 0x4697A38D, 0xD3F9C66B, @@ -722,22 +777,22 @@ function &_getInvTables() 0xD2DF599C, 0xF2733F55, 0x14CE7918, 0xC737BF73, 0xF7CDEA53, 0xFDAA5B5F, 0x3D6F14DF, 0x44DB8678, 0xAFF381CA, 0x68C43EB9, 0x24342C38, 0xA3405FC2, 0x1DC37216, 0xE2250CBC, 0x3C498B28, 0x0D9541FF, 0xA8017139, 0x0CB3DE08, 0xB4E49CD8, 0x56C19064, 0xCB84617B, 0x32B670D5, 0x6C5C7448, 0xB85742D0 - )); + ]); foreach ($dt3 as $dt3i) { - $dt0[] = (($dt3i << 24) & 0xFF000000) | (($dt3i >> 8) & 0x00FFFFFF); - $dt1[] = (($dt3i << 16) & 0xFFFF0000) | (($dt3i >> 16) & 0x0000FFFF); - $dt2[] = (($dt3i << 8) & 0xFFFFFF00) | (($dt3i >> 24) & 0x000000FF); + $dt0[] = (($dt3i << 24) & intval(0xFF000000)) | (($dt3i >> 8) & 0x00FFFFFF); + $dt1[] = (($dt3i << 16) & intval(0xFFFF0000)) | (($dt3i >> 16) & 0x0000FFFF); + $dt2[] = (($dt3i << 8) & intval(0xFFFFFF00)) | (($dt3i >> 24) & 0x000000FF); }; - $tables = array( + $tables = [ // The Precomputed inverse mixColumns tables dt0 - dt3 $dt0, $dt1, $dt2, $dt3, // The inverse SubByte S-Box - array( + [ 0x52, 0x09, 0x6A, 0xD5, 0x30, 0x36, 0xA5, 0x38, 0xBF, 0x40, 0xA3, 0x9E, 0x81, 0xF3, 0xD7, 0xFB, 0x7C, 0xE3, 0x39, 0x82, 0x9B, 0x2F, 0xFF, 0x87, 0x34, 0x8E, 0x43, 0x44, 0xC4, 0xDE, 0xE9, 0xCB, 0x54, 0x7B, 0x94, 0x32, 0xA6, 0xC2, 0x23, 0x3D, 0xEE, 0x4C, 0x95, 0x0B, 0x42, 0xFA, 0xC3, 0x4E, @@ -754,8 +809,8 @@ function &_getInvTables() 0x60, 0x51, 0x7F, 0xA9, 0x19, 0xB5, 0x4A, 0x0D, 0x2D, 0xE5, 0x7A, 0x9F, 0x93, 0xC9, 0x9C, 0xEF, 0xA0, 0xE0, 0x3B, 0x4D, 0xAE, 0x2A, 0xF5, 0xB0, 0xC8, 0xEB, 0xBB, 0x3C, 0x83, 0x53, 0x99, 0x61, 0x17, 0x2B, 0x04, 0x7E, 0xBA, 0x77, 0xD6, 0x26, 0xE1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0C, 0x7D - ) - ); + ] + ]; } return $tables; } @@ -763,174 +818,221 @@ function &_getInvTables() /** * Setup the performance-optimized function for de/encrypt() * - * @see \phpseclib\Crypt\Base::_setupInlineCrypt() - * @access private + * @see \phpseclib3\Crypt\Common\SymmetricKey::setupInlineCrypt() */ - function _setupInlineCrypt() + protected function setupInlineCrypt() { - // Note: _setupInlineCrypt() will be called only if $this->changed === true - // So here we are'nt under the same heavy timing-stress as we are in _de/encryptBlock() or de/encrypt(). - // However...the here generated function- $code, stored as php callback in $this->inline_crypt, must work as fast as even possible. - - $lambda_functions =& self::_getLambdaFunctions(); - - // We create max. 10 hi-optimized code for memory reason. Means: For each $key one ultra fast inline-crypt function. - // (Currently, for Crypt_Rijndael/AES, one generated $lambda_function cost on php5.5@32bit ~80kb unfreeable mem and ~130kb on php5.5@64bit) - // After that, we'll still create very fast optimized code but not the hi-ultimative code, for each $mode one. - $gen_hi_opt_code = (bool)(count($lambda_functions) < 10); - - // Generation of a uniqe hash for our generated code - $code_hash = "Crypt_Rijndael, {$this->mode}, {$this->Nr}, {$this->Nb}"; - if ($gen_hi_opt_code) { - $code_hash = str_pad($code_hash, 32) . $this->_hashInlineCryptFunction($this->key); - } - - if (!isset($lambda_functions[$code_hash])) { - switch (true) { - case $gen_hi_opt_code: - // The hi-optimized $lambda_functions will use the key-words hardcoded for better performance. - $w = $this->w; - $dw = $this->dw; - $init_encrypt = ''; - $init_decrypt = ''; - break; - default: - for ($i = 0, $cw = count($this->w); $i < $cw; ++$i) { - $w[] = '$w[' . $i . ']'; - $dw[] = '$dw[' . $i . ']'; - } - $init_encrypt = '$w = $self->w;'; - $init_decrypt = '$dw = $self->dw;'; - } + $w = $this->w; + $dw = $this->dw; + $init_encrypt = ''; + $init_decrypt = ''; - $Nr = $this->Nr; - $Nb = $this->Nb; - $c = $this->c; + $Nr = $this->Nr; + $Nb = $this->Nb; + $c = $this->c; - // Generating encrypt code: - $init_encrypt.= ' - static $tables; - if (empty($tables)) { - $tables = &$self->_getTables(); - } - $t0 = $tables[0]; - $t1 = $tables[1]; - $t2 = $tables[2]; - $t3 = $tables[3]; - $sbox = $tables[4]; - '; - - $s = 'e'; - $e = 's'; - $wc = $Nb - 1; - - // Preround: addRoundKey - $encrypt_block = '$in = unpack("N*", $in);'."\n"; - for ($i = 0; $i < $Nb; ++$i) { - $encrypt_block .= '$s'.$i.' = $in['.($i + 1).'] ^ '.$w[++$wc].";\n"; + // Generating encrypt code: + $init_encrypt .= ' + static $tables; + if (empty($tables)) { + $tables = &$this->getTables(); } + $t0 = $tables[0]; + $t1 = $tables[1]; + $t2 = $tables[2]; + $t3 = $tables[3]; + $sbox = $tables[4]; + '; + + $s = 'e'; + $e = 's'; + $wc = $Nb - 1; - // Mainrounds: shiftRows + subWord + mixColumns + addRoundKey - for ($round = 1; $round < $Nr; ++$round) { - list($s, $e) = array($e, $s); - for ($i = 0; $i < $Nb; ++$i) { - $encrypt_block.= - '$'.$e.$i.' = - $t0[($'.$s.$i .' >> 24) & 0xff] ^ - $t1[($'.$s.(($i + $c[1]) % $Nb).' >> 16) & 0xff] ^ - $t2[($'.$s.(($i + $c[2]) % $Nb).' >> 8) & 0xff] ^ - $t3[ $'.$s.(($i + $c[3]) % $Nb).' & 0xff] ^ - '.$w[++$wc].";\n"; - } - } + // Preround: addRoundKey + $encrypt_block = '$in = unpack("N*", $in);' . "\n"; + for ($i = 0; $i < $Nb; ++$i) { + $encrypt_block .= '$s' . $i . ' = $in[' . ($i + 1) . '] ^ ' . $w[++$wc] . ";\n"; + } - // Finalround: subWord + shiftRows + addRoundKey + // Mainrounds: shiftRows + subWord + mixColumns + addRoundKey + for ($round = 1; $round < $Nr; ++$round) { + list($s, $e) = [$e, $s]; for ($i = 0; $i < $Nb; ++$i) { - $encrypt_block.= - '$'.$e.$i.' = - $sbox[ $'.$e.$i.' & 0xff] | - ($sbox[($'.$e.$i.' >> 8) & 0xff] << 8) | - ($sbox[($'.$e.$i.' >> 16) & 0xff] << 16) | - ($sbox[($'.$e.$i.' >> 24) & 0xff] << 24);'."\n"; + $encrypt_block .= + '$' . $e . $i . ' = + $t0[($' . $s . $i . ' >> 24) & 0xff] ^ + $t1[($' . $s . (($i + $c[1]) % $Nb) . ' >> 16) & 0xff] ^ + $t2[($' . $s . (($i + $c[2]) % $Nb) . ' >> 8) & 0xff] ^ + $t3[ $' . $s . (($i + $c[3]) % $Nb) . ' & 0xff] ^ + ' . $w[++$wc] . ";\n"; } - $encrypt_block .= '$in = pack("N*"'."\n"; - for ($i = 0; $i < $Nb; ++$i) { - $encrypt_block.= ', - ($'.$e.$i .' & '.((int)0xFF000000).') ^ - ($'.$e.(($i + $c[1]) % $Nb).' & 0x00FF0000 ) ^ - ($'.$e.(($i + $c[2]) % $Nb).' & 0x0000FF00 ) ^ - ($'.$e.(($i + $c[3]) % $Nb).' & 0x000000FF ) ^ - '.$w[$i]."\n"; + } + + // Finalround: subWord + shiftRows + addRoundKey + for ($i = 0; $i < $Nb; ++$i) { + $encrypt_block .= + '$' . $e . $i . ' = + $sbox[ $' . $e . $i . ' & 0xff] | + ($sbox[($' . $e . $i . ' >> 8) & 0xff] << 8) | + ($sbox[($' . $e . $i . ' >> 16) & 0xff] << 16) | + ($sbox[($' . $e . $i . ' >> 24) & 0xff] << 24);' . "\n"; + } + $encrypt_block .= '$in = pack("N*"' . "\n"; + for ($i = 0; $i < $Nb; ++$i) { + $encrypt_block .= ', + ($' . $e . $i . ' & ' . ((int)0xFF000000) . ') ^ + ($' . $e . (($i + $c[1]) % $Nb) . ' & 0x00FF0000 ) ^ + ($' . $e . (($i + $c[2]) % $Nb) . ' & 0x0000FF00 ) ^ + ($' . $e . (($i + $c[3]) % $Nb) . ' & 0x000000FF ) ^ + ' . $w[$i] . "\n"; + } + $encrypt_block .= ');'; + + // Generating decrypt code: + $init_decrypt .= ' + static $invtables; + if (empty($invtables)) { + $invtables = &$this->getInvTables(); } - $encrypt_block .= ');'; + $dt0 = $invtables[0]; + $dt1 = $invtables[1]; + $dt2 = $invtables[2]; + $dt3 = $invtables[3]; + $isbox = $invtables[4]; + '; + + $s = 'e'; + $e = 's'; + $wc = $Nb - 1; - // Generating decrypt code: - $init_decrypt.= ' - static $invtables; - if (empty($invtables)) { - $invtables = &$self->_getInvTables(); - } - $dt0 = $invtables[0]; - $dt1 = $invtables[1]; - $dt2 = $invtables[2]; - $dt3 = $invtables[3]; - $isbox = $invtables[4]; - '; - - $s = 'e'; - $e = 's'; - $wc = $Nb - 1; - - // Preround: addRoundKey - $decrypt_block = '$in = unpack("N*", $in);'."\n"; + // Preround: addRoundKey + $decrypt_block = '$in = unpack("N*", $in);' . "\n"; + for ($i = 0; $i < $Nb; ++$i) { + $decrypt_block .= '$s' . $i . ' = $in[' . ($i + 1) . '] ^ ' . $dw[++$wc] . ';' . "\n"; + } + + // Mainrounds: shiftRows + subWord + mixColumns + addRoundKey + for ($round = 1; $round < $Nr; ++$round) { + list($s, $e) = [$e, $s]; for ($i = 0; $i < $Nb; ++$i) { - $decrypt_block .= '$s'.$i.' = $in['.($i + 1).'] ^ '.$dw[++$wc].';'."\n"; + $decrypt_block .= + '$' . $e . $i . ' = + $dt0[($' . $s . $i . ' >> 24) & 0xff] ^ + $dt1[($' . $s . (($Nb + $i - $c[1]) % $Nb) . ' >> 16) & 0xff] ^ + $dt2[($' . $s . (($Nb + $i - $c[2]) % $Nb) . ' >> 8) & 0xff] ^ + $dt3[ $' . $s . (($Nb + $i - $c[3]) % $Nb) . ' & 0xff] ^ + ' . $dw[++$wc] . ";\n"; } + } + + // Finalround: subWord + shiftRows + addRoundKey + for ($i = 0; $i < $Nb; ++$i) { + $decrypt_block .= + '$' . $e . $i . ' = + $isbox[ $' . $e . $i . ' & 0xff] | + ($isbox[($' . $e . $i . ' >> 8) & 0xff] << 8) | + ($isbox[($' . $e . $i . ' >> 16) & 0xff] << 16) | + ($isbox[($' . $e . $i . ' >> 24) & 0xff] << 24);' . "\n"; + } + $decrypt_block .= '$in = pack("N*"' . "\n"; + for ($i = 0; $i < $Nb; ++$i) { + $decrypt_block .= ', + ($' . $e . $i . ' & ' . ((int)0xFF000000) . ') ^ + ($' . $e . (($Nb + $i - $c[1]) % $Nb) . ' & 0x00FF0000 ) ^ + ($' . $e . (($Nb + $i - $c[2]) % $Nb) . ' & 0x0000FF00 ) ^ + ($' . $e . (($Nb + $i - $c[3]) % $Nb) . ' & 0x000000FF ) ^ + ' . $dw[$i] . "\n"; + } + $decrypt_block .= ');'; + + $this->inline_crypt = $this->createInlineCryptFunction( + [ + 'init_crypt' => '', + 'init_encrypt' => $init_encrypt, + 'init_decrypt' => $init_decrypt, + 'encrypt_block' => $encrypt_block, + 'decrypt_block' => $decrypt_block + ] + ); + } + + /** + * Encrypts a message. + * + * @see self::decrypt() + * @see parent::encrypt() + * @param string $plaintext + * @return string + */ + public function encrypt($plaintext) + { + $this->setup(); + + switch ($this->engine) { + case self::ENGINE_LIBSODIUM: + $this->newtag = sodium_crypto_aead_aes256gcm_encrypt($plaintext, $this->aad, $this->nonce, $this->key); + return Strings::shift($this->newtag, strlen($plaintext)); + case self::ENGINE_OPENSSL_GCM: + return openssl_encrypt( + $plaintext, + 'aes-' . $this->getKeyLength() . '-gcm', + $this->key, + OPENSSL_RAW_DATA, + $this->nonce, + $this->newtag, + $this->aad + ); + } + + return parent::encrypt($plaintext); + } - // Mainrounds: shiftRows + subWord + mixColumns + addRoundKey - for ($round = 1; $round < $Nr; ++$round) { - list($s, $e) = array($e, $s); - for ($i = 0; $i < $Nb; ++$i) { - $decrypt_block.= - '$'.$e.$i.' = - $dt0[($'.$s.$i .' >> 24) & 0xff] ^ - $dt1[($'.$s.(($Nb + $i - $c[1]) % $Nb).' >> 16) & 0xff] ^ - $dt2[($'.$s.(($Nb + $i - $c[2]) % $Nb).' >> 8) & 0xff] ^ - $dt3[ $'.$s.(($Nb + $i - $c[3]) % $Nb).' & 0xff] ^ - '.$dw[++$wc].";\n"; + /** + * Decrypts a message. + * + * @see self::encrypt() + * @see parent::decrypt() + * @param string $ciphertext + * @return string + */ + public function decrypt($ciphertext) + { + $this->setup(); + + switch ($this->engine) { + case self::ENGINE_LIBSODIUM: + if ($this->oldtag === false) { + throw new InsufficientSetupException('Authentication Tag has not been set'); } - } + if (strlen($this->oldtag) != 16) { + break; + } + $plaintext = sodium_crypto_aead_aes256gcm_decrypt($ciphertext . $this->oldtag, $this->aad, $this->nonce, $this->key); + if ($plaintext === false) { + $this->oldtag = false; + throw new BadDecryptionException('Error decrypting ciphertext with libsodium'); + } + return $plaintext; + case self::ENGINE_OPENSSL_GCM: + if ($this->oldtag === false) { + throw new InsufficientSetupException('Authentication Tag has not been set'); + } + $plaintext = openssl_decrypt( + $ciphertext, + 'aes-' . $this->getKeyLength() . '-gcm', + $this->key, + OPENSSL_RAW_DATA, + $this->nonce, + $this->oldtag, + $this->aad + ); + if ($plaintext === false) { + $this->oldtag = false; + throw new BadDecryptionException('Error decrypting ciphertext with OpenSSL'); + } + return $plaintext; + } - // Finalround: subWord + shiftRows + addRoundKey - for ($i = 0; $i < $Nb; ++$i) { - $decrypt_block.= - '$'.$e.$i.' = - $isbox[ $'.$e.$i.' & 0xff] | - ($isbox[($'.$e.$i.' >> 8) & 0xff] << 8) | - ($isbox[($'.$e.$i.' >> 16) & 0xff] << 16) | - ($isbox[($'.$e.$i.' >> 24) & 0xff] << 24);'."\n"; - } - $decrypt_block .= '$in = pack("N*"'."\n"; - for ($i = 0; $i < $Nb; ++$i) { - $decrypt_block.= ', - ($'.$e.$i. ' & '.((int)0xFF000000).') ^ - ($'.$e.(($Nb + $i - $c[1]) % $Nb).' & 0x00FF0000 ) ^ - ($'.$e.(($Nb + $i - $c[2]) % $Nb).' & 0x0000FF00 ) ^ - ($'.$e.(($Nb + $i - $c[3]) % $Nb).' & 0x000000FF ) ^ - '.$dw[$i]."\n"; - } - $decrypt_block .= ');'; - - $lambda_functions[$code_hash] = $this->_createInlineCryptFunction( - array( - 'init_crypt' => '', - 'init_encrypt' => $init_encrypt, - 'init_decrypt' => $init_decrypt, - 'encrypt_block' => $encrypt_block, - 'decrypt_block' => $decrypt_block - ) - ); - } - $this->inline_crypt = $lambda_functions[$code_hash]; + return parent::decrypt($ciphertext); } } diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/Salsa20.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Salsa20.php new file mode 100644 index 00000000..0a35f478 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Salsa20.php @@ -0,0 +1,526 @@ + + * @copyright 2019 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\Crypt; + +use phpseclib3\Common\Functions\Strings; +use phpseclib3\Crypt\Common\StreamCipher; +use phpseclib3\Exception\BadDecryptionException; +use phpseclib3\Exception\InsufficientSetupException; + +/** + * Pure-PHP implementation of Salsa20. + * + * @author Jim Wigginton + */ +class Salsa20 extends StreamCipher +{ + /** + * Part 1 of the state + * + * @var string|false + */ + protected $p1 = false; + + /** + * Part 2 of the state + * + * @var string|false + */ + protected $p2 = false; + + /** + * Key Length (in bytes) + * + * @var int + */ + protected $key_length = 32; // = 256 bits + + /** + * @see \phpseclib3\Crypt\Salsa20::crypt() + */ + const ENCRYPT = 0; + + /** + * @see \phpseclib3\Crypt\Salsa20::crypt() + */ + const DECRYPT = 1; + + /** + * Encryption buffer for continuous mode + * + * @var array + */ + protected $enbuffer; + + /** + * Decryption buffer for continuous mode + * + * @var array + */ + protected $debuffer; + + /** + * Counter + * + * @var int + */ + protected $counter = 0; + + /** + * Using Generated Poly1305 Key + * + * @var boolean + */ + protected $usingGeneratedPoly1305Key = false; + + /** + * Salsa20 uses a nonce + * + * @return bool + */ + public function usesNonce() + { + return true; + } + + /** + * Sets the key. + * + * @param string $key + * @throws \LengthException if the key length isn't supported + */ + public function setKey($key) + { + switch (strlen($key)) { + case 16: + case 32: + break; + default: + throw new \LengthException('Key of size ' . strlen($key) . ' not supported by this algorithm. Only keys of sizes 16 or 32 are supported'); + } + + parent::setKey($key); + } + + /** + * Sets the nonce. + * + * @param string $nonce + */ + public function setNonce($nonce) + { + if (strlen($nonce) != 8) { + throw new \LengthException('Nonce of size ' . strlen($key) . ' not supported by this algorithm. Only an 64-bit nonce is supported'); + } + + $this->nonce = $nonce; + $this->changed = true; + $this->setEngine(); + } + + /** + * Sets the counter. + * + * @param int $counter + */ + public function setCounter($counter) + { + $this->counter = $counter; + $this->setEngine(); + } + + /** + * Creates a Poly1305 key using the method discussed in RFC8439 + * + * See https://tools.ietf.org/html/rfc8439#section-2.6.1 + */ + protected function createPoly1305Key() + { + if ($this->nonce === false) { + throw new InsufficientSetupException('No nonce has been defined'); + } + + if ($this->key === false) { + throw new InsufficientSetupException('No key has been defined'); + } + + $c = clone $this; + $c->setCounter(0); + $c->usePoly1305 = false; + $block = $c->encrypt(str_repeat("\0", 256)); + $this->setPoly1305Key(substr($block, 0, 32)); + + if ($this->counter == 0) { + $this->counter++; + } + } + + /** + * Setup the self::ENGINE_INTERNAL $engine + * + * (re)init, if necessary, the internal cipher $engine + * + * _setup() will be called each time if $changed === true + * typically this happens when using one or more of following public methods: + * + * - setKey() + * + * - setNonce() + * + * - First run of encrypt() / decrypt() with no init-settings + * + * @see self::setKey() + * @see self::setNonce() + * @see self::disableContinuousBuffer() + */ + protected function setup() + { + if (!$this->changed) { + return; + } + + $this->enbuffer = $this->debuffer = ['ciphertext' => '', 'counter' => $this->counter]; + + $this->changed = $this->nonIVChanged = false; + + if ($this->nonce === false) { + throw new InsufficientSetupException('No nonce has been defined'); + } + + if ($this->key === false) { + throw new InsufficientSetupException('No key has been defined'); + } + + if ($this->usePoly1305 && !isset($this->poly1305Key)) { + $this->usingGeneratedPoly1305Key = true; + $this->createPoly1305Key(); + } + + $key = $this->key; + if (strlen($key) == 16) { + $constant = 'expand 16-byte k'; + $key .= $key; + } else { + $constant = 'expand 32-byte k'; + } + + $this->p1 = substr($constant, 0, 4) . + substr($key, 0, 16) . + substr($constant, 4, 4) . + $this->nonce . + "\0\0\0\0"; + $this->p2 = substr($constant, 8, 4) . + substr($key, 16, 16) . + substr($constant, 12, 4); + } + + /** + * Setup the key (expansion) + */ + protected function setupKey() + { + // Salsa20 does not utilize this method + } + + /** + * Encrypts a message. + * + * @see \phpseclib3\Crypt\Common\SymmetricKey::decrypt() + * @see self::crypt() + * @param string $plaintext + * @return string $ciphertext + */ + public function encrypt($plaintext) + { + $ciphertext = $this->crypt($plaintext, self::ENCRYPT); + if (isset($this->poly1305Key)) { + $this->newtag = $this->poly1305($ciphertext); + } + return $ciphertext; + } + + /** + * Decrypts a message. + * + * $this->decrypt($this->encrypt($plaintext)) == $this->encrypt($this->encrypt($plaintext)). + * At least if the continuous buffer is disabled. + * + * @see \phpseclib3\Crypt\Common\SymmetricKey::encrypt() + * @see self::crypt() + * @param string $ciphertext + * @return string $plaintext + */ + public function decrypt($ciphertext) + { + if (isset($this->poly1305Key)) { + if ($this->oldtag === false) { + throw new InsufficientSetupException('Authentication Tag has not been set'); + } + $newtag = $this->poly1305($ciphertext); + if ($this->oldtag != substr($newtag, 0, strlen($this->oldtag))) { + $this->oldtag = false; + throw new BadDecryptionException('Derived authentication tag and supplied authentication tag do not match'); + } + $this->oldtag = false; + } + + return $this->crypt($ciphertext, self::DECRYPT); + } + + /** + * Encrypts a block + * + * @param string $in + */ + protected function encryptBlock($in) + { + // Salsa20 does not utilize this method + } + + /** + * Decrypts a block + * + * @param string $in + */ + protected function decryptBlock($in) + { + // Salsa20 does not utilize this method + } + + /** + * Encrypts or decrypts a message. + * + * @see self::encrypt() + * @see self::decrypt() + * @param string $text + * @param int $mode + * @return string $text + */ + private function crypt($text, $mode) + { + $this->setup(); + if (!$this->continuousBuffer) { + if ($this->engine == self::ENGINE_OPENSSL) { + $iv = pack('V', $this->counter) . $this->p2; + return openssl_encrypt( + $text, + $this->cipher_name_openssl, + $this->key, + OPENSSL_RAW_DATA, + $iv + ); + } + $i = $this->counter; + $blocks = str_split($text, 64); + foreach ($blocks as &$block) { + $block ^= static::salsa20($this->p1 . pack('V', $i++) . $this->p2); + } + + return implode('', $blocks); + } + + if ($mode == self::ENCRYPT) { + $buffer = &$this->enbuffer; + } else { + $buffer = &$this->debuffer; + } + if (!strlen($buffer['ciphertext'])) { + $ciphertext = ''; + } else { + $ciphertext = $text ^ Strings::shift($buffer['ciphertext'], strlen($text)); + $text = substr($text, strlen($ciphertext)); + if (!strlen($text)) { + return $ciphertext; + } + } + + $overflow = strlen($text) % 64; // & 0x3F + if ($overflow) { + $text2 = Strings::pop($text, $overflow); + if ($this->engine == self::ENGINE_OPENSSL) { + $iv = pack('V', $buffer['counter']) . $this->p2; + // at this point $text should be a multiple of 64 + $buffer['counter'] += (strlen($text) >> 6) + 1; // ie. divide by 64 + $encrypted = openssl_encrypt( + $text . str_repeat("\0", 64), + $this->cipher_name_openssl, + $this->key, + OPENSSL_RAW_DATA, + $iv + ); + $temp = Strings::pop($encrypted, 64); + } else { + $blocks = str_split($text, 64); + if (strlen($text)) { + foreach ($blocks as &$block) { + $block ^= static::salsa20($this->p1 . pack('V', $buffer['counter']++) . $this->p2); + } + } + $encrypted = implode('', $blocks); + $temp = static::salsa20($this->p1 . pack('V', $buffer['counter']++) . $this->p2); + } + $ciphertext .= $encrypted . ($text2 ^ $temp); + $buffer['ciphertext'] = substr($temp, $overflow); + } elseif (!strlen($buffer['ciphertext'])) { + if ($this->engine == self::ENGINE_OPENSSL) { + $iv = pack('V', $buffer['counter']) . $this->p2; + $buffer['counter'] += (strlen($text) >> 6); + $ciphertext .= openssl_encrypt( + $text, + $this->cipher_name_openssl, + $this->key, + OPENSSL_RAW_DATA, + $iv + ); + } else { + $blocks = str_split($text, 64); + foreach ($blocks as &$block) { + $block ^= static::salsa20($this->p1 . pack('V', $buffer['counter']++) . $this->p2); + } + $ciphertext .= implode('', $blocks); + } + } + + return $ciphertext; + } + + /** + * Left Rotate + * + * @param int $x + * @param int $n + * @return int + */ + protected static function leftRotate($x, $n) + { + if (PHP_INT_SIZE == 8) { + $r1 = $x << $n; + $r1 &= 0xFFFFFFFF; + $r2 = ($x & 0xFFFFFFFF) >> (32 - $n); + } else { + $x = (int) $x; + $r1 = $x << $n; + $r2 = $x >> (32 - $n); + $r2 &= (1 << $n) - 1; + } + return $r1 | $r2; + } + + /** + * The quarterround function + * + * @param int $a + * @param int $b + * @param int $c + * @param int $d + */ + protected static function quarterRound(&$a, &$b, &$c, &$d) + { + $b ^= self::leftRotate($a + $d, 7); + $c ^= self::leftRotate($b + $a, 9); + $d ^= self::leftRotate($c + $b, 13); + $a ^= self::leftRotate($d + $c, 18); + } + + /** + * The doubleround function + * + * @param int $x0 (by reference) + * @param int $x1 (by reference) + * @param int $x2 (by reference) + * @param int $x3 (by reference) + * @param int $x4 (by reference) + * @param int $x5 (by reference) + * @param int $x6 (by reference) + * @param int $x7 (by reference) + * @param int $x8 (by reference) + * @param int $x9 (by reference) + * @param int $x10 (by reference) + * @param int $x11 (by reference) + * @param int $x12 (by reference) + * @param int $x13 (by reference) + * @param int $x14 (by reference) + * @param int $x15 (by reference) + */ + protected static function doubleRound(&$x0, &$x1, &$x2, &$x3, &$x4, &$x5, &$x6, &$x7, &$x8, &$x9, &$x10, &$x11, &$x12, &$x13, &$x14, &$x15) + { + // columnRound + static::quarterRound($x0, $x4, $x8, $x12); + static::quarterRound($x5, $x9, $x13, $x1); + static::quarterRound($x10, $x14, $x2, $x6); + static::quarterRound($x15, $x3, $x7, $x11); + // rowRound + static::quarterRound($x0, $x1, $x2, $x3); + static::quarterRound($x5, $x6, $x7, $x4); + static::quarterRound($x10, $x11, $x8, $x9); + static::quarterRound($x15, $x12, $x13, $x14); + } + + /** + * The Salsa20 hash function function + * + * @param string $x + */ + protected static function salsa20($x) + { + $z = $x = unpack('V*', $x); + for ($i = 0; $i < 10; $i++) { + static::doubleRound($z[1], $z[2], $z[3], $z[4], $z[5], $z[6], $z[7], $z[8], $z[9], $z[10], $z[11], $z[12], $z[13], $z[14], $z[15], $z[16]); + } + + for ($i = 1; $i <= 16; $i++) { + $x[$i] += $z[$i]; + } + + return pack('V*', ...$x); + } + + /** + * Calculates Poly1305 MAC + * + * @see self::decrypt() + * @see self::encrypt() + * @param string $ciphertext + * @return string + */ + protected function poly1305($ciphertext) + { + if (!$this->usingGeneratedPoly1305Key) { + return parent::poly1305($this->aad . $ciphertext); + } else { + /* + sodium_crypto_aead_chacha20poly1305_encrypt does not calculate the poly1305 tag + the same way sodium_crypto_aead_chacha20poly1305_ietf_encrypt does. you can see + how the latter encrypts it in Salsa20::encrypt(). here's how the former encrypts + it: + + $this->newtag = $this->poly1305( + $this->aad . + pack('V', strlen($this->aad)) . "\0\0\0\0" . + $ciphertext . + pack('V', strlen($ciphertext)) . "\0\0\0\0" + ); + + phpseclib opts to use the IETF construction, even when the nonce is 64-bits + instead of 96-bits + */ + return parent::poly1305( + self::nullPad128($this->aad) . + self::nullPad128($ciphertext) . + pack('V', strlen($this->aad)) . "\0\0\0\0" . + pack('V', strlen($ciphertext)) . "\0\0\0\0" + ); + } + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/TripleDES.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/TripleDES.php index a2c41668..1ff5ed02 100644 --- a/vendor/phpseclib/phpseclib/phpseclib/Crypt/TripleDES.php +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/TripleDES.php @@ -12,7 +12,7 @@ * setKey('abcdefghijklmnopqrstuvwx'); * @@ -26,22 +26,18 @@ * ?> * * - * @category Crypt - * @package TripleDES * @author Jim Wigginton * @copyright 2007 Jim Wigginton * @license http://www.opensource.org/licenses/mit-license.html MIT License * @link http://phpseclib.sourceforge.net */ -namespace phpseclib\Crypt; +namespace phpseclib3\Crypt; /** * Pure-PHP implementation of Triple DES. * - * @package TripleDES * @author Jim Wigginton - * @access public */ class TripleDES extends DES { @@ -55,161 +51,148 @@ class TripleDES extends DES /** * Encrypt / decrypt using outer chaining * - * Outer chaining is used by SSH-2 and when the mode is set to \phpseclib\Crypt\Base::MODE_CBC. + * Outer chaining is used by SSH-2 and when the mode is set to \phpseclib3\Crypt\Common\BlockCipher::MODE_CBC. */ - const MODE_CBC3 = Base::MODE_CBC; + const MODE_CBC3 = self::MODE_CBC; /** * Key Length (in bytes) * - * @see \phpseclib\Crypt\TripleDES::setKeyLength() + * @see \phpseclib3\Crypt\TripleDES::setKeyLength() * @var int - * @access private */ - var $key_length = 24; - - /** - * The default salt used by setPassword() - * - * @see \phpseclib\Crypt\Base::password_default_salt - * @see \phpseclib\Crypt\Base::setPassword() - * @var string - * @access private - */ - var $password_default_salt = 'phpseclib'; + protected $key_length = 24; /** * The mcrypt specific name of the cipher * - * @see \phpseclib\Crypt\DES::cipher_name_mcrypt - * @see \phpseclib\Crypt\Base::cipher_name_mcrypt + * @see \phpseclib3\Crypt\DES::cipher_name_mcrypt + * @see \phpseclib3\Crypt\Common\SymmetricKey::cipher_name_mcrypt * @var string - * @access private */ - var $cipher_name_mcrypt = 'tripledes'; + protected $cipher_name_mcrypt = 'tripledes'; /** * Optimizing value while CFB-encrypting * - * @see \phpseclib\Crypt\Base::cfb_init_len + * @see \phpseclib3\Crypt\Common\SymmetricKey::cfb_init_len * @var int - * @access private */ - var $cfb_init_len = 750; + protected $cfb_init_len = 750; /** * max possible size of $key * * @see self::setKey() - * @see \phpseclib\Crypt\DES::setKey() + * @see \phpseclib3\Crypt\DES::setKey() * @var string - * @access private */ - var $key_length_max = 24; + protected $key_length_max = 24; /** * Internal flag whether using self::MODE_3CBC or not * * @var bool - * @access private */ - var $mode_3cbc; + private $mode_3cbc; /** - * The \phpseclib\Crypt\DES objects + * The \phpseclib3\Crypt\DES objects * * Used only if $mode_3cbc === true * * @var array - * @access private */ - var $des; + private $des; /** * Default Constructor. * - * Determines whether or not the mcrypt extension should be used. + * Determines whether or not the mcrypt or OpenSSL extensions should be used. * * $mode could be: * - * - \phpseclib\Crypt\Base::MODE_ECB + * - ecb * - * - \phpseclib\Crypt\Base::MODE_CBC + * - cbc * - * - \phpseclib\Crypt\Base::MODE_CTR + * - ctr * - * - \phpseclib\Crypt\Base::MODE_CFB + * - cfb * - * - \phpseclib\Crypt\Base::MODE_OFB + * - ofb * - * - \phpseclib\Crypt\TripleDES::MODE_3CBC + * - 3cbc * - * If not explicitly set, \phpseclib\Crypt\Base::MODE_CBC will be used. + * - cbc3 (same as cbc) * - * @see \phpseclib\Crypt\DES::__construct() - * @see \phpseclib\Crypt\Base::__construct() - * @param int $mode - * @access public + * @see \phpseclib3\Crypt\DES::__construct() + * @see \phpseclib3\Crypt\Common\SymmetricKey::__construct() + * @param string $mode */ - function __construct($mode = Base::MODE_CBC) + public function __construct($mode) { - switch ($mode) { + switch (strtolower($mode)) { // In case of self::MODE_3CBC, we init as CRYPT_DES_MODE_CBC // and additional flag us internally as 3CBC - case self::MODE_3CBC: - parent::__construct(Base::MODE_CBC); + case '3cbc': + parent::__construct('cbc'); $this->mode_3cbc = true; // This three $des'es will do the 3CBC work (if $key > 64bits) - $this->des = array( - new DES(Base::MODE_CBC), - new DES(Base::MODE_CBC), - new DES(Base::MODE_CBC), - ); + $this->des = [ + new DES('cbc'), + new DES('cbc'), + new DES('cbc'), + ]; - // we're going to be doing the padding, ourselves, so disable it in the \phpseclib\Crypt\DES objects + // we're going to be doing the padding, ourselves, so disable it in the \phpseclib3\Crypt\DES objects $this->des[0]->disablePadding(); $this->des[1]->disablePadding(); $this->des[2]->disablePadding(); break; + case 'cbc3': + $mode = 'cbc'; + // fall-through // If not 3CBC, we init as usual default: parent::__construct($mode); + + if ($this->mode == self::MODE_STREAM) { + throw new BadModeException('Block ciphers cannot be ran in stream mode'); + } } } /** * Test for engine validity * - * This is mainly just a wrapper to set things up for \phpseclib\Crypt\Base::isValidEngine() + * This is mainly just a wrapper to set things up for \phpseclib3\Crypt\Common\SymmetricKey::isValidEngine() * - * @see \phpseclib\Crypt\Base::__construct() + * @see \phpseclib3\Crypt\Common\SymmetricKey::__construct() * @param int $engine - * @access public * @return bool */ - function isValidEngine($engine) + protected function isValidEngineHelper($engine) { if ($engine == self::ENGINE_OPENSSL) { $this->cipher_name_openssl_ecb = 'des-ede3'; - $mode = $this->_openssl_translate_mode(); + $mode = $this->openssl_translate_mode(); $this->cipher_name_openssl = $mode == 'ecb' ? 'des-ede3' : 'des-ede3-' . $mode; } - return parent::isValidEngine($engine); + return parent::isValidEngineHelper($engine); } /** - * Sets the initialization vector. (optional) + * Sets the initialization vector. * - * SetIV is not required when \phpseclib\Crypt\Base::MODE_ECB is being used. If not explicitly set, it'll be assumed - * to be all zero's. + * SetIV is not required when \phpseclib3\Crypt\Common\SymmetricKey::MODE_ECB is being used. * - * @see \phpseclib\Crypt\Base::setIV() - * @access public + * @see \phpseclib3\Crypt\Common\SymmetricKey::setIV() * @param string $iv */ - function setIV($iv) + public function setIV($iv) { parent::setIV($iv); if ($this->mode_3cbc) { @@ -222,24 +205,22 @@ function setIV($iv) /** * Sets the key length. * - * Valid key lengths are 64, 128 and 192 + * Valid key lengths are 128 and 192 bits. + * + * If you want to use a 64-bit key use DES.php * - * @see \phpseclib\Crypt\Base:setKeyLength() - * @access public + * @see \phpseclib3\Crypt\Common\SymmetricKey:setKeyLength() + * @throws \LengthException if the key length is invalid * @param int $length */ - function setKeyLength($length) + public function setKeyLength($length) { - $length >>= 3; - switch (true) { - case $length <= 8: - $this->key_length = 8; - break; - case $length <= 16: - $this->key_length = 16; + switch ($length) { + case 128: + case 192: break; default: - $this->key_length = 24; + throw new \LengthException('Key size of ' . $length . ' bits is not supported by this algorithm. Only keys of sizes 128 or 192 bits are supported'); } parent::setKeyLength($length); @@ -248,38 +229,40 @@ function setKeyLength($length) /** * Sets the key. * - * Keys can be of any length. Triple DES, itself, can use 128-bit (eg. strlen($key) == 16) or - * 192-bit (eg. strlen($key) == 24) keys. This function pads and truncates $key as appropriate. + * Triple DES can use 128-bit (eg. strlen($key) == 16) or 192-bit (eg. strlen($key) == 24) keys. * * DES also requires that every eighth bit be a parity bit, however, we'll ignore that. * - * If the key is not explicitly set, it'll be assumed to be all null bytes. - * - * @access public - * @see \phpseclib\Crypt\DES::setKey() - * @see \phpseclib\Crypt\Base::setKey() + * @see \phpseclib3\Crypt\DES::setKey() + * @see \phpseclib3\Crypt\Common\SymmetricKey::setKey() + * @throws \LengthException if the key length is invalid * @param string $key */ - function setKey($key) + public function setKey($key) { - $length = $this->explicit_key_length ? $this->key_length : strlen($key); - if ($length > 8) { - $key = str_pad(substr($key, 0, 24), 24, chr(0)); - // if $key is between 64 and 128-bits, use the first 64-bits as the last, per this: - // http://php.net/function.mcrypt-encrypt#47973 - $key = $length <= 16 ? substr_replace($key, substr($key, 0, 8), 16) : substr($key, 0, 24); - } else { - $key = str_pad($key, 8, chr(0)); + if ($this->explicit_key_length !== false && strlen($key) != $this->explicit_key_length) { + throw new \LengthException('Key length has already been set to ' . $this->explicit_key_length . ' bytes and this key is ' . strlen($key) . ' bytes'); } - parent::setKey($key); - - // And in case of self::MODE_3CBC: - // if key <= 64bits we not need the 3 $des to work, - // because we will then act as regular DES-CBC with just a <= 64bit key. - // So only if the key > 64bits (> 8 bytes) we will call setKey() for the 3 $des. - if ($this->mode_3cbc && $length > 8) { - $this->des[0]->setKey(substr($key, 0, 8)); - $this->des[1]->setKey(substr($key, 8, 8)); + + switch (strlen($key)) { + case 16: + $key .= substr($key, 0, 8); + break; + case 24: + break; + default: + throw new \LengthException('Key of size ' . strlen($key) . ' not supported by this algorithm. Only keys of sizes 16 or 24 are supported'); + } + + // copied from self::setKey() + $this->key = $key; + $this->key_length = strlen($key); + $this->changed = $this->nonIVChanged = true; + $this->setEngine(); + + if ($this->mode_3cbc) { + $this->des[0]->setKey(substr($key, 0, 8)); + $this->des[1]->setKey(substr($key, 8, 8)); $this->des[2]->setKey(substr($key, 16, 8)); } } @@ -287,12 +270,11 @@ function setKey($key) /** * Encrypts a message. * - * @see \phpseclib\Crypt\Base::encrypt() - * @access public + * @see \phpseclib3\Crypt\Common\SymmetricKey::encrypt() * @param string $plaintext * @return string $cipertext */ - function encrypt($plaintext) + public function encrypt($plaintext) { // parent::en/decrypt() is able to do all the work for all modes and keylengths, // except for: self::MODE_3CBC (inner chaining CBC) with a key > 64bits @@ -302,7 +284,7 @@ function encrypt($plaintext) return $this->des[2]->encrypt( $this->des[1]->decrypt( $this->des[0]->encrypt( - $this->_pad($plaintext) + $this->pad($plaintext) ) ) ); @@ -314,15 +296,14 @@ function encrypt($plaintext) /** * Decrypts a message. * - * @see \phpseclib\Crypt\Base::decrypt() - * @access public + * @see \phpseclib3\Crypt\Common\SymmetricKey::decrypt() * @param string $ciphertext * @return string $plaintext */ - function decrypt($ciphertext) + public function decrypt($ciphertext) { if ($this->mode_3cbc && strlen($this->key) > 8) { - return $this->_unpad( + return $this->unpad( $this->des[0]->decrypt( $this->des[1]->encrypt( $this->des[2]->decrypt( @@ -365,16 +346,15 @@ function decrypt($ciphertext) * outputs. The reason is due to the fact that the initialization vector's change after every encryption / * decryption round when the continuous buffer is enabled. When it's disabled, they remain constant. * - * Put another way, when the continuous buffer is enabled, the state of the \phpseclib\Crypt\DES() object changes after each + * Put another way, when the continuous buffer is enabled, the state of the \phpseclib3\Crypt\DES() object changes after each * encryption / decryption round, whereas otherwise, it'd remain constant. For this reason, it's recommended that * continuous buffers not be used. They do offer better security and are, in fact, sometimes required (SSH uses them), * however, they are also less intuitive and more likely to cause you problems. * - * @see \phpseclib\Crypt\Base::enableContinuousBuffer() + * @see \phpseclib3\Crypt\Common\SymmetricKey::enableContinuousBuffer() * @see self::disableContinuousBuffer() - * @access public */ - function enableContinuousBuffer() + public function enableContinuousBuffer() { parent::enableContinuousBuffer(); if ($this->mode_3cbc) { @@ -389,11 +369,10 @@ function enableContinuousBuffer() * * The default behavior. * - * @see \phpseclib\Crypt\Base::disableContinuousBuffer() + * @see \phpseclib3\Crypt\Common\SymmetricKey::disableContinuousBuffer() * @see self::enableContinuousBuffer() - * @access public */ - function disableContinuousBuffer() + public function disableContinuousBuffer() { parent::disableContinuousBuffer(); if ($this->mode_3cbc) { @@ -406,11 +385,10 @@ function disableContinuousBuffer() /** * Creates the key schedule * - * @see \phpseclib\Crypt\DES::_setupKey() - * @see \phpseclib\Crypt\Base::_setupKey() - * @access private + * @see \phpseclib3\Crypt\DES::setupKey() + * @see \phpseclib3\Crypt\Common\SymmetricKey::setupKey() */ - function _setupKey() + protected function setupKey() { switch (true) { // if $key <= 64bits we configure our internal pure-php cipher engine @@ -425,29 +403,27 @@ function _setupKey() // (only) if 3CBC is used we have, of course, to setup the $des[0-2] keys also separately. if ($this->mode_3cbc) { - $this->des[0]->_setupKey(); - $this->des[1]->_setupKey(); - $this->des[2]->_setupKey(); + $this->des[0]->setupKey(); + $this->des[1]->setupKey(); + $this->des[2]->setupKey(); // because $des[0-2] will, now, do all the work we can return here - // not need unnecessary stress parent::_setupKey() with our, now unused, $key. + // not need unnecessary stress parent::setupKey() with our, now unused, $key. return; } } // setup our key - parent::_setupKey(); + parent::setupKey(); } /** * Sets the internal crypt engine * - * @see \phpseclib\Crypt\Base::__construct() - * @see \phpseclib\Crypt\Base::setPreferredEngine() + * @see \phpseclib3\Crypt\Common\SymmetricKey::__construct() + * @see \phpseclib3\Crypt\Common\SymmetricKey::setPreferredEngine() * @param int $engine - * @access public - * @return int */ - function setPreferredEngine($engine) + public function setPreferredEngine($engine) { if ($this->mode_3cbc) { $this->des[0]->setPreferredEngine($engine); @@ -455,6 +431,6 @@ function setPreferredEngine($engine) $this->des[2]->setPreferredEngine($engine); } - return parent::setPreferredEngine($engine); + parent::setPreferredEngine($engine); } } diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/Twofish.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Twofish.php index 70980a2f..bf765632 100644 --- a/vendor/phpseclib/phpseclib/phpseclib/Crypt/Twofish.php +++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Twofish.php @@ -16,7 +16,7 @@ * setKey('12345678901234567890123456789012'); * @@ -26,8 +26,6 @@ * ?> * * - * @category Crypt - * @package Twofish * @author Jim Wigginton * @author Hans-Juergen Petrich * @copyright 2007 Jim Wigginton @@ -35,43 +33,41 @@ * @link http://phpseclib.sourceforge.net */ -namespace phpseclib\Crypt; +namespace phpseclib3\Crypt; + +use phpseclib3\Crypt\Common\BlockCipher; +use phpseclib3\Exception\BadModeException; /** * Pure-PHP implementation of Twofish. * - * @package Twofish * @author Jim Wigginton * @author Hans-Juergen Petrich - * @access public */ -class Twofish extends Base +class Twofish extends BlockCipher { /** * The mcrypt specific name of the cipher * - * @see \phpseclib\Crypt\Base::cipher_name_mcrypt + * @see \phpseclib3\Crypt\Common\SymmetricKey::cipher_name_mcrypt * @var string - * @access private */ - var $cipher_name_mcrypt = 'twofish'; + protected $cipher_name_mcrypt = 'twofish'; /** * Optimizing value while CFB-encrypting * - * @see \phpseclib\Crypt\Base::cfb_init_len + * @see \phpseclib3\Crypt\Common\SymmetricKey::cfb_init_len * @var int - * @access private */ - var $cfb_init_len = 800; + protected $cfb_init_len = 800; /** * Q-Table * * @var array - * @access private */ - var $q0 = array( + private static $q0 = [ 0xA9, 0x67, 0xB3, 0xE8, 0x04, 0xFD, 0xA3, 0x76, 0x9A, 0x92, 0x80, 0x78, 0xE4, 0xDD, 0xD1, 0x38, 0x0D, 0xC6, 0x35, 0x98, 0x18, 0xF7, 0xEC, 0x6C, @@ -104,15 +100,14 @@ class Twofish extends Base 0xC8, 0xA8, 0x2B, 0x40, 0xDC, 0xFE, 0x32, 0xA4, 0xCA, 0x10, 0x21, 0xF0, 0xD3, 0x5D, 0x0F, 0x00, 0x6F, 0x9D, 0x36, 0x42, 0x4A, 0x5E, 0xC1, 0xE0 - ); + ]; /** * Q-Table * * @var array - * @access private */ - var $q1 = array( + private static $q1 = [ 0x75, 0xF3, 0xC6, 0xF4, 0xDB, 0x7B, 0xFB, 0xC8, 0x4A, 0xD3, 0xE6, 0x6B, 0x45, 0x7D, 0xE8, 0x4B, 0xD6, 0x32, 0xD8, 0xFD, 0x37, 0x71, 0xF1, 0xE1, @@ -145,15 +140,14 @@ class Twofish extends Base 0x12, 0xA2, 0x0D, 0x52, 0xBB, 0x02, 0x2F, 0xA9, 0xD7, 0x61, 0x1E, 0xB4, 0x50, 0x04, 0xF6, 0xC2, 0x16, 0x25, 0x86, 0x56, 0x55, 0x09, 0xBE, 0x91 - ); + ]; /** * M-Table * * @var array - * @access private */ - var $m0 = array( + private static $m0 = [ 0xBCBC3275, 0xECEC21F3, 0x202043C6, 0xB3B3C9F4, 0xDADA03DB, 0x02028B7B, 0xE2E22BFB, 0x9E9EFAC8, 0xC9C9EC4A, 0xD4D409D3, 0x18186BE6, 0x1E1E9F6B, 0x98980E45, 0xB2B2387D, 0xA6A6D2E8, 0x2626B74B, 0x3C3C57D6, 0x93938A32, 0x8282EED8, 0x525298FD, 0x7B7BD437, 0xBBBB3771, 0x5B5B97F1, 0x474783E1, @@ -186,15 +180,14 @@ class Twofish extends Base 0xABABA212, 0x6F6F3EA2, 0xE6E6540D, 0xDBDBF252, 0x92927BBB, 0xB7B7B602, 0x6969CA2F, 0x3939D9A9, 0xD3D30CD7, 0xA7A72361, 0xA2A2AD1E, 0xC3C399B4, 0x6C6C4450, 0x07070504, 0x04047FF6, 0x272746C2, 0xACACA716, 0xD0D07625, 0x50501386, 0xDCDCF756, 0x84841A55, 0xE1E15109, 0x7A7A25BE, 0x1313EF91 - ); + ]; /** * M-Table * * @var array - * @access private */ - var $m1 = array( + private static $m1 = [ 0xA9D93939, 0x67901717, 0xB3719C9C, 0xE8D2A6A6, 0x04050707, 0xFD985252, 0xA3658080, 0x76DFE4E4, 0x9A084545, 0x92024B4B, 0x80A0E0E0, 0x78665A5A, 0xE4DDAFAF, 0xDDB06A6A, 0xD1BF6363, 0x38362A2A, 0x0D54E6E6, 0xC6432020, 0x3562CCCC, 0x98BEF2F2, 0x181E1212, 0xF724EBEB, 0xECD7A1A1, 0x6C774141, @@ -227,15 +220,14 @@ class Twofish extends Base 0xC8FA9E9E, 0xA882D6D6, 0x2BCF6E6E, 0x40507070, 0xDCEB8585, 0xFE750A0A, 0x328A9393, 0xA48DDFDF, 0xCA4C2929, 0x10141C1C, 0x2173D7D7, 0xF0CCB4B4, 0xD309D4D4, 0x5D108A8A, 0x0FE25151, 0x00000000, 0x6F9A1919, 0x9DE01A1A, 0x368F9494, 0x42E6C7C7, 0x4AECC9C9, 0x5EFDD2D2, 0xC1AB7F7F, 0xE0D8A8A8 - ); + ]; /** * M-Table * * @var array - * @access private */ - var $m2 = array( + private static $m2 = [ 0xBC75BC32, 0xECF3EC21, 0x20C62043, 0xB3F4B3C9, 0xDADBDA03, 0x027B028B, 0xE2FBE22B, 0x9EC89EFA, 0xC94AC9EC, 0xD4D3D409, 0x18E6186B, 0x1E6B1E9F, 0x9845980E, 0xB27DB238, 0xA6E8A6D2, 0x264B26B7, 0x3CD63C57, 0x9332938A, 0x82D882EE, 0x52FD5298, 0x7B377BD4, 0xBB71BB37, 0x5BF15B97, 0x47E14783, @@ -268,15 +260,14 @@ class Twofish extends Base 0xAB12ABA2, 0x6FA26F3E, 0xE60DE654, 0xDB52DBF2, 0x92BB927B, 0xB702B7B6, 0x692F69CA, 0x39A939D9, 0xD3D7D30C, 0xA761A723, 0xA21EA2AD, 0xC3B4C399, 0x6C506C44, 0x07040705, 0x04F6047F, 0x27C22746, 0xAC16ACA7, 0xD025D076, 0x50865013, 0xDC56DCF7, 0x8455841A, 0xE109E151, 0x7ABE7A25, 0x139113EF - ); + ]; /** * M-Table * * @var array - * @access private */ - var $m3 = array( + private static $m3 = [ 0xD939A9D9, 0x90176790, 0x719CB371, 0xD2A6E8D2, 0x05070405, 0x9852FD98, 0x6580A365, 0xDFE476DF, 0x08459A08, 0x024B9202, 0xA0E080A0, 0x665A7866, 0xDDAFE4DD, 0xB06ADDB0, 0xBF63D1BF, 0x362A3836, 0x54E60D54, 0x4320C643, 0x62CC3562, 0xBEF298BE, 0x1E12181E, 0x24EBF724, 0xD7A1ECD7, 0x77416C77, @@ -309,120 +300,164 @@ class Twofish extends Base 0xFA9EC8FA, 0x82D6A882, 0xCF6E2BCF, 0x50704050, 0xEB85DCEB, 0x750AFE75, 0x8A93328A, 0x8DDFA48D, 0x4C29CA4C, 0x141C1014, 0x73D72173, 0xCCB4F0CC, 0x09D4D309, 0x108A5D10, 0xE2510FE2, 0x00000000, 0x9A196F9A, 0xE01A9DE0, 0x8F94368F, 0xE6C742E6, 0xECC94AEC, 0xFDD25EFD, 0xAB7FC1AB, 0xD8A8E0D8 - ); + ]; /** * The Key Schedule Array * * @var array - * @access private */ - var $K = array(); + private $K = []; /** * The Key depended S-Table 0 * * @var array - * @access private */ - var $S0 = array(); + private $S0 = []; /** * The Key depended S-Table 1 * * @var array - * @access private */ - var $S1 = array(); + private $S1 = []; /** * The Key depended S-Table 2 * * @var array - * @access private */ - var $S2 = array(); + private $S2 = []; /** * The Key depended S-Table 3 * * @var array - * @access private */ - var $S3 = array(); + private $S3 = []; /** * Holds the last used key * * @var array - * @access private */ - var $kl; + private $kl; /** * The Key Length (in bytes) * * @see Crypt_Twofish::setKeyLength() * @var int - * @access private */ - var $key_length = 16; + protected $key_length = 16; + + /** + * Default Constructor. + * + * @param string $mode + * @throws BadModeException if an invalid / unsupported mode is provided + */ + public function __construct($mode) + { + parent::__construct($mode); + + if ($this->mode == self::MODE_STREAM) { + throw new BadModeException('Block ciphers cannot be ran in stream mode'); + } + } + + /** + * Initialize Static Variables + */ + protected static function initialize_static_variables() + { + if (is_float(self::$m3[0])) { + self::$m0 = array_map('intval', self::$m0); + self::$m1 = array_map('intval', self::$m1); + self::$m2 = array_map('intval', self::$m2); + self::$m3 = array_map('intval', self::$m3); + self::$q0 = array_map('intval', self::$q0); + self::$q1 = array_map('intval', self::$q1); + } + + parent::initialize_static_variables(); + } /** * Sets the key length. * * Valid key lengths are 128, 192 or 256 bits * - * @access public * @param int $length */ - function setKeyLength($length) + public function setKeyLength($length) { - switch (true) { - case $length <= 128: - $this->key_length = 16; - break; - case $length <= 192: - $this->key_length = 24; + switch ($length) { + case 128: + case 192: + case 256: break; default: - $this->key_length = 32; + throw new \LengthException('Key of size ' . $length . ' not supported by this algorithm. Only keys of sizes 16, 24 or 32 supported'); } parent::setKeyLength($length); } + /** + * Sets the key. + * + * Rijndael supports five different key lengths + * + * @see setKeyLength() + * @param string $key + * @throws \LengthException if the key length isn't supported + */ + public function setKey($key) + { + switch (strlen($key)) { + case 16: + case 24: + case 32: + break; + default: + throw new \LengthException('Key of size ' . strlen($key) . ' not supported by this algorithm. Only keys of sizes 16, 24 or 32 supported'); + } + + parent::setKey($key); + } + /** * Setup the key (expansion) * - * @see \phpseclib\Crypt\Base::_setupKey() - * @access private + * @see \phpseclib3\Crypt\Common\SymmetricKey::_setupKey() */ - function _setupKey() + protected function setupKey() { if (isset($this->kl['key']) && $this->key === $this->kl['key']) { // already expanded return; } - $this->kl = array('key' => $this->key); + $this->kl = ['key' => $this->key]; /* Key expanding and generating the key-depended s-boxes */ $le_longs = unpack('V*', $this->key); $key = unpack('C*', $this->key); - $m0 = $this->m0; - $m1 = $this->m1; - $m2 = $this->m2; - $m3 = $this->m3; - $q0 = $this->q0; - $q1 = $this->q1; + $m0 = self::$m0; + $m1 = self::$m1; + $m2 = self::$m2; + $m3 = self::$m3; + $q0 = self::$q0; + $q1 = self::$q1; - $K = $S0 = $S1 = $S2 = $S3 = array(); + $K = $S0 = $S1 = $S2 = $S3 = []; switch (strlen($this->key)) { case 16: - list($s7, $s6, $s5, $s4) = $this->_mdsrem($le_longs[1], $le_longs[2]); - list($s3, $s2, $s1, $s0) = $this->_mdsrem($le_longs[3], $le_longs[4]); - for ($i = 0, $j = 1; $i < 40; $i+= 2, $j+= 2) { + list($s7, $s6, $s5, $s4) = $this->mdsrem($le_longs[1], $le_longs[2]); + list($s3, $s2, $s1, $s0) = $this->mdsrem($le_longs[3], $le_longs[4]); + for ($i = 0, $j = 1; $i < 40; $i += 2, $j += 2) { $A = $m0[$q0[$q0[$i] ^ $key[ 9]] ^ $key[1]] ^ $m1[$q0[$q1[$i] ^ $key[10]] ^ $key[2]] ^ $m2[$q1[$q0[$i] ^ $key[11]] ^ $key[3]] ^ @@ -432,9 +467,9 @@ function _setupKey() $m2[$q1[$q0[$j] ^ $key[15]] ^ $key[7]] ^ $m3[$q1[$q1[$j] ^ $key[16]] ^ $key[8]]; $B = ($B << 8) | ($B >> 24 & 0xff); - $A = $this->safe_intval($A + $B); + $A = self::safe_intval($A + $B); $K[] = $A; - $A = $this->safe_intval($A + $B); + $A = self::safe_intval($A + $B); $K[] = ($A << 9 | $A >> 23 & 0x1ff); } for ($i = 0; $i < 256; ++$i) { @@ -445,10 +480,10 @@ function _setupKey() } break; case 24: - list($sb, $sa, $s9, $s8) = $this->_mdsrem($le_longs[1], $le_longs[2]); - list($s7, $s6, $s5, $s4) = $this->_mdsrem($le_longs[3], $le_longs[4]); - list($s3, $s2, $s1, $s0) = $this->_mdsrem($le_longs[5], $le_longs[6]); - for ($i = 0, $j = 1; $i < 40; $i+= 2, $j+= 2) { + list($sb, $sa, $s9, $s8) = $this->mdsrem($le_longs[1], $le_longs[2]); + list($s7, $s6, $s5, $s4) = $this->mdsrem($le_longs[3], $le_longs[4]); + list($s3, $s2, $s1, $s0) = $this->mdsrem($le_longs[5], $le_longs[6]); + for ($i = 0, $j = 1; $i < 40; $i += 2, $j += 2) { $A = $m0[$q0[$q0[$q1[$i] ^ $key[17]] ^ $key[ 9]] ^ $key[1]] ^ $m1[$q0[$q1[$q1[$i] ^ $key[18]] ^ $key[10]] ^ $key[2]] ^ $m2[$q1[$q0[$q0[$i] ^ $key[19]] ^ $key[11]] ^ $key[3]] ^ @@ -458,9 +493,9 @@ function _setupKey() $m2[$q1[$q0[$q0[$j] ^ $key[23]] ^ $key[15]] ^ $key[7]] ^ $m3[$q1[$q1[$q0[$j] ^ $key[24]] ^ $key[16]] ^ $key[8]]; $B = ($B << 8) | ($B >> 24 & 0xff); - $A = $this->safe_intval($A + $B); + $A = self::safe_intval($A + $B); $K[] = $A; - $A = $this->safe_intval($A + $B); + $A = self::safe_intval($A + $B); $K[] = ($A << 9 | $A >> 23 & 0x1ff); } for ($i = 0; $i < 256; ++$i) { @@ -471,11 +506,11 @@ function _setupKey() } break; default: // 32 - list($sf, $se, $sd, $sc) = $this->_mdsrem($le_longs[1], $le_longs[2]); - list($sb, $sa, $s9, $s8) = $this->_mdsrem($le_longs[3], $le_longs[4]); - list($s7, $s6, $s5, $s4) = $this->_mdsrem($le_longs[5], $le_longs[6]); - list($s3, $s2, $s1, $s0) = $this->_mdsrem($le_longs[7], $le_longs[8]); - for ($i = 0, $j = 1; $i < 40; $i+= 2, $j+= 2) { + list($sf, $se, $sd, $sc) = $this->mdsrem($le_longs[1], $le_longs[2]); + list($sb, $sa, $s9, $s8) = $this->mdsrem($le_longs[3], $le_longs[4]); + list($s7, $s6, $s5, $s4) = $this->mdsrem($le_longs[5], $le_longs[6]); + list($s3, $s2, $s1, $s0) = $this->mdsrem($le_longs[7], $le_longs[8]); + for ($i = 0, $j = 1; $i < 40; $i += 2, $j += 2) { $A = $m0[$q0[$q0[$q1[$q1[$i] ^ $key[25]] ^ $key[17]] ^ $key[ 9]] ^ $key[1]] ^ $m1[$q0[$q1[$q1[$q0[$i] ^ $key[26]] ^ $key[18]] ^ $key[10]] ^ $key[2]] ^ $m2[$q1[$q0[$q0[$q0[$i] ^ $key[27]] ^ $key[19]] ^ $key[11]] ^ $key[3]] ^ @@ -485,9 +520,9 @@ function _setupKey() $m2[$q1[$q0[$q0[$q0[$j] ^ $key[31]] ^ $key[23]] ^ $key[15]] ^ $key[7]] ^ $m3[$q1[$q1[$q0[$q1[$j] ^ $key[32]] ^ $key[24]] ^ $key[16]] ^ $key[8]]; $B = ($B << 8) | ($B >> 24 & 0xff); - $A = $this->safe_intval($A + $B); + $A = self::safe_intval($A + $B); $K[] = $A; - $A = $this->safe_intval($A + $B); + $A = self::safe_intval($A + $B); $K[] = ($A << 9 | $A >> 23 & 0x1ff); } for ($i = 0; $i < 256; ++$i) { @@ -508,12 +543,11 @@ function _setupKey() /** * _mdsrem function using by the twofish cipher algorithm * - * @access private * @param string $A * @param string $B * @return array */ - function _mdsrem($A, $B) + private function mdsrem($A, $B) { // No gain by unrolling this loop. for ($i = 0; $i < 8; ++$i) { @@ -522,45 +556,44 @@ function _mdsrem($A, $B) // Shift the others up. $B = ($B << 8) | (0xff & ($A >> 24)); - $A<<= 8; + $A <<= 8; $u = $t << 1; // Subtract the modular polynomial on overflow. if ($t & 0x80) { - $u^= 0x14d; + $u ^= 0x14d; } // Remove t * (a * x^2 + 1). $B ^= $t ^ ($u << 16); // Form u = a*t + t/a = t*(a + 1/a). - $u^= 0x7fffffff & ($t >> 1); + $u ^= 0x7fffffff & ($t >> 1); // Add the modular polynomial on underflow. if ($t & 0x01) { - $u^= 0xa6 ; + $u ^= 0xa6 ; } // Remove t * (a + 1/a) * (x^3 + x). - $B^= ($u << 24) | ($u << 8); + $B ^= ($u << 24) | ($u << 8); } - return array( + return [ 0xff & $B >> 24, 0xff & $B >> 16, 0xff & $B >> 8, - 0xff & $B); + 0xff & $B]; } /** * Encrypts a block * - * @access private * @param string $in * @return string */ - function _encryptBlock($in) + protected function encryptBlock($in) { $S0 = $this->S0; $S1 = $this->S1; @@ -584,9 +617,9 @@ function _encryptBlock($in) $S1[ $R1 & 0xff] ^ $S2[($R1 >> 8) & 0xff] ^ $S3[($R1 >> 16) & 0xff]; - $R2^= $this->safe_intval($t0 + $t1 + $K[++$ki]); + $R2 ^= self::safe_intval($t0 + $t1 + $K[++$ki]); $R2 = ($R2 >> 1 & 0x7fffffff) | ($R2 << 31); - $R3 = ((($R3 >> 31) & 1) | ($R3 << 1)) ^ $this->safe_intval($t0 + ($t1 << 1) + $K[++$ki]); + $R3 = ((($R3 >> 31) & 1) | ($R3 << 1)) ^ self::safe_intval($t0 + ($t1 << 1) + $K[++$ki]); $t0 = $S0[ $R2 & 0xff] ^ $S1[($R2 >> 8) & 0xff] ^ @@ -596,9 +629,9 @@ function _encryptBlock($in) $S1[ $R3 & 0xff] ^ $S2[($R3 >> 8) & 0xff] ^ $S3[($R3 >> 16) & 0xff]; - $R0^= $this->safe_intval($t0 + $t1 + $K[++$ki]); + $R0 ^= self::safe_intval($t0 + $t1 + $K[++$ki]); $R0 = ($R0 >> 1 & 0x7fffffff) | ($R0 << 31); - $R1 = ((($R1 >> 31) & 1) | ($R1 << 1)) ^ $this->safe_intval($t0 + ($t1 << 1) + $K[++$ki]); + $R1 = ((($R1 >> 31) & 1) | ($R1 << 1)) ^ self::safe_intval($t0 + ($t1 << 1) + $K[++$ki]); } // @codingStandardsIgnoreStart @@ -612,11 +645,10 @@ function _encryptBlock($in) /** * Decrypts a block * - * @access private * @param string $in * @return string */ - function _decryptBlock($in) + protected function decryptBlock($in) { $S0 = $this->S0; $S1 = $this->S1; @@ -640,9 +672,9 @@ function _decryptBlock($in) $S1[$R1 & 0xff] ^ $S2[$R1 >> 8 & 0xff] ^ $S3[$R1 >> 16 & 0xff]; - $R3^= $this->safe_intval($t0 + ($t1 << 1) + $K[--$ki]); + $R3 ^= self::safe_intval($t0 + ($t1 << 1) + $K[--$ki]); $R3 = $R3 >> 1 & 0x7fffffff | $R3 << 31; - $R2 = ($R2 >> 31 & 0x1 | $R2 << 1) ^ $this->safe_intval($t0 + $t1 + $K[--$ki]); + $R2 = ($R2 >> 31 & 0x1 | $R2 << 1) ^ self::safe_intval($t0 + $t1 + $K[--$ki]); $t0 = $S0[$R2 & 0xff] ^ $S1[$R2 >> 8 & 0xff] ^ @@ -652,9 +684,9 @@ function _decryptBlock($in) $S1[$R3 & 0xff] ^ $S2[$R3 >> 8 & 0xff] ^ $S3[$R3 >> 16 & 0xff]; - $R1^= $this->safe_intval($t0 + ($t1 << 1) + $K[--$ki]); + $R1 ^= self::safe_intval($t0 + ($t1 << 1) + $K[--$ki]); $R1 = $R1 >> 1 & 0x7fffffff | $R1 << 31; - $R0 = ($R0 >> 31 & 0x1 | $R0 << 1) ^ $this->safe_intval($t0 + $t1 + $K[--$ki]); + $R0 = ($R0 >> 31 & 0x1 | $R0 << 1) ^ self::safe_intval($t0 + $t1 + $K[--$ki]); } // @codingStandardsIgnoreStart @@ -668,149 +700,117 @@ function _decryptBlock($in) /** * Setup the performance-optimized function for de/encrypt() * - * @see \phpseclib\Crypt\Base::_setupInlineCrypt() - * @access private + * @see \phpseclib3\Crypt\Common\SymmetricKey::_setupInlineCrypt() */ - function _setupInlineCrypt() + protected function setupInlineCrypt() { - $lambda_functions =& self::_getLambdaFunctions(); - - // Max. 10 Ultra-Hi-optimized inline-crypt functions. After that, we'll (still) create very fast code, but not the ultimate fast one. - // (Currently, for Crypt_Twofish, one generated $lambda_function cost on php5.5@32bit ~140kb unfreeable mem and ~240kb on php5.5@64bit) - $gen_hi_opt_code = (bool)(count($lambda_functions) < 10); - - // Generation of a unique hash for our generated code - $code_hash = "Crypt_Twofish, {$this->mode}"; - if ($gen_hi_opt_code) { - $code_hash = str_pad($code_hash, 32) . $this->_hashInlineCryptFunction($this->key); - } - - $safeint = $this->safe_intval_inline(); - - if (!isset($lambda_functions[$code_hash])) { - switch (true) { - case $gen_hi_opt_code: - $K = $this->K; - $init_crypt = ' - static $S0, $S1, $S2, $S3; - if (!$S0) { - for ($i = 0; $i < 256; ++$i) { - $S0[] = (int)$self->S0[$i]; - $S1[] = (int)$self->S1[$i]; - $S2[] = (int)$self->S2[$i]; - $S3[] = (int)$self->S3[$i]; - } - } - '; - break; - default: - $K = array(); - for ($i = 0; $i < 40; ++$i) { - $K[] = '$K_' . $i; - } - $init_crypt = ' - $S0 = $self->S0; - $S1 = $self->S1; - $S2 = $self->S2; - $S3 = $self->S3; - list(' . implode(',', $K) . ') = $self->K; - '; + $K = $this->K; + $init_crypt = ' + static $S0, $S1, $S2, $S3; + if (!$S0) { + for ($i = 0; $i < 256; ++$i) { + $S0[] = (int)$this->S0[$i]; + $S1[] = (int)$this->S1[$i]; + $S2[] = (int)$this->S2[$i]; + $S3[] = (int)$this->S3[$i]; + } } - - // Generating encrypt code: - $encrypt_block = ' - $in = unpack("V4", $in); - $R0 = '.$K[0].' ^ $in[1]; - $R1 = '.$K[1].' ^ $in[2]; - $R2 = '.$K[2].' ^ $in[3]; - $R3 = '.$K[3].' ^ $in[4]; - '; - for ($ki = 7, $i = 0; $i < 8; ++$i) { - $encrypt_block.= ' - $t0 = $S0[ $R0 & 0xff] ^ - $S1[($R0 >> 8) & 0xff] ^ - $S2[($R0 >> 16) & 0xff] ^ - $S3[($R0 >> 24) & 0xff]; - $t1 = $S0[($R1 >> 24) & 0xff] ^ - $S1[ $R1 & 0xff] ^ - $S2[($R1 >> 8) & 0xff] ^ - $S3[($R1 >> 16) & 0xff]; + '; + + $safeint = self::safe_intval_inline(); + + // Generating encrypt code: + $encrypt_block = ' + $in = unpack("V4", $in); + $R0 = ' . $K[0] . ' ^ $in[1]; + $R1 = ' . $K[1] . ' ^ $in[2]; + $R2 = ' . $K[2] . ' ^ $in[3]; + $R3 = ' . $K[3] . ' ^ $in[4]; + '; + for ($ki = 7, $i = 0; $i < 8; ++$i) { + $encrypt_block .= ' + $t0 = $S0[ $R0 & 0xff] ^ + $S1[($R0 >> 8) & 0xff] ^ + $S2[($R0 >> 16) & 0xff] ^ + $S3[($R0 >> 24) & 0xff]; + $t1 = $S0[($R1 >> 24) & 0xff] ^ + $S1[ $R1 & 0xff] ^ + $S2[($R1 >> 8) & 0xff] ^ + $S3[($R1 >> 16) & 0xff]; $R2^= ' . sprintf($safeint, '$t0 + $t1 + ' . $K[++$ki]) . '; - $R2 = ($R2 >> 1 & 0x7fffffff) | ($R2 << 31); - $R3 = ((($R3 >> 31) & 1) | ($R3 << 1)) ^ ' . sprintf($safeint, '($t0 + ($t1 << 1) + ' . $K[++$ki] . ')') . '; - - $t0 = $S0[ $R2 & 0xff] ^ - $S1[($R2 >> 8) & 0xff] ^ - $S2[($R2 >> 16) & 0xff] ^ - $S3[($R2 >> 24) & 0xff]; - $t1 = $S0[($R3 >> 24) & 0xff] ^ - $S1[ $R3 & 0xff] ^ - $S2[($R3 >> 8) & 0xff] ^ - $S3[($R3 >> 16) & 0xff]; - $R0^= ' . sprintf($safeint, '($t0 + $t1 + ' . $K[++$ki] . ')') . '; - $R0 = ($R0 >> 1 & 0x7fffffff) | ($R0 << 31); - $R1 = ((($R1 >> 31) & 1) | ($R1 << 1)) ^ ' . sprintf($safeint, '($t0 + ($t1 << 1) + ' . $K[++$ki] . ')') . '; - '; - } - $encrypt_block.= ' - $in = pack("V4", ' . $K[4] . ' ^ $R2, - ' . $K[5] . ' ^ $R3, - ' . $K[6] . ' ^ $R0, - ' . $K[7] . ' ^ $R1); + $R2 = ($R2 >> 1 & 0x7fffffff) | ($R2 << 31); + $R3 = ((($R3 >> 31) & 1) | ($R3 << 1)) ^ ' . sprintf($safeint, '($t0 + ($t1 << 1) + ' . $K[++$ki] . ')') . '; + + $t0 = $S0[ $R2 & 0xff] ^ + $S1[($R2 >> 8) & 0xff] ^ + $S2[($R2 >> 16) & 0xff] ^ + $S3[($R2 >> 24) & 0xff]; + $t1 = $S0[($R3 >> 24) & 0xff] ^ + $S1[ $R3 & 0xff] ^ + $S2[($R3 >> 8) & 0xff] ^ + $S3[($R3 >> 16) & 0xff]; + $R0^= ' . sprintf($safeint, '($t0 + $t1 + ' . $K[++$ki] . ')') . '; + $R0 = ($R0 >> 1 & 0x7fffffff) | ($R0 << 31); + $R1 = ((($R1 >> 31) & 1) | ($R1 << 1)) ^ ' . sprintf($safeint, '($t0 + ($t1 << 1) + ' . $K[++$ki] . ')') . '; '; - - // Generating decrypt code: - $decrypt_block = ' - $in = unpack("V4", $in); - $R0 = '.$K[4].' ^ $in[1]; - $R1 = '.$K[5].' ^ $in[2]; - $R2 = '.$K[6].' ^ $in[3]; - $R3 = '.$K[7].' ^ $in[4]; - '; - for ($ki = 40, $i = 0; $i < 8; ++$i) { - $decrypt_block.= ' - $t0 = $S0[$R0 & 0xff] ^ - $S1[$R0 >> 8 & 0xff] ^ - $S2[$R0 >> 16 & 0xff] ^ - $S3[$R0 >> 24 & 0xff]; - $t1 = $S0[$R1 >> 24 & 0xff] ^ - $S1[$R1 & 0xff] ^ - $S2[$R1 >> 8 & 0xff] ^ - $S3[$R1 >> 16 & 0xff]; - $R3^= ' . sprintf($safeint, '$t0 + ($t1 << 1) + ' . $K[--$ki]) . '; - $R3 = $R3 >> 1 & 0x7fffffff | $R3 << 31; - $R2 = ($R2 >> 31 & 0x1 | $R2 << 1) ^ ' . sprintf($safeint, '($t0 + $t1 + '.$K[--$ki] . ')') . '; - - $t0 = $S0[$R2 & 0xff] ^ - $S1[$R2 >> 8 & 0xff] ^ - $S2[$R2 >> 16 & 0xff] ^ - $S3[$R2 >> 24 & 0xff]; - $t1 = $S0[$R3 >> 24 & 0xff] ^ - $S1[$R3 & 0xff] ^ - $S2[$R3 >> 8 & 0xff] ^ - $S3[$R3 >> 16 & 0xff]; - $R1^= ' . sprintf($safeint, '$t0 + ($t1 << 1) + ' . $K[--$ki]) . '; - $R1 = $R1 >> 1 & 0x7fffffff | $R1 << 31; - $R0 = ($R0 >> 31 & 0x1 | $R0 << 1) ^ ' . sprintf($safeint, '($t0 + $t1 + '.$K[--$ki] . ')') . '; - '; - } - $decrypt_block.= ' - $in = pack("V4", ' . $K[0] . ' ^ $R2, - ' . $K[1] . ' ^ $R3, - ' . $K[2] . ' ^ $R0, - ' . $K[3] . ' ^ $R1); + } + $encrypt_block .= ' + $in = pack("V4", ' . $K[4] . ' ^ $R2, + ' . $K[5] . ' ^ $R3, + ' . $K[6] . ' ^ $R0, + ' . $K[7] . ' ^ $R1); + '; + + // Generating decrypt code: + $decrypt_block = ' + $in = unpack("V4", $in); + $R0 = ' . $K[4] . ' ^ $in[1]; + $R1 = ' . $K[5] . ' ^ $in[2]; + $R2 = ' . $K[6] . ' ^ $in[3]; + $R3 = ' . $K[7] . ' ^ $in[4]; + '; + for ($ki = 40, $i = 0; $i < 8; ++$i) { + $decrypt_block .= ' + $t0 = $S0[$R0 & 0xff] ^ + $S1[$R0 >> 8 & 0xff] ^ + $S2[$R0 >> 16 & 0xff] ^ + $S3[$R0 >> 24 & 0xff]; + $t1 = $S0[$R1 >> 24 & 0xff] ^ + $S1[$R1 & 0xff] ^ + $S2[$R1 >> 8 & 0xff] ^ + $S3[$R1 >> 16 & 0xff]; + $R3^= ' . sprintf($safeint, '$t0 + ($t1 << 1) + ' . $K[--$ki]) . '; + $R3 = $R3 >> 1 & 0x7fffffff | $R3 << 31; + $R2 = ($R2 >> 31 & 0x1 | $R2 << 1) ^ ' . sprintf($safeint, '($t0 + $t1 + ' . $K[--$ki] . ')') . '; + + $t0 = $S0[$R2 & 0xff] ^ + $S1[$R2 >> 8 & 0xff] ^ + $S2[$R2 >> 16 & 0xff] ^ + $S3[$R2 >> 24 & 0xff]; + $t1 = $S0[$R3 >> 24 & 0xff] ^ + $S1[$R3 & 0xff] ^ + $S2[$R3 >> 8 & 0xff] ^ + $S3[$R3 >> 16 & 0xff]; + $R1^= ' . sprintf($safeint, '$t0 + ($t1 << 1) + ' . $K[--$ki]) . '; + $R1 = $R1 >> 1 & 0x7fffffff | $R1 << 31; + $R0 = ($R0 >> 31 & 0x1 | $R0 << 1) ^ ' . sprintf($safeint, '($t0 + $t1 + ' . $K[--$ki] . ')') . '; '; - - $lambda_functions[$code_hash] = $this->_createInlineCryptFunction( - array( - 'init_crypt' => $init_crypt, - 'init_encrypt' => '', - 'init_decrypt' => '', - 'encrypt_block' => $encrypt_block, - 'decrypt_block' => $decrypt_block - ) - ); } - $this->inline_crypt = $lambda_functions[$code_hash]; + $decrypt_block .= ' + $in = pack("V4", ' . $K[0] . ' ^ $R2, + ' . $K[1] . ' ^ $R3, + ' . $K[2] . ' ^ $R0, + ' . $K[3] . ' ^ $R1); + '; + + $this->inline_crypt = $this->createInlineCryptFunction( + [ + 'init_crypt' => $init_crypt, + 'init_encrypt' => '', + 'init_decrypt' => '', + 'encrypt_block' => $encrypt_block, + 'decrypt_block' => $decrypt_block + ] + ); } } diff --git a/vendor/phpseclib/phpseclib/phpseclib/Exception/BadConfigurationException.php b/vendor/phpseclib/phpseclib/phpseclib/Exception/BadConfigurationException.php new file mode 100644 index 00000000..1aabcae0 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Exception/BadConfigurationException.php @@ -0,0 +1,23 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\Exception; + +/** + * BadConfigurationException + * + * @author Jim Wigginton + */ +class BadConfigurationException extends \RuntimeException +{ +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Exception/BadDecryptionException.php b/vendor/phpseclib/phpseclib/phpseclib/Exception/BadDecryptionException.php new file mode 100644 index 00000000..88331dce --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Exception/BadDecryptionException.php @@ -0,0 +1,23 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\Exception; + +/** + * BadDecryptionException + * + * @author Jim Wigginton + */ +class BadDecryptionException extends \RuntimeException +{ +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Exception/BadModeException.php b/vendor/phpseclib/phpseclib/phpseclib/Exception/BadModeException.php new file mode 100644 index 00000000..87689b22 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Exception/BadModeException.php @@ -0,0 +1,23 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\Exception; + +/** + * BadModeException + * + * @author Jim Wigginton + */ +class BadModeException extends \RuntimeException +{ +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Exception/ConnectionClosedException.php b/vendor/phpseclib/phpseclib/phpseclib/Exception/ConnectionClosedException.php new file mode 100644 index 00000000..6aaccbad --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Exception/ConnectionClosedException.php @@ -0,0 +1,23 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\Exception; + +/** + * ConnectionClosedException + * + * @author Jim Wigginton + */ +class ConnectionClosedException extends \RuntimeException +{ +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Exception/FileNotFoundException.php b/vendor/phpseclib/phpseclib/phpseclib/Exception/FileNotFoundException.php new file mode 100644 index 00000000..66e72709 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Exception/FileNotFoundException.php @@ -0,0 +1,23 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\Exception; + +/** + * FileNotFoundException + * + * @author Jim Wigginton + */ +class FileNotFoundException extends \RuntimeException +{ +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Exception/InconsistentSetupException.php b/vendor/phpseclib/phpseclib/phpseclib/Exception/InconsistentSetupException.php new file mode 100644 index 00000000..23c38fb0 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Exception/InconsistentSetupException.php @@ -0,0 +1,23 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\Exception; + +/** + * InconsistentSetupException + * + * @author Jim Wigginton + */ +class InconsistentSetupException extends \RuntimeException +{ +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Exception/InsufficientSetupException.php b/vendor/phpseclib/phpseclib/phpseclib/Exception/InsufficientSetupException.php new file mode 100644 index 00000000..4f4114d7 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Exception/InsufficientSetupException.php @@ -0,0 +1,23 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\Exception; + +/** + * InsufficientSetupException + * + * @author Jim Wigginton + */ +class InsufficientSetupException extends \RuntimeException +{ +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Exception/NoKeyLoadedException.php b/vendor/phpseclib/phpseclib/phpseclib/Exception/NoKeyLoadedException.php new file mode 100644 index 00000000..7ec2fe9b --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Exception/NoKeyLoadedException.php @@ -0,0 +1,23 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\Exception; + +/** + * NoKeyLoadedException + * + * @author Jim Wigginton + */ +class NoKeyLoadedException extends \RuntimeException +{ +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Exception/NoSupportedAlgorithmsException.php b/vendor/phpseclib/phpseclib/phpseclib/Exception/NoSupportedAlgorithmsException.php new file mode 100644 index 00000000..b3ea8f38 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Exception/NoSupportedAlgorithmsException.php @@ -0,0 +1,23 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\Exception; + +/** + * NoSupportedAlgorithmsException + * + * @author Jim Wigginton + */ +class NoSupportedAlgorithmsException extends \RuntimeException +{ +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Exception/UnableToConnectException.php b/vendor/phpseclib/phpseclib/phpseclib/Exception/UnableToConnectException.php new file mode 100644 index 00000000..bfa005b4 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Exception/UnableToConnectException.php @@ -0,0 +1,23 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\Exception; + +/** + * UnableToConnectException + * + * @author Jim Wigginton + */ +class UnableToConnectException extends \RuntimeException +{ +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Exception/UnsupportedAlgorithmException.php b/vendor/phpseclib/phpseclib/phpseclib/Exception/UnsupportedAlgorithmException.php new file mode 100644 index 00000000..210a9a5c --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Exception/UnsupportedAlgorithmException.php @@ -0,0 +1,23 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\Exception; + +/** + * UnsupportedAlgorithmException + * + * @author Jim Wigginton + */ +class UnsupportedAlgorithmException extends \RuntimeException +{ +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Exception/UnsupportedCurveException.php b/vendor/phpseclib/phpseclib/phpseclib/Exception/UnsupportedCurveException.php new file mode 100644 index 00000000..99152152 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Exception/UnsupportedCurveException.php @@ -0,0 +1,23 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\Exception; + +/** + * UnsupportedCurveException + * + * @author Jim Wigginton + */ +class UnsupportedCurveException extends \RuntimeException +{ +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Exception/UnsupportedFormatException.php b/vendor/phpseclib/phpseclib/phpseclib/Exception/UnsupportedFormatException.php new file mode 100644 index 00000000..e207d7e2 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Exception/UnsupportedFormatException.php @@ -0,0 +1,23 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\Exception; + +/** + * UnsupportedFormatException + * + * @author Jim Wigginton + */ +class UnsupportedFormatException extends \RuntimeException +{ +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Exception/UnsupportedOperationException.php b/vendor/phpseclib/phpseclib/phpseclib/Exception/UnsupportedOperationException.php new file mode 100644 index 00000000..9a115444 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Exception/UnsupportedOperationException.php @@ -0,0 +1,23 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\Exception; + +/** + * UnsupportedOperationException + * + * @author Jim Wigginton + */ +class UnsupportedOperationException extends \RuntimeException +{ +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ANSI.php b/vendor/phpseclib/phpseclib/phpseclib/File/ANSI.php index b6874d35..5803a372 100644 --- a/vendor/phpseclib/phpseclib/phpseclib/File/ANSI.php +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ANSI.php @@ -5,27 +5,23 @@ * * PHP version 5 * - * If you call read() in \phpseclib\Net\SSH2 you may get {@link http://en.wikipedia.org/wiki/ANSI_escape_code ANSI escape codes} back. + * If you call read() in \phpseclib3\Net\SSH2 you may get {@link http://en.wikipedia.org/wiki/ANSI_escape_code ANSI escape codes} back. * They'd look like chr(0x1B) . '[00m' or whatever (0x1B = ESC). They tell a * {@link http://en.wikipedia.org/wiki/Terminal_emulator terminal emulator} how to format the characters, what - * color to display them in, etc. \phpseclib\File\ANSI is a {@link http://en.wikipedia.org/wiki/VT100 VT100} terminal emulator. + * color to display them in, etc. \phpseclib3\File\ANSI is a {@link http://en.wikipedia.org/wiki/VT100 VT100} terminal emulator. * - * @category File - * @package ANSI * @author Jim Wigginton * @copyright 2012 Jim Wigginton * @license http://www.opensource.org/licenses/mit-license.html MIT License * @link http://phpseclib.sourceforge.net */ -namespace phpseclib\File; +namespace phpseclib3\File; /** * Pure-PHP ANSI Decoder * - * @package ANSI * @author Jim Wigginton - * @access public */ class ANSI { @@ -33,137 +29,120 @@ class ANSI * Max Width * * @var int - * @access private */ - var $max_x; + private $max_x; /** * Max Height * * @var int - * @access private */ - var $max_y; + private $max_y; /** * Max History * * @var int - * @access private */ - var $max_history; + private $max_history; /** * History * * @var array - * @access private */ - var $history; + private $history; /** * History Attributes * * @var array - * @access private */ - var $history_attrs; + private $history_attrs; /** * Current Column * * @var int - * @access private */ - var $x; + private $x; /** * Current Row * * @var int - * @access private */ - var $y; + private $y; /** * Old Column * * @var int - * @access private */ - var $old_x; + private $old_x; /** * Old Row * * @var int - * @access private */ - var $old_y; + private $old_y; /** * An empty attribute cell * * @var object - * @access private */ - var $base_attr_cell; + private $base_attr_cell; /** * The current attribute cell * * @var object - * @access private */ - var $attr_cell; + private $attr_cell; /** * An empty attribute row * * @var array - * @access private */ - var $attr_row; + private $attr_row; /** * The current screen text * - * @var array - * @access private + * @var list */ - var $screen; + private $screen; /** * The current screen attributes * * @var array - * @access private */ - var $attrs; + private $attrs; /** * Current ANSI code * * @var string - * @access private */ - var $ansi; + private $ansi; /** * Tokenization * * @var array - * @access private */ - var $tokenization; + private $tokenization; /** * Default Constructor. * - * @return \phpseclib\File\ANSI - * @access public + * @return \phpseclib3\File\ANSI */ - function __construct() + public function __construct() { $attr_cell = new \stdClass(); $attr_cell->bold = false; @@ -186,14 +165,13 @@ function __construct() * * @param int $x * @param int $y - * @access public */ - function setDimensions($x, $y) + public function setDimensions($x, $y) { $this->max_x = $x - 1; $this->max_y = $y - 1; $this->x = $this->y = 0; - $this->history = $this->history_attrs = array(); + $this->history = $this->history_attrs = []; $this->attr_row = array_fill(0, $this->max_x + 2, $this->base_attr_cell); $this->screen = array_fill(0, $this->max_y + 1, ''); $this->attrs = array_fill(0, $this->max_y + 1, $this->attr_row); @@ -204,9 +182,8 @@ function setDimensions($x, $y) * Set the number of lines that should be logged past the terminal height * * @param int $history - * @access public */ - function setHistory($history) + public function setHistory($history) { $this->max_history = $history; } @@ -215,9 +192,8 @@ function setHistory($history) * Load a string * * @param string $source - * @access public */ - function loadString($source) + public function loadString($source) { $this->setDimensions($this->max_x + 1, $this->max_y + 1); $this->appendString($source); @@ -227,14 +203,13 @@ function loadString($source) * Appdend a string * * @param string $source - * @access public */ - function appendString($source) + public function appendString($source) { - $this->tokenization = array(''); + $this->tokenization = ['']; for ($i = 0; $i < strlen($source); $i++) { if (strlen($this->ansi)) { - $this->ansi.= $source[$i]; + $this->ansi .= $source[$i]; $chr = ord($source[$i]); // http://en.wikipedia.org/wiki/ANSI_escape_code#Sequence_elements // single character CSI's not currently supported @@ -268,6 +243,7 @@ function appendString($source) array_shift($this->history); array_shift($this->history_attrs); } + // fall-through case "\x1B[K": // Clear screen from cursor right $this->screen[$this->y] = substr($this->screen[$this->y], 0, $this->x); @@ -282,28 +258,28 @@ function appendString($source) case "\x1B(B": // set united states g0 character set break; case "\x1BE": // Move to next line - $this->_newLine(); + $this->newLine(); $this->x = 0; break; default: switch (true) { case preg_match('#\x1B\[(\d+)B#', $this->ansi, $match): // Move cursor down n lines $this->old_y = $this->y; - $this->y+= $match[1]; + $this->y += (int) $match[1]; break; case preg_match('#\x1B\[(\d+);(\d+)H#', $this->ansi, $match): // Move cursor to screen location v,h $this->old_x = $this->x; $this->old_y = $this->y; $this->x = $match[2] - 1; - $this->y = $match[1] - 1; + $this->y = (int) $match[1] - 1; break; case preg_match('#\x1B\[(\d+)C#', $this->ansi, $match): // Move cursor right n lines $this->old_x = $this->x; - $this->x+= $match[1]; + $this->x += $match[1]; break; case preg_match('#\x1B\[(\d+)D#', $this->ansi, $match): // Move cursor left n lines $this->old_x = $this->x; - $this->x-= $match[1]; + $this->x -= $match[1]; if ($this->x < 0) { $this->x = 0; } @@ -376,13 +352,13 @@ function appendString($source) continue; } - $this->tokenization[count($this->tokenization) - 1].= $source[$i]; + $this->tokenization[count($this->tokenization) - 1] .= $source[$i]; switch ($source[$i]) { case "\r": $this->x = 0; break; case "\n": - $this->_newLine(); + $this->newLine(); break; case "\x08": // backspace if ($this->x) { @@ -403,7 +379,7 @@ function appendString($source) //if (!strlen($this->tokenization[count($this->tokenization) - 1])) { // array_pop($this->tokenization); //} - $this->ansi.= "\x1B"; + $this->ansi .= "\x1B"; break; default: $this->attrs[$this->y][$this->x] = clone $this->attr_cell; @@ -419,7 +395,7 @@ function appendString($source) if ($this->x > $this->max_x) { $this->x = 0; - $this->_newLine(); + $this->newLine(); } else { $this->x++; } @@ -432,19 +408,18 @@ function appendString($source) * * Also update the $this->screen and $this->history buffers * - * @access private */ - function _newLine() + private function newLine() { //if ($this->y < $this->max_y) { // $this->y++; //} while ($this->y >= $this->max_y) { - $this->history = array_merge($this->history, array(array_shift($this->screen))); + $this->history = array_merge($this->history, [array_shift($this->screen)]); $this->screen[] = ''; - $this->history_attrs = array_merge($this->history_attrs, array(array_shift($this->attrs))); + $this->history_attrs = array_merge($this->history_attrs, [array_shift($this->attrs)]); $this->attrs[] = $this->attr_row; if (count($this->history) >= $this->max_history) { @@ -460,10 +435,12 @@ function _newLine() /** * Returns the current coordinate without preformating * - * @access private + * @param \stdClass $last_attr + * @param \stdClass $cur_attr + * @param string $char * @return string */ - function _processCoordinate($last_attr, $cur_attr, $char) + private function processCoordinate(\stdClass $last_attr, \stdClass $cur_attr, $char) { $output = ''; @@ -471,7 +448,7 @@ function _processCoordinate($last_attr, $cur_attr, $char) $close = $open = ''; if ($last_attr->foreground != $cur_attr->foreground) { if ($cur_attr->foreground != 'white') { - $open.= ''; + $open .= ''; } if ($last_attr->foreground != 'white') { $close = '' . $close; @@ -479,7 +456,7 @@ function _processCoordinate($last_attr, $cur_attr, $char) } if ($last_attr->background != $cur_attr->background) { if ($cur_attr->background != 'black') { - $open.= ''; + $open .= ''; } if ($last_attr->background != 'black') { $close = '' . $close; @@ -487,29 +464,29 @@ function _processCoordinate($last_attr, $cur_attr, $char) } if ($last_attr->bold != $cur_attr->bold) { if ($cur_attr->bold) { - $open.= ''; + $open .= ''; } else { $close = '' . $close; } } if ($last_attr->underline != $cur_attr->underline) { if ($cur_attr->underline) { - $open.= ''; + $open .= ''; } else { $close = '' . $close; } } if ($last_attr->blink != $cur_attr->blink) { if ($cur_attr->blink) { - $open.= ''; + $open .= ''; } else { $close = '' . $close; } } - $output.= $close . $open; + $output .= $close . $open; } - $output.= htmlspecialchars($char); + $output .= htmlspecialchars($char); return $output; } @@ -517,59 +494,56 @@ function _processCoordinate($last_attr, $cur_attr, $char) /** * Returns the current screen without preformating * - * @access private * @return string */ - function _getScreen() + private function getScreenHelper() { $output = ''; $last_attr = $this->base_attr_cell; for ($i = 0; $i <= $this->max_y; $i++) { for ($j = 0; $j <= $this->max_x; $j++) { $cur_attr = $this->attrs[$i][$j]; - $output.= $this->_processCoordinate($last_attr, $cur_attr, isset($this->screen[$i][$j]) ? $this->screen[$i][$j] : ''); + $output .= $this->processCoordinate($last_attr, $cur_attr, isset($this->screen[$i][$j]) ? $this->screen[$i][$j] : ''); $last_attr = $this->attrs[$i][$j]; } - $output.= "\r\n"; + $output .= "\r\n"; } $output = substr($output, 0, -2); // close any remaining open tags - $output.= $this->_processCoordinate($last_attr, $this->base_attr_cell, ''); + $output .= $this->processCoordinate($last_attr, $this->base_attr_cell, ''); return rtrim($output); } /** * Returns the current screen * - * @access public * @return string */ - function getScreen() + public function getScreen() { - return '
' . $this->_getScreen() . '
'; + return '
' . $this->getScreenHelper() . '
'; } /** * Returns the current screen and the x previous lines * - * @access public * @return string */ - function getHistory() + public function getHistory() { $scrollback = ''; $last_attr = $this->base_attr_cell; for ($i = 0; $i < count($this->history); $i++) { for ($j = 0; $j <= $this->max_x + 1; $j++) { $cur_attr = $this->history_attrs[$i][$j]; - $scrollback.= $this->_processCoordinate($last_attr, $cur_attr, isset($this->history[$i][$j]) ? $this->history[$i][$j] : ''); + $scrollback .= $this->processCoordinate($last_attr, $cur_attr, isset($this->history[$i][$j]) ? $this->history[$i][$j] : ''); $last_attr = $this->history_attrs[$i][$j]; } - $scrollback.= "\r\n"; + $scrollback .= "\r\n"; } $base_attr_cell = $this->base_attr_cell; $this->base_attr_cell = $last_attr; - $scrollback.= $this->_getScreen(); + $scrollback .= $this->getScreen(); $this->base_attr_cell = $base_attr_cell; return '
' . $scrollback . '
'; diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1.php index dc5b78f6..e21589c5 100644 --- a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1.php +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1.php @@ -9,52 +9,39 @@ * utilized scheme is DER or the "Distinguished Encoding Rules". PEM's are base64 encoded * DER blobs. * - * \phpseclib\File\ASN1 decodes and encodes DER formatted messages and places them in a semantic context. + * \phpseclib3\File\ASN1 decodes and encodes DER formatted messages and places them in a semantic context. * * Uses the 1988 ASN.1 syntax. * - * @category File - * @package ASN1 * @author Jim Wigginton * @copyright 2012 Jim Wigginton * @license http://www.opensource.org/licenses/mit-license.html MIT License * @link http://phpseclib.sourceforge.net */ -namespace phpseclib\File; +namespace phpseclib3\File; -use phpseclib\File\ASN1\Element; -use phpseclib\Math\BigInteger; use DateTime; -use DateTimeZone; +use phpseclib3\Common\Functions\Strings; +use phpseclib3\File\ASN1\Element; +use phpseclib3\Math\BigInteger; /** * Pure-PHP ASN.1 Parser * - * @package ASN1 * @author Jim Wigginton - * @access public */ -class ASN1 +abstract class ASN1 { - /**#@+ - * Tag Classes - * - * @access private - * @link http://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#page=12 - */ + // Tag Classes + // http://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#page=12 const CLASS_UNIVERSAL = 0; const CLASS_APPLICATION = 1; const CLASS_CONTEXT_SPECIFIC = 2; const CLASS_PRIVATE = 3; - /**#@-*/ - /**#@+ - * Tag Classes - * - * @access private - * @link http://www.obj-sys.com/asn1tutorial/node124.html - */ + // Tag Classes + // http://www.obj-sys.com/asn1tutorial/node124.html const TYPE_BOOLEAN = 1; const TYPE_INTEGER = 2; const TYPE_BIT_STRING = 3; @@ -70,13 +57,9 @@ class ASN1 //const TYPE_RELATIVE_OID = 13; const TYPE_SEQUENCE = 16; // SEQUENCE OF const TYPE_SET = 17; // SET OF - /**#@-*/ - /**#@+ - * More Tag Classes - * - * @access private - * @link http://www.obj-sys.com/asn1tutorial/node10.html - */ + + // More Tag Classes + // http://www.obj-sys.com/asn1tutorial/node10.html const TYPE_NUMERIC_STRING = 18; const TYPE_PRINTABLE_STRING = 19; const TYPE_TELETEX_STRING = 20; // T61String @@ -90,47 +73,34 @@ class ASN1 const TYPE_UNIVERSAL_STRING = 28; //const TYPE_CHARACTER_STRING = 29; const TYPE_BMP_STRING = 30; - /**#@-*/ - /**#@+ - * Tag Aliases - * - * These tags are kinda place holders for other tags. - * - * @access private - */ + // Tag Aliases + // These tags are kinda place holders for other tags. const TYPE_CHOICE = -1; const TYPE_ANY = -2; - /**#@-*/ /** - * ASN.1 object identifier + * ASN.1 object identifiers * * @var array - * @access private * @link http://en.wikipedia.org/wiki/Object_identifier */ - var $oids = array(); + private static $oids = []; /** - * Default date format + * ASN.1 object identifier reverse mapping * - * @var string - * @access private - * @link http://php.net/class.datetime + * @var array */ - var $format = 'D, d M Y H:i:s O'; + private static $reverseOIDs = []; /** * Default date format * - * @var array - * @access private - * @see self::setTimeFormat() - * @see self::asn1map() + * @var string * @link http://php.net/class.datetime */ - var $encoded; + private static $format = 'D, d M Y H:i:s O'; /** * Filters @@ -138,22 +108,40 @@ class ASN1 * If the mapping type is self::TYPE_ANY what do we actually encode it as? * * @var array - * @access private - * @see self::_encode_der() + * @see self::encode_der() + */ + private static $filters; + + /** + * Current Location of most recent ASN.1 encode process + * + * Useful for debug purposes + * + * @var array + * @see self::encode_der() + */ + private static $location; + + /** + * DER Encoded String + * + * In case we need to create ASN1\Element object's.. + * + * @var string + * @see self::decodeDER() */ - var $filters; + private static $encoded; /** * Type mapping table for the ANY type. * - * Structured or unknown types are mapped to a \phpseclib\File\ASN1\Element. + * Structured or unknown types are mapped to a \phpseclib3\File\ASN1\Element. * Unambiguous types get the direct mapping (int/real/bool). * Others are mapped as a choice, with an extra indexing level. * * @var array - * @access public */ - var $ANYmap = array( + const ANY_MAP = [ self::TYPE_BOOLEAN => true, self::TYPE_INTEGER => true, self::TYPE_BIT_STRING => 'bitString', @@ -176,7 +164,7 @@ class ASN1 self::TYPE_UNIVERSAL_STRING => 'universalString', //self::TYPE_CHARACTER_STRING => 'characterString', self::TYPE_BMP_STRING => 'bmpString' - ); + ]; /** * String type to character size mapping table. @@ -185,9 +173,8 @@ class ASN1 * size == 0 indicates variable length encoding. * * @var array - * @access public */ - var $stringTypeSize = array( + const STRING_TYPE_SIZE = [ self::TYPE_UTF8_STRING => 0, self::TYPE_BMP_STRING => 2, self::TYPE_UNIVERSAL_STRING => 4, @@ -195,26 +182,30 @@ class ASN1 self::TYPE_TELETEX_STRING => 1, self::TYPE_IA5_STRING => 1, self::TYPE_VISIBLE_STRING => 1, - ); + ]; /** * Parse BER-encoding * * Serves a similar purpose to openssl's asn1parse * - * @param string $encoded - * @return array - * @access public + * @param Element|string $encoded + * @return ?array */ - function decodeBER($encoded) + public static function decodeBER($encoded) { if ($encoded instanceof Element) { $encoded = $encoded->element; } - $this->encoded = $encoded; - // encapsulate in an array for BC with the old decodeBER - return array($this->_decode_ber($encoded)); + self::$encoded = $encoded; + + $decoded = self::decode_ber($encoded); + if ($decoded === false) { + return null; + } + + return [self::decode_ber($encoded)]; } /** @@ -227,13 +218,15 @@ function decodeBER($encoded) * @param string $encoded * @param int $start * @param int $encoded_pos - * @return array - * @access private + * @return array|bool */ - function _decode_ber($encoded, $start = 0, $encoded_pos = 0) + private static function decode_ber($encoded, $start = 0, $encoded_pos = 0) { - $current = array('start' => $start); + $current = ['start' => $start]; + if (!isset($encoded[$encoded_pos])) { + return false; + } $type = ord($encoded[$encoded_pos++]); $startOffset = 1; @@ -244,6 +237,9 @@ function _decode_ber($encoded, $start = 0, $encoded_pos = 0) $tag = 0; // process septets (since the eighth bit is ignored, it's not an octet) do { + if (!isset($encoded[$encoded_pos])) { + return false; + } $temp = ord($encoded[$encoded_pos++]); $startOffset++; $loop = $temp >> 7; @@ -257,9 +253,12 @@ function _decode_ber($encoded, $start = 0, $encoded_pos = 0) } while ($loop); } - $start+= $startOffset; + $start += $startOffset; // Length, as discussed in paragraph 8.1.3 of X.690-0207.pdf#page=13 + if (!isset($encoded[$encoded_pos])) { + return false; + } $length = ord($encoded[$encoded_pos++]); $start++; if ($length == 0x80) { // indefinite length @@ -269,15 +268,16 @@ function _decode_ber($encoded, $start = 0, $encoded_pos = 0) } elseif ($length & 0x80) { // definite length, long form // technically, the long form of the length can be represented by up to 126 octets (bytes), but we'll only // support it up to four. - $length&= 0x7F; + $length &= 0x7F; $temp = substr($encoded, $encoded_pos, $length); $encoded_pos += $length; // tags of indefinte length don't really have a header length; this length includes the tag - $current+= array('headerlength' => $length + 2); - $start+= $length; + $current += ['headerlength' => $length + 2]; + $start += $length; extract(unpack('Nlength', substr(str_pad($temp, 4, chr(0), STR_PAD_LEFT), -4))); + /** @var integer $length */ } else { - $current+= array('headerlength' => 2); + $current += ['headerlength' => 2]; } if ($length > (strlen($encoded) - $encoded_pos)) { @@ -304,36 +304,36 @@ function _decode_ber($encoded, $start = 0, $encoded_pos = 0) case self::CLASS_PRIVATE: case self::CLASS_CONTEXT_SPECIFIC: if (!$constructed) { - return array( + return [ 'type' => $class, 'constant' => $tag, 'content' => $content, 'length' => $length + $start - $current['start'] - ); + ] + $current; } - $newcontent = array(); + $newcontent = []; $remainingLength = $length; while ($remainingLength > 0) { - $temp = $this->_decode_ber($content, $start, $content_pos); + $temp = self::decode_ber($content, $start, $content_pos); if ($temp === false) { break; } $length = $temp['length']; // end-of-content octets - see paragraph 8.1.5 if (substr($content, $content_pos + $length, 2) == "\0\0") { - $length+= 2; - $start+= $length; + $length += 2; + $start += $length; $newcontent[] = $temp; break; } - $start+= $length; - $remainingLength-= $length; + $start += $length; + $remainingLength -= $length; $newcontent[] = $temp; $content_pos += $length; } - return array( + return [ 'type' => $class, 'constant' => $tag, // the array encapsulation is for BC with the old format @@ -342,10 +342,10 @@ function _decode_ber($encoded, $start = 0, $encoded_pos = 0) // the absence of $content['headerlength'] is how we know if something is indefinite or not. // technically, it could be defined to be 2 and then another indicator could be used but whatever. 'length' => $start - $current['start'] - ) + $current; + ] + $current; } - $current+= array('type' => $tag); + $current += ['type' => $tag]; // decode UNIVERSAL tags switch ($tag) { @@ -372,18 +372,18 @@ function _decode_ber($encoded, $start = 0, $encoded_pos = 0) if (!$constructed) { $current['content'] = substr($content, $content_pos); } else { - $temp = $this->_decode_ber($content, $start, $content_pos); + $temp = self::decode_ber($content, $start, $content_pos); if ($temp === false) { return false; } - $length-= (strlen($content) - $content_pos); + $length -= (strlen($content) - $content_pos); $last = count($temp) - 1; for ($i = 0; $i < $last; $i++) { // all subtags should be bit strings if ($temp[$i]['type'] != self::TYPE_BIT_STRING) { return false; } - $current['content'].= substr($temp[$i]['content'], 1); + $current['content'] .= substr($temp[$i]['content'], 1); } // all subtags should be bit strings if ($temp[$last]['type'] != self::TYPE_BIT_STRING) { @@ -399,7 +399,7 @@ function _decode_ber($encoded, $start = 0, $encoded_pos = 0) $current['content'] = ''; $length = 0; while (substr($content, $content_pos, 2) != "\0\0") { - $temp = $this->_decode_ber($content, $length + $start, $content_pos); + $temp = self::decode_ber($content, $length + $start, $content_pos); if ($temp === false) { return false; } @@ -408,11 +408,11 @@ function _decode_ber($encoded, $start = 0, $encoded_pos = 0) if ($temp['type'] != self::TYPE_OCTET_STRING) { return false; } - $current['content'].= $temp['content']; - $length+= $temp['length']; + $current['content'] .= $temp['content']; + $length += $temp['length']; } if (substr($content, $content_pos, 2) == "\0\0") { - $length+= 2; // +2 for the EOC + $length += 2; // +2 for the EOC } } break; @@ -428,7 +428,7 @@ function _decode_ber($encoded, $start = 0, $encoded_pos = 0) return false; } $offset = 0; - $current['content'] = array(); + $current['content'] = []; $content_len = strlen($content); while ($content_pos < $content_len) { // if indefinite length construction was used and we have an end-of-content string next @@ -437,20 +437,20 @@ function _decode_ber($encoded, $start = 0, $encoded_pos = 0) $length = $offset + 2; // +2 for the EOC break 2; } - $temp = $this->_decode_ber($content, $start + $offset, $content_pos); + $temp = self::decode_ber($content, $start + $offset, $content_pos); if ($temp === false) { return false; } $content_pos += $temp['length']; $current['content'][] = $temp; - $offset+= $temp['length']; + $offset += $temp['length']; } break; case self::TYPE_OBJECT_IDENTIFIER: if ($constructed) { return false; } - $current['content'] = $this->_decodeOID(substr($content, $content_pos)); + $current['content'] = self::decodeOID(substr($content, $content_pos)); if ($current['content'] === false) { return false; } @@ -493,16 +493,16 @@ function _decode_ber($encoded, $start = 0, $encoded_pos = 0) if ($constructed) { return false; } - $current['content'] = $this->_decodeTime(substr($content, $content_pos), $tag); + $current['content'] = self::decodeTime(substr($content, $content_pos), $tag); break; default: return false; } - $start+= $length; + $start += $length; // ie. length is the length of the full TLV encoding - it's not just the length of the value - return $current + array('length' => $start - $current['start']); + return $current + ['length' => $start - $current['start']]; } /** @@ -515,15 +515,10 @@ function _decode_ber($encoded, $start = 0, $encoded_pos = 0) * @param array $decoded * @param array $mapping * @param array $special - * @return array - * @access public + * @return array|bool|Element|string|null */ - function asn1map($decoded, $mapping, $special = array()) + public static function asn1map(array $decoded, $mapping, $special = []) { - if (!is_array($decoded)) { - return false; - } - if (isset($mapping['explicit']) && is_array($decoded['content'])) { $decoded = $decoded['content'][0]; } @@ -531,12 +526,13 @@ function asn1map($decoded, $mapping, $special = array()) switch (true) { case $mapping['type'] == self::TYPE_ANY: $intype = $decoded['type']; - if (isset($decoded['constant']) || !isset($this->ANYmap[$intype]) || (ord($this->encoded[$decoded['start']]) & 0x20)) { - return new Element(substr($this->encoded, $decoded['start'], $decoded['length'])); + // !isset(self::ANY_MAP[$intype]) produces a fatal error on PHP 5.6 + if (isset($decoded['constant']) || !array_key_exists($intype, self::ANY_MAP) || (ord(self::$encoded[$decoded['start']]) & 0x20)) { + return new Element(substr(self::$encoded, $decoded['start'], $decoded['length'])); } - $inmap = $this->ANYmap[$intype]; + $inmap = self::ANY_MAP[$intype]; if (is_string($inmap)) { - return array($inmap => $this->asn1map($decoded, array('type' => $intype) + $mapping, $special)); + return [$inmap => self::asn1map($decoded, ['type' => $intype] + $mapping, $special)]; } break; case $mapping['type'] == self::TYPE_CHOICE: @@ -544,19 +540,19 @@ function asn1map($decoded, $mapping, $special = array()) switch (true) { case isset($option['constant']) && $option['constant'] == $decoded['constant']: case !isset($option['constant']) && $option['type'] == $decoded['type']: - $value = $this->asn1map($decoded, $option, $special); + $value = self::asn1map($decoded, $option, $special); break; case !isset($option['constant']) && $option['type'] == self::TYPE_CHOICE: - $v = $this->asn1map($decoded, $option, $special); + $v = self::asn1map($decoded, $option, $special); if (isset($v)) { $value = $v; } } if (isset($value)) { if (isset($special[$key])) { - $value = call_user_func($special[$key], $value); + $value = $special[$key]($value); } - return array($key => $value); + return [$key => $value]; } } return null; @@ -582,13 +578,13 @@ function asn1map($decoded, $mapping, $special = array()) switch ($decoded['type']) { case self::TYPE_SEQUENCE: - $map = array(); + $map = []; // ignore the min and max if (isset($mapping['min']) && isset($mapping['max'])) { $child = $mapping['children']; foreach ($decoded['content'] as $content) { - if (($map[] = $this->asn1map($content, $child, $special)) === null) { + if (($map[] = self::asn1map($content, $child, $special)) === null) { return null; } } @@ -624,43 +620,43 @@ function asn1map($decoded, $mapping, $special = array()) $maymatch = $constant == $temp['constant'] && $childClass == $tempClass; } else { // Can only match if no constant expected and type matches or is generic. - $maymatch = !isset($child['constant']) && array_search($child['type'], array($temp['type'], self::TYPE_ANY, self::TYPE_CHOICE)) !== false; + $maymatch = !isset($child['constant']) && array_search($child['type'], [$temp['type'], self::TYPE_ANY, self::TYPE_CHOICE]) !== false; } } } if ($maymatch) { // Attempt submapping. - $candidate = $this->asn1map($temp, $child, $special); + $candidate = self::asn1map($temp, $child, $special); $maymatch = $candidate !== null; } if ($maymatch) { // Got the match: use it. if (isset($special[$key])) { - $candidate = call_user_func($special[$key], $candidate); + $candidate = $special[$key]($candidate); } $map[$key] = $candidate; $i++; } elseif (isset($child['default'])) { - $map[$key] = $child['default']; // Use default. + $map[$key] = $child['default']; } elseif (!isset($child['optional'])) { return null; // Syntax error. } } // Fail mapping if all input items have not been consumed. - return $i < $n ? null: $map; + return $i < $n ? null : $map; // the main diff between sets and sequences is the encapsulation of the foreach in another for loop case self::TYPE_SET: - $map = array(); + $map = []; // ignore the min and max if (isset($mapping['min']) && isset($mapping['max'])) { $child = $mapping['children']; foreach ($decoded['content'] as $content) { - if (($map[] = $this->asn1map($content, $child, $special)) === null) { + if (($map[] = self::asn1map($content, $child, $special)) === null) { return null; } } @@ -696,13 +692,13 @@ function asn1map($decoded, $mapping, $special = array()) $maymatch = $constant == $temp['constant'] && $childClass == $tempClass; } else { // Can only match if no constant expected and type matches or is generic. - $maymatch = !isset($child['constant']) && array_search($child['type'], array($temp['type'], self::TYPE_ANY, self::TYPE_CHOICE)) !== false; + $maymatch = !isset($child['constant']) && array_search($child['type'], [$temp['type'], self::TYPE_ANY, self::TYPE_CHOICE]) !== false; } } if ($maymatch) { // Attempt submapping. - $candidate = $this->asn1map($temp, $child, $special); + $candidate = self::asn1map($temp, $child, $special); $maymatch = $candidate !== null; } @@ -712,7 +708,7 @@ function asn1map($decoded, $mapping, $special = array()) // Got the match: use it. if (isset($special[$key])) { - $candidate = call_user_func($special[$key], $candidate); + $candidate = $special[$key]($candidate); } $map[$key] = $candidate; break; @@ -730,7 +726,7 @@ function asn1map($decoded, $mapping, $special = array()) } return $map; case self::TYPE_OBJECT_IDENTIFIER: - return isset($this->oids[$decoded['content']]) ? $this->oids[$decoded['content']] : $decoded['content']; + return isset(self::$oids[$decoded['content']]) ? self::$oids[$decoded['content']] : $decoded['content']; case self::TYPE_UTC_TIME: case self::TYPE_GENERALIZED_TIME: // for explicitly tagged optional stuff @@ -741,9 +737,9 @@ function asn1map($decoded, $mapping, $special = array()) // in theory, doing isset($mapping['implicit']) would work but malformed certs do exist // in the wild that OpenSSL decodes without issue so we'll support them as well if (!is_object($decoded['content'])) { - $decoded['content'] = $this->_decodeTime($decoded['content'], $decoded['type']); + $decoded['content'] = self::decodeTime($decoded['content'], $decoded['type']); } - return $decoded['content'] ? $decoded['content']->format($this->format) : false; + return $decoded['content'] ? $decoded['content']->format(self::$format) : false; case self::TYPE_BIT_STRING: if (isset($mapping['mapping'])) { $offset = ord($decoded['content'][0]); @@ -756,7 +752,7 @@ function asn1map($decoded, $mapping, $special = array()) therefore ensure that different semantics are not associated with such values which differ only in the number of trailing 0 bits." */ - $bits = count($mapping['mapping']) == $size ? array() : array_fill(0, count($mapping['mapping']) - $size, false); + $bits = count($mapping['mapping']) == $size ? [] : array_fill(0, count($mapping['mapping']) - $size, false); for ($i = strlen($decoded['content']) - 1; $i > 0; $i--) { $current = ord($decoded['content'][$i]); for ($j = $offset; $j < 8; $j++) { @@ -764,7 +760,7 @@ function asn1map($decoded, $mapping, $special = array()) } $offset = 0; } - $values = array(); + $values = []; $map = array_reverse($mapping['mapping']); foreach ($map as $i => $value) { if ($bits[$i]) { @@ -773,12 +769,12 @@ function asn1map($decoded, $mapping, $special = array()) } return $values; } + // fall-through case self::TYPE_OCTET_STRING: - return base64_encode($decoded['content']); + return $decoded['content']; case self::TYPE_NULL: return ''; case self::TYPE_BOOLEAN: - return $decoded['content']; case self::TYPE_NUMERIC_STRING: case self::TYPE_PRINTABLE_STRING: case self::TYPE_TELETEX_STRING: @@ -807,6 +803,26 @@ function asn1map($decoded, $mapping, $special = array()) } } + /** + * DER-decode the length + * + * DER supports lengths up to (2**8)**127, however, we'll only support lengths up to (2**8)**4. See + * {@link http://itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#p=13 X.690 paragraph 8.1.3} for more information. + * + * @param string $string + * @return int + */ + public static function decodeLength(&$string) + { + $length = ord(Strings::shift($string)); + if ($length & 0x80) { // definite length, long form + $length &= 0x7F; + $temp = Strings::shift($string, $length); + list(, $length) = unpack('N', substr(str_pad($temp, 4, chr(0), STR_PAD_LEFT), -4)); + } + return $length; + } + /** * ASN.1 Encode * @@ -815,29 +831,27 @@ function asn1map($decoded, $mapping, $special = array()) * * "Special" mappings can be applied via $special. * - * @param string $source - * @param string $mapping + * @param Element|string|array $source + * @param array $mapping * @param array $special * @return string - * @access public */ - function encodeDER($source, $mapping, $special = array()) + public static function encodeDER($source, $mapping, $special = []) { - $this->location = array(); - return $this->_encode_der($source, $mapping, null, $special); + self::$location = []; + return self::encode_der($source, $mapping, null, $special); } /** * ASN.1 Encode (Helper function) * - * @param string $source - * @param string $mapping + * @param Element|string|array|null $source + * @param array $mapping * @param int $idx * @param array $special * @return string - * @access private */ - function _encode_der($source, $mapping, $idx = null, $special = array()) + private static function encode_der($source, array $mapping, $idx = null, array $special = []) { if ($source instanceof Element) { return $source->element; @@ -850,9 +864,9 @@ function _encode_der($source, $mapping, $idx = null, $special = array()) if (isset($idx)) { if (isset($special[$idx])) { - $source = call_user_func($special[$idx], $source); + $source = $special[$idx]($source); } - $this->location[] = $idx; + self::$location[] = $idx; } $tag = $mapping['type']; @@ -860,19 +874,19 @@ function _encode_der($source, $mapping, $idx = null, $special = array()) switch ($tag) { case self::TYPE_SET: // Children order is not important, thus process in sequence. case self::TYPE_SEQUENCE: - $tag|= 0x20; // set the constructed bit + $tag |= 0x20; // set the constructed bit // ignore the min and max if (isset($mapping['min']) && isset($mapping['max'])) { - $value = array(); + $value = []; $child = $mapping['children']; foreach ($source as $content) { - $temp = $this->_encode_der($content, $child, null, $special); + $temp = self::encode_der($content, $child, null, $special); if ($temp === false) { return false; } - $value[]= $temp; + $value[] = $temp; } /* "The encodings of the component values of a set-of value shall appear in ascending order, the encodings being compared as octet strings with the shorter components being padded at their trailing end with 0-octets. @@ -895,7 +909,7 @@ function _encode_der($source, $mapping, $idx = null, $special = array()) continue; } - $temp = $this->_encode_der($source[$key], $child, $key, $special); + $temp = self::encode_der($source[$key], $child, $key, $special); if ($temp === false) { return false; } @@ -919,13 +933,13 @@ function _encode_der($source, $mapping, $idx = null, $special = array()) */ if (isset($child['explicit']) || $child['type'] == self::TYPE_CHOICE) { $subtag = chr((self::CLASS_CONTEXT_SPECIFIC << 6) | 0x20 | $child['constant']); - $temp = $subtag . $this->_encodeLength(strlen($temp)) . $temp; + $temp = $subtag . self::encodeLength(strlen($temp)) . $temp; } else { $subtag = chr((self::CLASS_CONTEXT_SPECIFIC << 6) | (ord($temp[0]) & 0x20) | $child['constant']); $temp = $subtag . substr($temp, 1); } } - $value.= $temp; + $value .= $temp; } break; case self::TYPE_CHOICE: @@ -936,7 +950,7 @@ function _encode_der($source, $mapping, $idx = null, $special = array()) continue; } - $temp = $this->_encode_der($source[$key], $child, $key, $special); + $temp = self::encode_der($source[$key], $child, $key, $special); if ($temp === false) { return false; } @@ -953,7 +967,7 @@ function _encode_der($source, $mapping, $idx = null, $special = array()) if (isset($child['constant'])) { if (isset($child['explicit']) || $child['type'] == self::TYPE_CHOICE) { $subtag = chr((self::CLASS_CONTEXT_SPECIFIC << 6) | 0x20 | $child['constant']); - $temp = $subtag . $this->_encodeLength(strlen($temp)) . $temp; + $temp = $subtag . self::encodeLength(strlen($temp)) . $temp; } else { $subtag = chr((self::CLASS_CONTEXT_SPECIFIC << 6) | (ord($temp[0]) & 0x20) | $child['constant']); $temp = $subtag . substr($temp, 1); @@ -962,7 +976,7 @@ function _encode_der($source, $mapping, $idx = null, $special = array()) } if (isset($idx)) { - array_pop($this->location); + array_pop(self::$location); } if ($temp && isset($mapping['cast'])) { @@ -992,8 +1006,11 @@ function _encode_der($source, $mapping, $idx = null, $special = array()) case self::TYPE_UTC_TIME: case self::TYPE_GENERALIZED_TIME: $format = $mapping['type'] == self::TYPE_UTC_TIME ? 'y' : 'Y'; - $format.= 'mdHis'; - $date = new DateTime($source, new DateTimeZone('GMT')); + $format .= 'mdHis'; + // if $source does _not_ include timezone information within it then assume that the timezone is GMT + $date = new \DateTime($source, new \DateTimeZone('GMT')); + // if $source _does_ include timezone information within it then convert the time to GMT + $date->setTimezone(new \DateTimeZone('GMT')); $value = $date->format($format) . 'Z'; break; case self::TYPE_BIT_STRING: @@ -1023,46 +1040,47 @@ function _encode_der($source, $mapping, $idx = null, $special = array()) $bits = implode('', array_pad($bits, $size + $offset + 1, 0)); $bytes = explode(' ', rtrim(chunk_split($bits, 8, ' '))); foreach ($bytes as $byte) { - $value.= chr(bindec($byte)); + $value .= chr(bindec($byte)); } break; } + // fall-through case self::TYPE_OCTET_STRING: /* The initial octet shall encode, as an unsigned binary integer with bit 1 as the least significant bit, the number of unused bits in the final subsequent octet. The number shall be in the range zero to seven. -- http://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#page=16 */ - $value = base64_decode($source); + $value = $source; break; case self::TYPE_OBJECT_IDENTIFIER: - $value = $this->_encodeOID($source); + $value = self::encodeOID($source); break; case self::TYPE_ANY: - $loc = $this->location; + $loc = self::$location; if (isset($idx)) { - array_pop($this->location); + array_pop(self::$location); } switch (true) { case !isset($source): - return $this->_encode_der(null, array('type' => self::TYPE_NULL) + $mapping, null, $special); + return self::encode_der(null, ['type' => self::TYPE_NULL] + $mapping, null, $special); case is_int($source): case $source instanceof BigInteger: - return $this->_encode_der($source, array('type' => self::TYPE_INTEGER) + $mapping, null, $special); + return self::encode_der($source, ['type' => self::TYPE_INTEGER] + $mapping, null, $special); case is_float($source): - return $this->_encode_der($source, array('type' => self::TYPE_REAL) + $mapping, null, $special); + return self::encode_der($source, ['type' => self::TYPE_REAL] + $mapping, null, $special); case is_bool($source): - return $this->_encode_der($source, array('type' => self::TYPE_BOOLEAN) + $mapping, null, $special); + return self::encode_der($source, ['type' => self::TYPE_BOOLEAN] + $mapping, null, $special); case is_array($source) && count($source) == 1: $typename = implode('', array_keys($source)); - $outtype = array_search($typename, $this->ANYmap, true); + $outtype = array_search($typename, self::ANY_MAP, true); if ($outtype !== false) { - return $this->_encode_der($source[$typename], array('type' => $outtype) + $mapping, null, $special); + return self::encode_der($source[$typename], ['type' => $outtype] + $mapping, null, $special); } } - $filters = $this->filters; + $filters = self::$filters; foreach ($loc as $part) { if (!isset($filters[$part])) { $filters = false; @@ -1071,10 +1089,9 @@ function _encode_der($source, $mapping, $idx = null, $special = array()) $filters = $filters[$part]; } if ($filters === false) { - user_error('No filters defined for ' . implode('/', $loc)); - return false; + throw new \RuntimeException('No filters defined for ' . implode('/', $loc)); } - return $this->_encode_der($source, $filters + $mapping, null, $special); + return self::encode_der($source, $filters + $mapping, null, $special); case self::TYPE_NULL: $value = ''; break; @@ -1095,44 +1112,23 @@ function _encode_der($source, $mapping, $idx = null, $special = array()) $value = $source ? "\xFF" : "\x00"; break; default: - user_error('Mapping provides no type definition for ' . implode('/', $this->location)); - return false; + throw new \RuntimeException('Mapping provides no type definition for ' . implode('/', self::$location)); } if (isset($idx)) { - array_pop($this->location); + array_pop(self::$location); } if (isset($mapping['cast'])) { if (isset($mapping['explicit']) || $mapping['type'] == self::TYPE_CHOICE) { - $value = chr($tag) . $this->_encodeLength(strlen($value)) . $value; + $value = chr($tag) . self::encodeLength(strlen($value)) . $value; $tag = ($mapping['class'] << 6) | 0x20 | $mapping['cast']; } else { $tag = ($mapping['class'] << 6) | (ord($temp[0]) & 0x20) | $mapping['cast']; } } - return chr($tag) . $this->_encodeLength(strlen($value)) . $value; - } - - /** - * DER-encode the length - * - * DER supports lengths up to (2**8)**127, however, we'll only support lengths up to (2**8)**4. See - * {@link http://itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#p=13 X.690 paragraph 8.1.3} for more information. - * - * @access private - * @param int $length - * @return string - */ - function _encodeLength($length) - { - if ($length <= 0x7F) { - return chr($length); - } - - $temp = ltrim(pack('N', $length), chr(0)); - return pack('Ca*', 0x80 | strlen($temp), $temp); + return chr($tag) . self::encodeLength(strlen($value)) . $value; } /** @@ -1140,18 +1136,17 @@ function _encodeLength($length) * * Called by _decode_ber() * - * @access private * @param string $content * @return string */ - function _decodeOID($content) + public static function decodeOID($content) { static $eighty; if (!$eighty) { $eighty = new BigInteger(80); } - $oid = array(); + $oid = []; $pos = 0; $len = strlen($content); @@ -1193,11 +1188,10 @@ function _decodeOID($content) * * Called by _encode_der() * - * @access private * @param string $source * @return string */ - function _encodeOID($source) + public static function encodeOID($source) { static $mask, $zero, $forty; if (!$mask) { @@ -1206,11 +1200,15 @@ function _encodeOID($source) $forty = new BigInteger(40); } - $oid = preg_match('#(?:\d+\.)+#', $source) ? $source : array_search($source, $this->oids); + if (!preg_match('#(?:\d+\.)+#', $source)) { + $oid = isset(self::$reverseOIDs[$source]) ? self::$reverseOIDs[$source] : false; + } else { + $oid = $source; + } if ($oid === false) { - user_error('Invalid OID'); - return false; + throw new \RuntimeException('Invalid OID'); } + $parts = explode('.', $oid); $part1 = array_shift($parts); $part2 = array_shift($parts); @@ -1236,7 +1234,7 @@ function _encodeOID($source) } $temp[strlen($temp) - 1] = $temp[strlen($temp) - 1] & chr(0x7F); } - $value.= $temp; + $value .= $temp; } return $value; @@ -1247,12 +1245,11 @@ function _encodeOID($source) * * Called by _decode_ber() and in the case of implicit tags asn1map(). * - * @access private * @param string $content * @param int $tag - * @return string + * @return \DateTime|false */ - function _decodeTime($content, $tag) + private static function decodeTime($content, $tag) { /* UTCTime: http://tools.ietf.org/html/rfc5280#section-4.1.2.5.1 @@ -1274,7 +1271,7 @@ function _decodeTime($content, $tag) $prefix = substr($content, 0, 2) >= 50 ? '19' : '20'; $content = $prefix . $content; } elseif (strpos($content, '.') !== false) { - $format.= '.u'; + $format .= '.u'; } if ($content[strlen($content) - 1] == 'Z') { @@ -1282,12 +1279,12 @@ function _decodeTime($content, $tag) } if (strpos($content, '-') !== false || strpos($content, '+') !== false) { - $format.= 'O'; + $format .= 'O'; } // error supression isn't necessary as of PHP 7.0: // http://php.net/manual/en/migration70.other-changes.php - return @DateTime::createFromFormat($format, $content); + return @\DateTime::createFromFormat($format, $content); } /** @@ -1295,55 +1292,38 @@ function _decodeTime($content, $tag) * * Sets the time / date format for asn1map(). * - * @access public * @param string $format */ - function setTimeFormat($format) + public static function setTimeFormat($format) { - $this->format = $format; + self::$format = $format; } /** * Load OIDs * * Load the relevant OIDs for a particular ASN.1 semantic mapping. + * Previously loaded OIDs are retained. * - * @access public * @param array $oids */ - function loadOIDs($oids) + public static function loadOIDs(array $oids) { - $this->oids = $oids; + self::$reverseOIDs += $oids; + self::$oids = array_flip(self::$reverseOIDs); } /** - * Load filters + * Set filters * - * See \phpseclib\File\X509, etc, for an example. + * See \phpseclib3\File\X509, etc, for an example. + * Previously loaded filters are not retained. * - * @access public * @param array $filters */ - function loadFilters($filters) - { - $this->filters = $filters; - } - - /** - * String Shift - * - * Inspired by array_shift - * - * @param string $string - * @param int $index - * @return string - * @access private - */ - function _string_shift(&$string, $index = 1) + public static function setFilters(array $filters) { - $substr = substr($string, 0, $index); - $string = substr($string, $index); - return $substr; + self::$filters = $filters; } /** @@ -1356,15 +1336,15 @@ function _string_shift(&$string, $index = 1) * @param int $from * @param int $to * @return string - * @access public */ - function convert($in, $from = self::TYPE_UTF8_STRING, $to = self::TYPE_UTF8_STRING) + public static function convert($in, $from = self::TYPE_UTF8_STRING, $to = self::TYPE_UTF8_STRING) { - if (!isset($this->stringTypeSize[$from]) || !isset($this->stringTypeSize[$to])) { + // isset(self::STRING_TYPE_SIZE[$from] returns a fatal error on PHP 5.6 + if (!array_key_exists($from, self::STRING_TYPE_SIZE) || !array_key_exists($to, self::STRING_TYPE_SIZE)) { return false; } - $insize = $this->stringTypeSize[$from]; - $outsize = $this->stringTypeSize[$to]; + $insize = self::STRING_TYPE_SIZE[$from]; + $outsize = self::STRING_TYPE_SIZE[$to]; $inlength = strlen($in); $out = ''; @@ -1379,8 +1359,10 @@ function convert($in, $from = self::TYPE_UTF8_STRING, $to = self::TYPE_UTF8_STRI case $insize == 4: $c = ($c << 8) | ord($in[$i++]); $c = ($c << 8) | ord($in[$i++]); + // fall-through case $insize == 2: $c = ($c << 8) | ord($in[$i++]); + // fall-through case $insize == 1: break; case ($c & 0x80) == 0x00: @@ -1409,9 +1391,11 @@ function convert($in, $from = self::TYPE_UTF8_STRING, $to = self::TYPE_UTF8_STRI $c >>= 8; $v .= chr($c & 0xFF); $c >>= 8; + // fall-through case $outsize == 2: $v .= chr($c & 0xFF); $c >>= 8; + // fall-through case $outsize == 1: $v .= chr($c & 0xFF); $c >>= 8; @@ -1424,18 +1408,23 @@ function convert($in, $from = self::TYPE_UTF8_STRING, $to = self::TYPE_UTF8_STRI case $c >= 0x04000000: $v .= chr(0x80 | ($c & 0x3F)); $c = ($c >> 6) | 0x04000000; + // fall-through case $c >= 0x00200000: $v .= chr(0x80 | ($c & 0x3F)); $c = ($c >> 6) | 0x00200000; + // fall-through case $c >= 0x00010000: $v .= chr(0x80 | ($c & 0x3F)); $c = ($c >> 6) | 0x00010000; + // fall-through case $c >= 0x00000800: $v .= chr(0x80 | ($c & 0x3F)); $c = ($c >> 6) | 0x00000800; + // fall-through case $c >= 0x00000080: $v .= chr(0x80 | ($c & 0x3F)); $c = ($c >> 6) | 0x000000C0; + // fall-through default: $v .= chr($c); break; @@ -1444,4 +1433,77 @@ function convert($in, $from = self::TYPE_UTF8_STRING, $to = self::TYPE_UTF8_STRI } return $out; } + + /** + * Extract raw BER from Base64 encoding + * + * @param string $str + * @return string + */ + public static function extractBER($str) + { + /* X.509 certs are assumed to be base64 encoded but sometimes they'll have additional things in them + * above and beyond the ceritificate. + * ie. some may have the following preceding the -----BEGIN CERTIFICATE----- line: + * + * Bag Attributes + * localKeyID: 01 00 00 00 + * subject=/O=organization/OU=org unit/CN=common name + * issuer=/O=organization/CN=common name + */ + if (strlen($str) > ini_get('pcre.backtrack_limit')) { + $temp = $str; + } else { + $temp = preg_replace('#.*?^-+[^-]+-+[\r\n ]*$#ms', '', $str, 1); + $temp = preg_replace('#-+END.*[\r\n ]*.*#ms', '', $temp, 1); + } + // remove new lines + $temp = str_replace(["\r", "\n", ' '], '', $temp); + // remove the -----BEGIN CERTIFICATE----- and -----END CERTIFICATE----- stuff + $temp = preg_replace('#^-+[^-]+-+|-+[^-]+-+$#', '', $temp); + $temp = preg_match('#^[a-zA-Z\d/+]*={0,2}$#', $temp) ? Strings::base64_decode($temp) : false; + return $temp != false ? $temp : $str; + } + + /** + * DER-encode the length + * + * DER supports lengths up to (2**8)**127, however, we'll only support lengths up to (2**8)**4. See + * {@link http://itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#p=13 X.690 paragraph 8.1.3} for more information. + * + * @param int $length + * @return string + */ + public static function encodeLength($length) + { + if ($length <= 0x7F) { + return chr($length); + } + + $temp = ltrim(pack('N', $length), chr(0)); + return pack('Ca*', 0x80 | strlen($temp), $temp); + } + + /** + * Returns the OID corresponding to a name + * + * What's returned in the associative array returned by loadX509() (or load*()) is either a name or an OID if + * no OID to name mapping is available. The problem with this is that what may be an unmapped OID in one version + * of phpseclib may not be unmapped in the next version, so apps that are looking at this OID may not be able + * to work from version to version. + * + * This method will return the OID if a name is passed to it and if no mapping is avialable it'll assume that + * what's being passed to it already is an OID and return that instead. A few examples. + * + * getOID('2.16.840.1.101.3.4.2.1') == '2.16.840.1.101.3.4.2.1' + * getOID('id-sha256') == '2.16.840.1.101.3.4.2.1' + * getOID('zzz') == 'zzz' + * + * @param string $name + * @return string + */ + public static function getOID($name) + { + return isset(self::$reverseOIDs[$name]) ? self::$reverseOIDs[$name] : $name; + } } diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Element.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Element.php index 68246e2b..6540b421 100644 --- a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Element.php +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Element.php @@ -1,27 +1,25 @@ * @copyright 2012 Jim Wigginton * @license http://www.opensource.org/licenses/mit-license.html MIT License * @link http://phpseclib.sourceforge.net */ -namespace phpseclib\File\ASN1; +namespace phpseclib3\File\ASN1; /** - * ASN.1 Element + * ASN.1 Raw Element * - * Bypass normal encoding rules in phpseclib\File\ASN1::encodeDER() + * An ASN.1 ANY mapping will return an ASN1\Element object. Use of this object + * will also bypass the normal encoding rules in ASN1::encodeDER() * - * @package ASN1 * @author Jim Wigginton - * @access public */ class Element { @@ -29,18 +27,16 @@ class Element * Raw element value * * @var string - * @access private */ - var $element; + public $element; /** * Constructor * * @param string $encoded - * @return \phpseclib\File\ASN1\Element - * @access public + * @return \phpseclib3\File\ASN1\Element */ - function __construct($encoded) + public function __construct($encoded) { $this->element = $encoded; } diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AccessDescription.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AccessDescription.php new file mode 100644 index 00000000..1cbc5a59 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AccessDescription.php @@ -0,0 +1,32 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * AccessDescription + * + * @author Jim Wigginton + */ +abstract class AccessDescription +{ + const MAP = [ + 'type' => ASN1::TYPE_SEQUENCE, + 'children' => [ + 'accessMethod' => ['type' => ASN1::TYPE_OBJECT_IDENTIFIER], + 'accessLocation' => GeneralName::MAP + ] + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AdministrationDomainName.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AdministrationDomainName.php new file mode 100644 index 00000000..04183a13 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AdministrationDomainName.php @@ -0,0 +1,36 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * AdministrationDomainName + * + * @author Jim Wigginton + */ +abstract class AdministrationDomainName +{ + const MAP = [ + 'type' => ASN1::TYPE_CHOICE, + // if class isn't present it's assumed to be \phpseclib3\File\ASN1::CLASS_UNIVERSAL or + // (if constant is present) \phpseclib3\File\ASN1::CLASS_CONTEXT_SPECIFIC + 'class' => ASN1::CLASS_APPLICATION, + 'cast' => 2, + 'children' => [ + 'numeric' => ['type' => ASN1::TYPE_NUMERIC_STRING], + 'printable' => ['type' => ASN1::TYPE_PRINTABLE_STRING] + ] + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AlgorithmIdentifier.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AlgorithmIdentifier.php new file mode 100644 index 00000000..0da7eb10 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AlgorithmIdentifier.php @@ -0,0 +1,35 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * AlgorithmIdentifier + * + * @author Jim Wigginton + */ +abstract class AlgorithmIdentifier +{ + const MAP = [ + 'type' => ASN1::TYPE_SEQUENCE, + 'children' => [ + 'algorithm' => ['type' => ASN1::TYPE_OBJECT_IDENTIFIER], + 'parameters' => [ + 'type' => ASN1::TYPE_ANY, + 'optional' => true + ] + ] + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AnotherName.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AnotherName.php new file mode 100644 index 00000000..d96c170b --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AnotherName.php @@ -0,0 +1,37 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * AnotherName + * + * @author Jim Wigginton + */ +abstract class AnotherName +{ + const MAP = [ + 'type' => ASN1::TYPE_SEQUENCE, + 'children' => [ + 'type-id' => ['type' => ASN1::TYPE_OBJECT_IDENTIFIER], + 'value' => [ + 'type' => ASN1::TYPE_ANY, + 'constant' => 0, + 'optional' => true, + 'explicit' => true + ] + ] + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Attribute.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Attribute.php new file mode 100644 index 00000000..38a6aeef --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Attribute.php @@ -0,0 +1,37 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * Attribute + * + * @author Jim Wigginton + */ +abstract class Attribute +{ + const MAP = [ + 'type' => ASN1::TYPE_SEQUENCE, + 'children' => [ + 'type' => AttributeType::MAP, + 'value' => [ + 'type' => ASN1::TYPE_SET, + 'min' => 1, + 'max' => -1, + 'children' => AttributeValue::MAP + ] + ] + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AttributeType.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AttributeType.php new file mode 100644 index 00000000..5cbc2bcc --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AttributeType.php @@ -0,0 +1,26 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * AttributeType + * + * @author Jim Wigginton + */ +abstract class AttributeType +{ + const MAP = ['type' => ASN1::TYPE_OBJECT_IDENTIFIER]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AttributeTypeAndValue.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AttributeTypeAndValue.php new file mode 100644 index 00000000..fe414f16 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AttributeTypeAndValue.php @@ -0,0 +1,32 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * AttributeTypeAndValue + * + * @author Jim Wigginton + */ +abstract class AttributeTypeAndValue +{ + const MAP = [ + 'type' => ASN1::TYPE_SEQUENCE, + 'children' => [ + 'type' => AttributeType::MAP, + 'value' => AttributeValue::MAP + ] + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AttributeValue.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AttributeValue.php new file mode 100644 index 00000000..3b3b6d2e --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AttributeValue.php @@ -0,0 +1,26 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * AttributeValue + * + * @author Jim Wigginton + */ +abstract class AttributeValue +{ + const MAP = ['type' => ASN1::TYPE_ANY]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Attributes.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Attributes.php new file mode 100644 index 00000000..cd53ecfa --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Attributes.php @@ -0,0 +1,31 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * Attributes + * + * @author Jim Wigginton + */ +abstract class Attributes +{ + const MAP = [ + 'type' => ASN1::TYPE_SET, + 'min' => 1, + 'max' => -1, + 'children' => Attribute::MAP + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AuthorityInfoAccessSyntax.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AuthorityInfoAccessSyntax.php new file mode 100644 index 00000000..3e80a55d --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AuthorityInfoAccessSyntax.php @@ -0,0 +1,31 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * AuthorityInfoAccessSyntax + * + * @author Jim Wigginton + */ +abstract class AuthorityInfoAccessSyntax +{ + const MAP = [ + 'type' => ASN1::TYPE_SEQUENCE, + 'min' => 1, + 'max' => -1, + 'children' => AccessDescription::MAP + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AuthorityKeyIdentifier.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AuthorityKeyIdentifier.php new file mode 100644 index 00000000..e7ec5b28 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AuthorityKeyIdentifier.php @@ -0,0 +1,45 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * AuthorityKeyIdentifier + * + * @author Jim Wigginton + */ +abstract class AuthorityKeyIdentifier +{ + const MAP = [ + 'type' => ASN1::TYPE_SEQUENCE, + 'children' => [ + 'keyIdentifier' => [ + 'constant' => 0, + 'optional' => true, + 'implicit' => true + ] + KeyIdentifier::MAP, + 'authorityCertIssuer' => [ + 'constant' => 1, + 'optional' => true, + 'implicit' => true + ] + GeneralNames::MAP, + 'authorityCertSerialNumber' => [ + 'constant' => 2, + 'optional' => true, + 'implicit' => true + ] + CertificateSerialNumber::MAP + ] + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/BaseDistance.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/BaseDistance.php new file mode 100644 index 00000000..e59668ab --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/BaseDistance.php @@ -0,0 +1,26 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * BaseDistance + * + * @author Jim Wigginton + */ +abstract class BaseDistance +{ + const MAP = ['type' => ASN1::TYPE_INTEGER]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/BasicConstraints.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/BasicConstraints.php new file mode 100644 index 00000000..587ef1b0 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/BasicConstraints.php @@ -0,0 +1,39 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * BasicConstraints + * + * @author Jim Wigginton + */ +abstract class BasicConstraints +{ + const MAP = [ + 'type' => ASN1::TYPE_SEQUENCE, + 'children' => [ + 'cA' => [ + 'type' => ASN1::TYPE_BOOLEAN, + 'optional' => true, + 'default' => false + ], + 'pathLenConstraint' => [ + 'type' => ASN1::TYPE_INTEGER, + 'optional' => true + ] + ] + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/BuiltInDomainDefinedAttribute.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/BuiltInDomainDefinedAttribute.php new file mode 100644 index 00000000..e81bc78e --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/BuiltInDomainDefinedAttribute.php @@ -0,0 +1,32 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * BuiltInDomainDefinedAttribute + * + * @author Jim Wigginton + */ +abstract class BuiltInDomainDefinedAttribute +{ + const MAP = [ + 'type' => ASN1::TYPE_SEQUENCE, + 'children' => [ + 'type' => ['type' => ASN1::TYPE_PRINTABLE_STRING], + 'value' => ['type' => ASN1::TYPE_PRINTABLE_STRING] + ] + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/BuiltInDomainDefinedAttributes.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/BuiltInDomainDefinedAttributes.php new file mode 100644 index 00000000..471e88f9 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/BuiltInDomainDefinedAttributes.php @@ -0,0 +1,31 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * BuiltInDomainDefinedAttributes + * + * @author Jim Wigginton + */ +abstract class BuiltInDomainDefinedAttributes +{ + const MAP = [ + 'type' => ASN1::TYPE_SEQUENCE, + 'min' => 1, + 'max' => 4, // ub-domain-defined-attributes + 'children' => BuiltInDomainDefinedAttribute::MAP + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/BuiltInStandardAttributes.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/BuiltInStandardAttributes.php new file mode 100644 index 00000000..752f400d --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/BuiltInStandardAttributes.php @@ -0,0 +1,67 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * BuiltInStandardAttributes + * + * @author Jim Wigginton + */ +abstract class BuiltInStandardAttributes +{ + const MAP = [ + 'type' => ASN1::TYPE_SEQUENCE, + 'children' => [ + 'country-name' => ['optional' => true] + CountryName::MAP, + 'administration-domain-name' => ['optional' => true] + AdministrationDomainName::MAP, + 'network-address' => [ + 'constant' => 0, + 'optional' => true, + 'implicit' => true + ] + NetworkAddress::MAP, + 'terminal-identifier' => [ + 'constant' => 1, + 'optional' => true, + 'implicit' => true + ] + TerminalIdentifier::MAP, + 'private-domain-name' => [ + 'constant' => 2, + 'optional' => true, + 'explicit' => true + ] + PrivateDomainName::MAP, + 'organization-name' => [ + 'constant' => 3, + 'optional' => true, + 'implicit' => true + ] + OrganizationName::MAP, + 'numeric-user-identifier' => [ + 'constant' => 4, + 'optional' => true, + 'implicit' => true + ] + NumericUserIdentifier::MAP, + 'personal-name' => [ + 'constant' => 5, + 'optional' => true, + 'implicit' => true + ] + PersonalName::MAP, + 'organizational-unit-names' => [ + 'constant' => 6, + 'optional' => true, + 'implicit' => true + ] + OrganizationalUnitNames::MAP + ] + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CPSuri.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CPSuri.php new file mode 100644 index 00000000..56e58887 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CPSuri.php @@ -0,0 +1,26 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * CPSuri + * + * @author Jim Wigginton + */ +abstract class CPSuri +{ + const MAP = ['type' => ASN1::TYPE_IA5_STRING]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CRLDistributionPoints.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CRLDistributionPoints.php new file mode 100644 index 00000000..79860b2f --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CRLDistributionPoints.php @@ -0,0 +1,31 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * CRLDistributionPoints + * + * @author Jim Wigginton + */ +abstract class CRLDistributionPoints +{ + const MAP = [ + 'type' => ASN1::TYPE_SEQUENCE, + 'min' => 1, + 'max' => -1, + 'children' => DistributionPoint::MAP + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CRLNumber.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CRLNumber.php new file mode 100644 index 00000000..f6cb9567 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CRLNumber.php @@ -0,0 +1,26 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * CRLNumber + * + * @author Jim Wigginton + */ +abstract class CRLNumber +{ + const MAP = ['type' => ASN1::TYPE_INTEGER]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CRLReason.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CRLReason.php new file mode 100644 index 00000000..d3736529 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CRLReason.php @@ -0,0 +1,41 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * CRLReason + * + * @author Jim Wigginton + */ +abstract class CRLReason +{ + const MAP = [ + 'type' => ASN1::TYPE_ENUMERATED, + 'mapping' => [ + 'unspecified', + 'keyCompromise', + 'cACompromise', + 'affiliationChanged', + 'superseded', + 'cessationOfOperation', + 'certificateHold', + // Value 7 is not used. + 8 => 'removeFromCRL', + 'privilegeWithdrawn', + 'aACompromise' + ] + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CertPolicyId.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CertPolicyId.php new file mode 100644 index 00000000..d7e7776e --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CertPolicyId.php @@ -0,0 +1,26 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * CertPolicyId + * + * @author Jim Wigginton + */ +abstract class CertPolicyId +{ + const MAP = ['type' => ASN1::TYPE_OBJECT_IDENTIFIER]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Certificate.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Certificate.php new file mode 100644 index 00000000..01943a0d --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Certificate.php @@ -0,0 +1,33 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * Certificate + * + * @author Jim Wigginton + */ +abstract class Certificate +{ + const MAP = [ + 'type' => ASN1::TYPE_SEQUENCE, + 'children' => [ + 'tbsCertificate' => TBSCertificate::MAP, + 'signatureAlgorithm' => AlgorithmIdentifier::MAP, + 'signature' => ['type' => ASN1::TYPE_BIT_STRING] + ] + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CertificateIssuer.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CertificateIssuer.php new file mode 100644 index 00000000..ccd68dde --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CertificateIssuer.php @@ -0,0 +1,24 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +/** + * CertificateIssuer + * + * @author Jim Wigginton + */ +abstract class CertificateIssuer +{ + const MAP = GeneralNames::MAP; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CertificateList.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CertificateList.php new file mode 100644 index 00000000..d54ed6d9 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CertificateList.php @@ -0,0 +1,33 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * CertificateList + * + * @author Jim Wigginton + */ +abstract class CertificateList +{ + const MAP = [ + 'type' => ASN1::TYPE_SEQUENCE, + 'children' => [ + 'tbsCertList' => TBSCertList::MAP, + 'signatureAlgorithm' => AlgorithmIdentifier::MAP, + 'signature' => ['type' => ASN1::TYPE_BIT_STRING] + ] + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CertificatePolicies.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CertificatePolicies.php new file mode 100644 index 00000000..ec0fa6b5 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CertificatePolicies.php @@ -0,0 +1,31 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * CertificatePolicies + * + * @author Jim Wigginton + */ +abstract class CertificatePolicies +{ + const MAP = [ + 'type' => ASN1::TYPE_SEQUENCE, + 'min' => 1, + 'max' => -1, + 'children' => PolicyInformation::MAP + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CertificateSerialNumber.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CertificateSerialNumber.php new file mode 100644 index 00000000..06ec944c --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CertificateSerialNumber.php @@ -0,0 +1,26 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * CertificateSerialNumber + * + * @author Jim Wigginton + */ +abstract class CertificateSerialNumber +{ + const MAP = ['type' => ASN1::TYPE_INTEGER]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CertificationRequest.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CertificationRequest.php new file mode 100644 index 00000000..2da70ed6 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CertificationRequest.php @@ -0,0 +1,33 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * CertificationRequest + * + * @author Jim Wigginton + */ +abstract class CertificationRequest +{ + const MAP = [ + 'type' => ASN1::TYPE_SEQUENCE, + 'children' => [ + 'certificationRequestInfo' => CertificationRequestInfo::MAP, + 'signatureAlgorithm' => AlgorithmIdentifier::MAP, + 'signature' => ['type' => ASN1::TYPE_BIT_STRING] + ] + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CertificationRequestInfo.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CertificationRequestInfo.php new file mode 100644 index 00000000..ce6dc880 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CertificationRequestInfo.php @@ -0,0 +1,41 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * CertificationRequestInfo + * + * @author Jim Wigginton + */ +abstract class CertificationRequestInfo +{ + const MAP = [ + 'type' => ASN1::TYPE_SEQUENCE, + 'children' => [ + 'version' => [ + 'type' => ASN1::TYPE_INTEGER, + 'mapping' => ['v1'] + ], + 'subject' => Name::MAP, + 'subjectPKInfo' => SubjectPublicKeyInfo::MAP, + 'attributes' => [ + 'constant' => 0, + 'optional' => true, + 'implicit' => true + ] + Attributes::MAP, + ] + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Characteristic_two.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Characteristic_two.php new file mode 100644 index 00000000..5bf59bb8 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Characteristic_two.php @@ -0,0 +1,36 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * Characteristic_two + * + * @author Jim Wigginton + */ +abstract class Characteristic_two +{ + const MAP = [ + 'type' => ASN1::TYPE_SEQUENCE, + 'children' => [ + 'm' => ['type' => ASN1::TYPE_INTEGER], // field size 2**m + 'basis' => ['type' => ASN1::TYPE_OBJECT_IDENTIFIER], + 'parameters' => [ + 'type' => ASN1::TYPE_ANY, + 'optional' => true + ] + ] + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CountryName.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CountryName.php new file mode 100644 index 00000000..737d844d --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CountryName.php @@ -0,0 +1,36 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * CountryName + * + * @author Jim Wigginton + */ +abstract class CountryName +{ + const MAP = [ + 'type' => ASN1::TYPE_CHOICE, + // if class isn't present it's assumed to be \phpseclib3\File\ASN1::CLASS_UNIVERSAL or + // (if constant is present) \phpseclib3\File\ASN1::CLASS_CONTEXT_SPECIFIC + 'class' => ASN1::CLASS_APPLICATION, + 'cast' => 1, + 'children' => [ + 'x121-dcc-code' => ['type' => ASN1::TYPE_NUMERIC_STRING], + 'iso-3166-alpha2-code' => ['type' => ASN1::TYPE_PRINTABLE_STRING] + ] + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Curve.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Curve.php new file mode 100644 index 00000000..621f1035 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Curve.php @@ -0,0 +1,36 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * Curve + * + * @author Jim Wigginton + */ +abstract class Curve +{ + const MAP = [ + 'type' => ASN1::TYPE_SEQUENCE, + 'children' => [ + 'a' => FieldElement::MAP, + 'b' => FieldElement::MAP, + 'seed' => [ + 'type' => ASN1::TYPE_BIT_STRING, + 'optional' => true + ] + ] + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DHParameter.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DHParameter.php new file mode 100644 index 00000000..26863dbc --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DHParameter.php @@ -0,0 +1,38 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * DHParameter + * + * @author Jim Wigginton + */ +abstract class DHParameter +{ + const MAP = [ + 'type' => ASN1::TYPE_SEQUENCE, + 'children' => [ + 'prime' => ['type' => ASN1::TYPE_INTEGER], + 'base' => ['type' => ASN1::TYPE_INTEGER], + 'privateValueLength' => [ + 'type' => ASN1::TYPE_INTEGER, + 'optional' => true + ] + ] + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DSAParams.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DSAParams.php new file mode 100644 index 00000000..7af397bb --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DSAParams.php @@ -0,0 +1,33 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * DSAParams + * + * @author Jim Wigginton + */ +abstract class DSAParams +{ + const MAP = [ + 'type' => ASN1::TYPE_SEQUENCE, + 'children' => [ + 'p' => ['type' => ASN1::TYPE_INTEGER], + 'q' => ['type' => ASN1::TYPE_INTEGER], + 'g' => ['type' => ASN1::TYPE_INTEGER] + ] + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DSAPrivateKey.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DSAPrivateKey.php new file mode 100644 index 00000000..d97cd023 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DSAPrivateKey.php @@ -0,0 +1,36 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * DSAPrivateKey + * + * @author Jim Wigginton + */ +abstract class DSAPrivateKey +{ + const MAP = [ + 'type' => ASN1::TYPE_SEQUENCE, + 'children' => [ + 'version' => ['type' => ASN1::TYPE_INTEGER], + 'p' => ['type' => ASN1::TYPE_INTEGER], + 'q' => ['type' => ASN1::TYPE_INTEGER], + 'g' => ['type' => ASN1::TYPE_INTEGER], + 'y' => ['type' => ASN1::TYPE_INTEGER], + 'x' => ['type' => ASN1::TYPE_INTEGER] + ] + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DSAPublicKey.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DSAPublicKey.php new file mode 100644 index 00000000..f795747a --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DSAPublicKey.php @@ -0,0 +1,26 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * DSAPublicKey + * + * @author Jim Wigginton + */ +abstract class DSAPublicKey +{ + const MAP = ['type' => ASN1::TYPE_INTEGER]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DigestInfo.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DigestInfo.php new file mode 100644 index 00000000..b38ff3c4 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DigestInfo.php @@ -0,0 +1,34 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * DigestInfo + * + * from https://tools.ietf.org/html/rfc2898#appendix-A.3 + * + * @author Jim Wigginton + */ +abstract class DigestInfo +{ + const MAP = [ + 'type' => ASN1::TYPE_SEQUENCE, + 'children' => [ + 'digestAlgorithm' => AlgorithmIdentifier::MAP, + 'digest' => ['type' => ASN1::TYPE_OCTET_STRING] + ] + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DirectoryString.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DirectoryString.php new file mode 100644 index 00000000..45218e3e --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DirectoryString.php @@ -0,0 +1,35 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * DirectoryString + * + * @author Jim Wigginton + */ +abstract class DirectoryString +{ + const MAP = [ + 'type' => ASN1::TYPE_CHOICE, + 'children' => [ + 'teletexString' => ['type' => ASN1::TYPE_TELETEX_STRING], + 'printableString' => ['type' => ASN1::TYPE_PRINTABLE_STRING], + 'universalString' => ['type' => ASN1::TYPE_UNIVERSAL_STRING], + 'utf8String' => ['type' => ASN1::TYPE_UTF8_STRING], + 'bmpString' => ['type' => ASN1::TYPE_BMP_STRING] + ] + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DisplayText.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DisplayText.php new file mode 100644 index 00000000..a13e6a64 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DisplayText.php @@ -0,0 +1,34 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * DisplayText + * + * @author Jim Wigginton + */ +abstract class DisplayText +{ + const MAP = [ + 'type' => ASN1::TYPE_CHOICE, + 'children' => [ + 'ia5String' => ['type' => ASN1::TYPE_IA5_STRING], + 'visibleString' => ['type' => ASN1::TYPE_VISIBLE_STRING], + 'bmpString' => ['type' => ASN1::TYPE_BMP_STRING], + 'utf8String' => ['type' => ASN1::TYPE_UTF8_STRING] + ] + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DistributionPoint.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DistributionPoint.php new file mode 100644 index 00000000..4d9af6b5 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DistributionPoint.php @@ -0,0 +1,45 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * DistributionPoint + * + * @author Jim Wigginton + */ +abstract class DistributionPoint +{ + const MAP = [ + 'type' => ASN1::TYPE_SEQUENCE, + 'children' => [ + 'distributionPoint' => [ + 'constant' => 0, + 'optional' => true, + 'explicit' => true + ] + DistributionPointName::MAP, + 'reasons' => [ + 'constant' => 1, + 'optional' => true, + 'implicit' => true + ] + ReasonFlags::MAP, + 'cRLIssuer' => [ + 'constant' => 2, + 'optional' => true, + 'implicit' => true + ] + GeneralNames::MAP + ] + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DistributionPointName.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DistributionPointName.php new file mode 100644 index 00000000..bc0cec8f --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DistributionPointName.php @@ -0,0 +1,40 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * DistributionPointName + * + * @author Jim Wigginton + */ +abstract class DistributionPointName +{ + const MAP = [ + 'type' => ASN1::TYPE_CHOICE, + 'children' => [ + 'fullName' => [ + 'constant' => 0, + 'optional' => true, + 'implicit' => true + ] + GeneralNames::MAP, + 'nameRelativeToCRLIssuer' => [ + 'constant' => 1, + 'optional' => true, + 'implicit' => true + ] + RelativeDistinguishedName::MAP + ] + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DssSigValue.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DssSigValue.php new file mode 100644 index 00000000..2af74088 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DssSigValue.php @@ -0,0 +1,32 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * DssSigValue + * + * @author Jim Wigginton + */ +abstract class DssSigValue +{ + const MAP = [ + 'type' => ASN1::TYPE_SEQUENCE, + 'children' => [ + 'r' => ['type' => ASN1::TYPE_INTEGER], + 's' => ['type' => ASN1::TYPE_INTEGER] + ] + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ECParameters.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ECParameters.php new file mode 100644 index 00000000..f25f6faa --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ECParameters.php @@ -0,0 +1,45 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * ECParameters + * + * ECParameters ::= CHOICE { + * namedCurve OBJECT IDENTIFIER + * -- implicitCurve NULL + * -- specifiedCurve SpecifiedECDomain + * } + * -- implicitCurve and specifiedCurve MUST NOT be used in PKIX. + * -- Details for SpecifiedECDomain can be found in [X9.62]. + * -- Any future additions to this CHOICE should be coordinated + * -- with ANSI X9. + * + * @author Jim Wigginton + */ +abstract class ECParameters +{ + const MAP = [ + 'type' => ASN1::TYPE_CHOICE, + 'children' => [ + 'namedCurve' => ['type' => ASN1::TYPE_OBJECT_IDENTIFIER], + 'implicitCurve' => ['type' => ASN1::TYPE_NULL], + 'specifiedCurve' => SpecifiedECDomain::MAP + ] + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ECPoint.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ECPoint.php new file mode 100644 index 00000000..fb11db83 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ECPoint.php @@ -0,0 +1,26 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * ECPoint + * + * @author Jim Wigginton + */ +abstract class ECPoint +{ + const MAP = ['type' => ASN1::TYPE_OCTET_STRING]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ECPrivateKey.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ECPrivateKey.php new file mode 100644 index 00000000..7454f387 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ECPrivateKey.php @@ -0,0 +1,48 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * ECPrivateKey + * + * @author Jim Wigginton + */ +abstract class ECPrivateKey +{ + const MAP = [ + 'type' => ASN1::TYPE_SEQUENCE, + 'children' => [ + 'version' => [ + 'type' => ASN1::TYPE_INTEGER, + 'mapping' => [1 => 'ecPrivkeyVer1'] + ], + 'privateKey' => ['type' => ASN1::TYPE_OCTET_STRING], + 'parameters' => [ + 'constant' => 0, + 'optional' => true, + 'explicit' => true + ] + ECParameters::MAP, + 'publicKey' => [ + 'type' => ASN1::TYPE_BIT_STRING, + 'constant' => 1, + 'optional' => true, + 'explicit' => true + ] + ] + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/EDIPartyName.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/EDIPartyName.php new file mode 100644 index 00000000..ea7dcf19 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/EDIPartyName.php @@ -0,0 +1,42 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * EDIPartyName + * + * @author Jim Wigginton + */ +abstract class EDIPartyName +{ + const MAP = [ + 'type' => ASN1::TYPE_SEQUENCE, + 'children' => [ + 'nameAssigner' => [ + 'constant' => 0, + 'optional' => true, + 'implicit' => true + ] + DirectoryString::MAP, + // partyName is technically required but \phpseclib3\File\ASN1 doesn't currently support non-optional constants and + // setting it to optional gets the job done in any event. + 'partyName' => [ + 'constant' => 1, + 'optional' => true, + 'implicit' => true + ] + DirectoryString::MAP + ] + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/EcdsaSigValue.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/EcdsaSigValue.php new file mode 100644 index 00000000..8ab9ff1e --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/EcdsaSigValue.php @@ -0,0 +1,32 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * EcdsaSigValue + * + * @author Jim Wigginton + */ +abstract class EcdsaSigValue +{ + const MAP = [ + 'type' => ASN1::TYPE_SEQUENCE, + 'children' => [ + 'r' => ['type' => ASN1::TYPE_INTEGER], + 's' => ['type' => ASN1::TYPE_INTEGER] + ] + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/EncryptedData.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/EncryptedData.php new file mode 100644 index 00000000..8d8739e1 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/EncryptedData.php @@ -0,0 +1,26 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * EncryptedData + * + * @author Jim Wigginton + */ +abstract class EncryptedData +{ + const MAP = ['type' => ASN1::TYPE_OCTET_STRING]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/EncryptedPrivateKeyInfo.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/EncryptedPrivateKeyInfo.php new file mode 100644 index 00000000..2c935676 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/EncryptedPrivateKeyInfo.php @@ -0,0 +1,32 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * EncryptedPrivateKeyInfo + * + * @author Jim Wigginton + */ +abstract class EncryptedPrivateKeyInfo +{ + const MAP = [ + 'type' => ASN1::TYPE_SEQUENCE, + 'children' => [ + 'encryptionAlgorithm' => AlgorithmIdentifier::MAP, + 'encryptedData' => EncryptedData::MAP + ] + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ExtKeyUsageSyntax.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ExtKeyUsageSyntax.php new file mode 100644 index 00000000..f9bc5def --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ExtKeyUsageSyntax.php @@ -0,0 +1,31 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * ExtKeyUsageSyntax + * + * @author Jim Wigginton + */ +abstract class ExtKeyUsageSyntax +{ + const MAP = [ + 'type' => ASN1::TYPE_SEQUENCE, + 'min' => 1, + 'max' => -1, + 'children' => KeyPurposeId::MAP + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Extension.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Extension.php new file mode 100644 index 00000000..e32668fb --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Extension.php @@ -0,0 +1,43 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * Extension + * + * A certificate using system MUST reject the certificate if it encounters + * a critical extension it does not recognize; however, a non-critical + * extension may be ignored if it is not recognized. + * + * http://tools.ietf.org/html/rfc5280#section-4.2 + * + * @author Jim Wigginton + */ +abstract class Extension +{ + const MAP = [ + 'type' => ASN1::TYPE_SEQUENCE, + 'children' => [ + 'extnId' => ['type' => ASN1::TYPE_OBJECT_IDENTIFIER], + 'critical' => [ + 'type' => ASN1::TYPE_BOOLEAN, + 'optional' => true, + 'default' => false + ], + 'extnValue' => ['type' => ASN1::TYPE_OCTET_STRING] + ] + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ExtensionAttribute.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ExtensionAttribute.php new file mode 100644 index 00000000..565b36d3 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ExtensionAttribute.php @@ -0,0 +1,42 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * ExtensionAttribute + * + * @author Jim Wigginton + */ +abstract class ExtensionAttribute +{ + const MAP = [ + 'type' => ASN1::TYPE_SEQUENCE, + 'children' => [ + 'extension-attribute-type' => [ + 'type' => ASN1::TYPE_PRINTABLE_STRING, + 'constant' => 0, + 'optional' => true, + 'implicit' => true + ], + 'extension-attribute-value' => [ + 'type' => ASN1::TYPE_ANY, + 'constant' => 1, + 'optional' => true, + 'explicit' => true + ] + ] + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ExtensionAttributes.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ExtensionAttributes.php new file mode 100644 index 00000000..a2e9bfae --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ExtensionAttributes.php @@ -0,0 +1,31 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * ExtensionAttributes + * + * @author Jim Wigginton + */ +abstract class ExtensionAttributes +{ + const MAP = [ + 'type' => ASN1::TYPE_SET, + 'min' => 1, + 'max' => 256, // ub-extension-attributes + 'children' => ExtensionAttribute::MAP + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Extensions.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Extensions.php new file mode 100644 index 00000000..5015c976 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Extensions.php @@ -0,0 +1,33 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * Extensions + * + * @author Jim Wigginton + */ +abstract class Extensions +{ + const MAP = [ + 'type' => ASN1::TYPE_SEQUENCE, + 'min' => 1, + // technically, it's MAX, but we'll assume anything < 0 is MAX + 'max' => -1, + // if 'children' isn't an array then 'min' and 'max' must be defined + 'children' => Extension::MAP + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/FieldElement.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/FieldElement.php new file mode 100644 index 00000000..31734078 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/FieldElement.php @@ -0,0 +1,26 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * FieldElement + * + * @author Jim Wigginton + */ +abstract class FieldElement +{ + const MAP = ['type' => ASN1::TYPE_OCTET_STRING]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/FieldID.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/FieldID.php new file mode 100644 index 00000000..e32a9c03 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/FieldID.php @@ -0,0 +1,35 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * FieldID + * + * @author Jim Wigginton + */ +abstract class FieldID +{ + const MAP = [ + 'type' => ASN1::TYPE_SEQUENCE, + 'children' => [ + 'fieldType' => ['type' => ASN1::TYPE_OBJECT_IDENTIFIER], + 'parameters' => [ + 'type' => ASN1::TYPE_ANY, + 'optional' => true + ] + ] + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/GeneralName.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/GeneralName.php new file mode 100644 index 00000000..57d86da8 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/GeneralName.php @@ -0,0 +1,80 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * GeneralName + * + * @author Jim Wigginton + */ +abstract class GeneralName +{ + const MAP = [ + 'type' => ASN1::TYPE_CHOICE, + 'children' => [ + 'otherName' => [ + 'constant' => 0, + 'optional' => true, + 'implicit' => true + ] + AnotherName::MAP, + 'rfc822Name' => [ + 'type' => ASN1::TYPE_IA5_STRING, + 'constant' => 1, + 'optional' => true, + 'implicit' => true + ], + 'dNSName' => [ + 'type' => ASN1::TYPE_IA5_STRING, + 'constant' => 2, + 'optional' => true, + 'implicit' => true + ], + 'x400Address' => [ + 'constant' => 3, + 'optional' => true, + 'implicit' => true + ] + ORAddress::MAP, + 'directoryName' => [ + 'constant' => 4, + 'optional' => true, + 'explicit' => true + ] + Name::MAP, + 'ediPartyName' => [ + 'constant' => 5, + 'optional' => true, + 'implicit' => true + ] + EDIPartyName::MAP, + 'uniformResourceIdentifier' => [ + 'type' => ASN1::TYPE_IA5_STRING, + 'constant' => 6, + 'optional' => true, + 'implicit' => true + ], + 'iPAddress' => [ + 'type' => ASN1::TYPE_OCTET_STRING, + 'constant' => 7, + 'optional' => true, + 'implicit' => true + ], + 'registeredID' => [ + 'type' => ASN1::TYPE_OBJECT_IDENTIFIER, + 'constant' => 8, + 'optional' => true, + 'implicit' => true + ] + ] + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/GeneralNames.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/GeneralNames.php new file mode 100644 index 00000000..5d931532 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/GeneralNames.php @@ -0,0 +1,31 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * GeneralNames + * + * @author Jim Wigginton + */ +abstract class GeneralNames +{ + const MAP = [ + 'type' => ASN1::TYPE_SEQUENCE, + 'min' => 1, + 'max' => -1, + 'children' => GeneralName::MAP + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/GeneralSubtree.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/GeneralSubtree.php new file mode 100644 index 00000000..5388db55 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/GeneralSubtree.php @@ -0,0 +1,42 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * GeneralSubtree + * + * @author Jim Wigginton + */ +abstract class GeneralSubtree +{ + const MAP = [ + 'type' => ASN1::TYPE_SEQUENCE, + 'children' => [ + 'base' => GeneralName::MAP, + 'minimum' => [ + 'constant' => 0, + 'optional' => true, + 'implicit' => true, + 'default' => '0' + ] + BaseDistance::MAP, + 'maximum' => [ + 'constant' => 1, + 'optional' => true, + 'implicit' => true, + ] + BaseDistance::MAP + ] + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/GeneralSubtrees.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/GeneralSubtrees.php new file mode 100644 index 00000000..27548cfe --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/GeneralSubtrees.php @@ -0,0 +1,31 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * GeneralSubtrees + * + * @author Jim Wigginton + */ +abstract class GeneralSubtrees +{ + const MAP = [ + 'type' => ASN1::TYPE_SEQUENCE, + 'min' => 1, + 'max' => -1, + 'children' => GeneralSubtree::MAP + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/HashAlgorithm.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/HashAlgorithm.php new file mode 100644 index 00000000..deb13cab --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/HashAlgorithm.php @@ -0,0 +1,24 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +/** + * HashAglorithm + * + * @author Jim Wigginton + */ +abstract class HashAlgorithm +{ + const MAP = AlgorithmIdentifier::MAP; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/HoldInstructionCode.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/HoldInstructionCode.php new file mode 100644 index 00000000..88d6ff3e --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/HoldInstructionCode.php @@ -0,0 +1,26 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * HoldInstructionCode + * + * @author Jim Wigginton + */ +abstract class HoldInstructionCode +{ + const MAP = ['type' => ASN1::TYPE_OBJECT_IDENTIFIER]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/InvalidityDate.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/InvalidityDate.php new file mode 100644 index 00000000..f34b5f72 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/InvalidityDate.php @@ -0,0 +1,26 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * InvalidityDate + * + * @author Jim Wigginton + */ +abstract class InvalidityDate +{ + const MAP = ['type' => ASN1::TYPE_GENERALIZED_TIME]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/IssuerAltName.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/IssuerAltName.php new file mode 100644 index 00000000..e9d03244 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/IssuerAltName.php @@ -0,0 +1,24 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +/** + * IssuerAltName + * + * @author Jim Wigginton + */ +abstract class IssuerAltName +{ + const MAP = GeneralNames::MAP; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/IssuingDistributionPoint.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/IssuingDistributionPoint.php new file mode 100644 index 00000000..415996f5 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/IssuingDistributionPoint.php @@ -0,0 +1,68 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * IssuingDistributionPoint + * + * @author Jim Wigginton + */ +abstract class IssuingDistributionPoint +{ + const MAP = [ + 'type' => ASN1::TYPE_SEQUENCE, + 'children' => [ + 'distributionPoint' => [ + 'constant' => 0, + 'optional' => true, + 'explicit' => true + ] + DistributionPointName::MAP, + 'onlyContainsUserCerts' => [ + 'type' => ASN1::TYPE_BOOLEAN, + 'constant' => 1, + 'optional' => true, + 'default' => false, + 'implicit' => true + ], + 'onlyContainsCACerts' => [ + 'type' => ASN1::TYPE_BOOLEAN, + 'constant' => 2, + 'optional' => true, + 'default' => false, + 'implicit' => true + ], + 'onlySomeReasons' => [ + 'constant' => 3, + 'optional' => true, + 'implicit' => true + ] + ReasonFlags::MAP, + 'indirectCRL' => [ + 'type' => ASN1::TYPE_BOOLEAN, + 'constant' => 4, + 'optional' => true, + 'default' => false, + 'implicit' => true + ], + 'onlyContainsAttributeCerts' => [ + 'type' => ASN1::TYPE_BOOLEAN, + 'constant' => 5, + 'optional' => true, + 'default' => false, + 'implicit' => true + ] + ] + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/KeyIdentifier.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/KeyIdentifier.php new file mode 100644 index 00000000..82a41519 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/KeyIdentifier.php @@ -0,0 +1,26 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * KeyIdentifier + * + * @author Jim Wigginton + */ +abstract class KeyIdentifier +{ + const MAP = ['type' => ASN1::TYPE_OCTET_STRING]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/KeyPurposeId.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/KeyPurposeId.php new file mode 100644 index 00000000..b8509f19 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/KeyPurposeId.php @@ -0,0 +1,26 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * KeyPurposeId + * + * @author Jim Wigginton + */ +abstract class KeyPurposeId +{ + const MAP = ['type' => ASN1::TYPE_OBJECT_IDENTIFIER]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/KeyUsage.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/KeyUsage.php new file mode 100644 index 00000000..827ce033 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/KeyUsage.php @@ -0,0 +1,39 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * KeyUsage + * + * @author Jim Wigginton + */ +abstract class KeyUsage +{ + const MAP = [ + 'type' => ASN1::TYPE_BIT_STRING, + 'mapping' => [ + 'digitalSignature', + 'nonRepudiation', + 'keyEncipherment', + 'dataEncipherment', + 'keyAgreement', + 'keyCertSign', + 'cRLSign', + 'encipherOnly', + 'decipherOnly' + ] + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/MaskGenAlgorithm.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/MaskGenAlgorithm.php new file mode 100644 index 00000000..ea3f998b --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/MaskGenAlgorithm.php @@ -0,0 +1,24 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +/** + * MaskGenAglorithm + * + * @author Jim Wigginton + */ +abstract class MaskGenAlgorithm +{ + const MAP = AlgorithmIdentifier::MAP; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Name.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Name.php new file mode 100644 index 00000000..a6a9009d --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Name.php @@ -0,0 +1,31 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * Name + * + * @author Jim Wigginton + */ +abstract class Name +{ + const MAP = [ + 'type' => ASN1::TYPE_CHOICE, + 'children' => [ + 'rdnSequence' => RDNSequence::MAP + ] + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/NameConstraints.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/NameConstraints.php new file mode 100644 index 00000000..80486f94 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/NameConstraints.php @@ -0,0 +1,40 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * NameConstraints + * + * @author Jim Wigginton + */ +abstract class NameConstraints +{ + const MAP = [ + 'type' => ASN1::TYPE_SEQUENCE, + 'children' => [ + 'permittedSubtrees' => [ + 'constant' => 0, + 'optional' => true, + 'implicit' => true + ] + GeneralSubtrees::MAP, + 'excludedSubtrees' => [ + 'constant' => 1, + 'optional' => true, + 'implicit' => true + ] + GeneralSubtrees::MAP + ] + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/NetworkAddress.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/NetworkAddress.php new file mode 100644 index 00000000..6c68df00 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/NetworkAddress.php @@ -0,0 +1,26 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * NetworkAddress + * + * @author Jim Wigginton + */ +abstract class NetworkAddress +{ + const MAP = ['type' => ASN1::TYPE_NUMERIC_STRING]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/NoticeReference.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/NoticeReference.php new file mode 100644 index 00000000..9eec123a --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/NoticeReference.php @@ -0,0 +1,37 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * NoticeReference + * + * @author Jim Wigginton + */ +abstract class NoticeReference +{ + const MAP = [ + 'type' => ASN1::TYPE_SEQUENCE, + 'children' => [ + 'organization' => DisplayText::MAP, + 'noticeNumbers' => [ + 'type' => ASN1::TYPE_SEQUENCE, + 'min' => 1, + 'max' => 200, + 'children' => ['type' => ASN1::TYPE_INTEGER] + ] + ] + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/NumericUserIdentifier.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/NumericUserIdentifier.php new file mode 100644 index 00000000..635a89dc --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/NumericUserIdentifier.php @@ -0,0 +1,26 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * NumericUserIdentifier + * + * @author Jim Wigginton + */ +abstract class NumericUserIdentifier +{ + const MAP = ['type' => ASN1::TYPE_NUMERIC_STRING]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ORAddress.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ORAddress.php new file mode 100644 index 00000000..b853abe8 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ORAddress.php @@ -0,0 +1,33 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * ORAddress + * + * @author Jim Wigginton + */ +abstract class ORAddress +{ + const MAP = [ + 'type' => ASN1::TYPE_SEQUENCE, + 'children' => [ + 'built-in-standard-attributes' => BuiltInStandardAttributes::MAP, + 'built-in-domain-defined-attributes' => ['optional' => true] + BuiltInDomainDefinedAttributes::MAP, + 'extension-attributes' => ['optional' => true] + ExtensionAttributes::MAP + ] + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/OneAsymmetricKey.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/OneAsymmetricKey.php new file mode 100644 index 00000000..59530248 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/OneAsymmetricKey.php @@ -0,0 +1,48 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * OneAsymmetricKey + * + * @author Jim Wigginton + */ +abstract class OneAsymmetricKey +{ + const MAP = [ + 'type' => ASN1::TYPE_SEQUENCE, + 'children' => [ + 'version' => [ + 'type' => ASN1::TYPE_INTEGER, + 'mapping' => ['v1', 'v2'] + ], + 'privateKeyAlgorithm' => AlgorithmIdentifier::MAP, + 'privateKey' => PrivateKey::MAP, + 'attributes' => [ + 'constant' => 0, + 'optional' => true, + 'implicit' => true + ] + Attributes::MAP, + 'publicKey' => [ + 'constant' => 1, + 'optional' => true, + 'implicit' => true + ] + PublicKey::MAP + ] + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/OrganizationName.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/OrganizationName.php new file mode 100644 index 00000000..b5cc9491 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/OrganizationName.php @@ -0,0 +1,26 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * OrganizationName + * + * @author Jim Wigginton + */ +abstract class OrganizationName +{ + const MAP = ['type' => ASN1::TYPE_PRINTABLE_STRING]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/OrganizationalUnitNames.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/OrganizationalUnitNames.php new file mode 100644 index 00000000..b3e57809 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/OrganizationalUnitNames.php @@ -0,0 +1,31 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * OrganizationalUnitNames + * + * @author Jim Wigginton + */ +abstract class OrganizationalUnitNames +{ + const MAP = [ + 'type' => ASN1::TYPE_SEQUENCE, + 'min' => 1, + 'max' => 4, // ub-organizational-units + 'children' => ['type' => ASN1::TYPE_PRINTABLE_STRING] + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/OtherPrimeInfo.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/OtherPrimeInfo.php new file mode 100644 index 00000000..5d565605 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/OtherPrimeInfo.php @@ -0,0 +1,34 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * OtherPrimeInfo + * + * @author Jim Wigginton + */ +abstract class OtherPrimeInfo +{ + // version must be multi if otherPrimeInfos present + const MAP = [ + 'type' => ASN1::TYPE_SEQUENCE, + 'children' => [ + 'prime' => ['type' => ASN1::TYPE_INTEGER], // ri + 'exponent' => ['type' => ASN1::TYPE_INTEGER], // di + 'coefficient' => ['type' => ASN1::TYPE_INTEGER] // ti + ] + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/OtherPrimeInfos.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/OtherPrimeInfos.php new file mode 100644 index 00000000..9802a808 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/OtherPrimeInfos.php @@ -0,0 +1,32 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * OtherPrimeInfos + * + * @author Jim Wigginton + */ +abstract class OtherPrimeInfos +{ + // version must be multi if otherPrimeInfos present + const MAP = [ + 'type' => ASN1::TYPE_SEQUENCE, + 'min' => 1, + 'max' => -1, + 'children' => OtherPrimeInfo::MAP + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PBEParameter.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PBEParameter.php new file mode 100644 index 00000000..8eb27cf6 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PBEParameter.php @@ -0,0 +1,34 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * PBEParameter + * + * from https://tools.ietf.org/html/rfc2898#appendix-A.3 + * + * @author Jim Wigginton + */ +abstract class PBEParameter +{ + const MAP = [ + 'type' => ASN1::TYPE_SEQUENCE, + 'children' => [ + 'salt' => ['type' => ASN1::TYPE_OCTET_STRING], + 'iterationCount' => ['type' => ASN1::TYPE_INTEGER] + ] + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PBES2params.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PBES2params.php new file mode 100644 index 00000000..bd31699f --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PBES2params.php @@ -0,0 +1,34 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * PBES2params + * + * from https://tools.ietf.org/html/rfc2898#appendix-A.3 + * + * @author Jim Wigginton + */ +abstract class PBES2params +{ + const MAP = [ + 'type' => ASN1::TYPE_SEQUENCE, + 'children' => [ + 'keyDerivationFunc' => AlgorithmIdentifier::MAP, + 'encryptionScheme' => AlgorithmIdentifier::MAP + ] + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PBKDF2params.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PBKDF2params.php new file mode 100644 index 00000000..2dafed9c --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PBKDF2params.php @@ -0,0 +1,41 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * PBKDF2params + * + * from https://tools.ietf.org/html/rfc2898#appendix-A.3 + * + * @author Jim Wigginton + */ +abstract class PBKDF2params +{ + const MAP = [ + 'type' => ASN1::TYPE_SEQUENCE, + 'children' => [ + // technically, this is a CHOICE in RFC2898 but the other "choice" is, currently, more of a placeholder + // in the RFC + 'salt' => ['type' => ASN1::TYPE_OCTET_STRING], + 'iterationCount' => ['type' => ASN1::TYPE_INTEGER], + 'keyLength' => [ + 'type' => ASN1::TYPE_INTEGER, + 'optional' => true + ], + 'prf' => AlgorithmIdentifier::MAP + ['optional' => true] + ] + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PBMAC1params.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PBMAC1params.php new file mode 100644 index 00000000..91319f58 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PBMAC1params.php @@ -0,0 +1,34 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * PBMAC1params + * + * from https://tools.ietf.org/html/rfc2898#appendix-A.3 + * + * @author Jim Wigginton + */ +abstract class PBMAC1params +{ + const MAP = [ + 'type' => ASN1::TYPE_SEQUENCE, + 'children' => [ + 'keyDerivationFunc' => AlgorithmIdentifier::MAP, + 'messageAuthScheme' => AlgorithmIdentifier::MAP + ] + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PKCS9String.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PKCS9String.php new file mode 100644 index 00000000..87d0862f --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PKCS9String.php @@ -0,0 +1,32 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * PKCS9String + * + * @author Jim Wigginton + */ +abstract class PKCS9String +{ + const MAP = [ + 'type' => ASN1::TYPE_CHOICE, + 'children' => [ + 'ia5String' => ['type' => ASN1::TYPE_IA5_STRING], + 'directoryString' => DirectoryString::MAP + ] + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Pentanomial.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Pentanomial.php new file mode 100644 index 00000000..b8c8c02f --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Pentanomial.php @@ -0,0 +1,33 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * Pentanomial + * + * @author Jim Wigginton + */ +abstract class Pentanomial +{ + const MAP = [ + 'type' => ASN1::TYPE_SEQUENCE, + 'children' => [ + 'k1' => ['type' => ASN1::TYPE_INTEGER], // k1 > 0 + 'k2' => ['type' => ASN1::TYPE_INTEGER], // k2 > k1 + 'k3' => ['type' => ASN1::TYPE_INTEGER], // k3 > h2 + ] + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PersonalName.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PersonalName.php new file mode 100644 index 00000000..14e2860e --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PersonalName.php @@ -0,0 +1,54 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * PersonalName + * + * @author Jim Wigginton + */ +abstract class PersonalName +{ + const MAP = [ + 'type' => ASN1::TYPE_SET, + 'children' => [ + 'surname' => [ + 'type' => ASN1::TYPE_PRINTABLE_STRING, + 'constant' => 0, + 'optional' => true, + 'implicit' => true + ], + 'given-name' => [ + 'type' => ASN1::TYPE_PRINTABLE_STRING, + 'constant' => 1, + 'optional' => true, + 'implicit' => true + ], + 'initials' => [ + 'type' => ASN1::TYPE_PRINTABLE_STRING, + 'constant' => 2, + 'optional' => true, + 'implicit' => true + ], + 'generation-qualifier' => [ + 'type' => ASN1::TYPE_PRINTABLE_STRING, + 'constant' => 3, + 'optional' => true, + 'implicit' => true + ] + ] + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PolicyInformation.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PolicyInformation.php new file mode 100644 index 00000000..1625d199 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PolicyInformation.php @@ -0,0 +1,38 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * PolicyInformation + * + * @author Jim Wigginton + */ +abstract class PolicyInformation +{ + const MAP = [ + 'type' => ASN1::TYPE_SEQUENCE, + 'children' => [ + 'policyIdentifier' => CertPolicyId::MAP, + 'policyQualifiers' => [ + 'type' => ASN1::TYPE_SEQUENCE, + 'min' => 0, + 'max' => -1, + 'optional' => true, + 'children' => PolicyQualifierInfo::MAP + ] + ] + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PolicyMappings.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PolicyMappings.php new file mode 100644 index 00000000..d30b8523 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PolicyMappings.php @@ -0,0 +1,37 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * PolicyMappings + * + * @author Jim Wigginton + */ +abstract class PolicyMappings +{ + const MAP = [ + 'type' => ASN1::TYPE_SEQUENCE, + 'min' => 1, + 'max' => -1, + 'children' => [ + 'type' => ASN1::TYPE_SEQUENCE, + 'children' => [ + 'issuerDomainPolicy' => CertPolicyId::MAP, + 'subjectDomainPolicy' => CertPolicyId::MAP + ] + ] + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PolicyQualifierId.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PolicyQualifierId.php new file mode 100644 index 00000000..7b7cd6a7 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PolicyQualifierId.php @@ -0,0 +1,26 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * PolicyQualifierId + * + * @author Jim Wigginton + */ +abstract class PolicyQualifierId +{ + const MAP = ['type' => ASN1::TYPE_OBJECT_IDENTIFIER]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PolicyQualifierInfo.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PolicyQualifierInfo.php new file mode 100644 index 00000000..d227702e --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PolicyQualifierInfo.php @@ -0,0 +1,32 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * PolicyQualifierInfo + * + * @author Jim Wigginton + */ +abstract class PolicyQualifierInfo +{ + const MAP = [ + 'type' => ASN1::TYPE_SEQUENCE, + 'children' => [ + 'policyQualifierId' => PolicyQualifierId::MAP, + 'qualifier' => ['type' => ASN1::TYPE_ANY] + ] + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PostalAddress.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PostalAddress.php new file mode 100644 index 00000000..142b309e --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PostalAddress.php @@ -0,0 +1,32 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * PostalAddress + * + * @author Jim Wigginton + */ +abstract class PostalAddress +{ + const MAP = [ + 'type' => ASN1::TYPE_SEQUENCE, + 'optional' => true, + 'min' => 1, + 'max' => -1, + 'children' => DirectoryString::MAP + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Prime_p.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Prime_p.php new file mode 100644 index 00000000..77430344 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Prime_p.php @@ -0,0 +1,26 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * Prime_p + * + * @author Jim Wigginton + */ +abstract class Prime_p +{ + const MAP = ['type' => ASN1::TYPE_INTEGER]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PrivateDomainName.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PrivateDomainName.php new file mode 100644 index 00000000..195dcaa5 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PrivateDomainName.php @@ -0,0 +1,32 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * PrivateDomainName + * + * @author Jim Wigginton + */ +abstract class PrivateDomainName +{ + const MAP = [ + 'type' => ASN1::TYPE_CHOICE, + 'children' => [ + 'numeric' => ['type' => ASN1::TYPE_NUMERIC_STRING], + 'printable' => ['type' => ASN1::TYPE_PRINTABLE_STRING] + ] + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PrivateKey.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PrivateKey.php new file mode 100644 index 00000000..3c895941 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PrivateKey.php @@ -0,0 +1,26 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * PrivateKey + * + * @author Jim Wigginton + */ +abstract class PrivateKey +{ + const MAP = ['type' => ASN1::TYPE_OCTET_STRING]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PrivateKeyInfo.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PrivateKeyInfo.php new file mode 100644 index 00000000..b440b78d --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PrivateKeyInfo.php @@ -0,0 +1,41 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * PrivateKeyInfo + * + * @author Jim Wigginton + */ +abstract class PrivateKeyInfo +{ + const MAP = [ + 'type' => ASN1::TYPE_SEQUENCE, + 'children' => [ + 'version' => [ + 'type' => ASN1::TYPE_INTEGER, + 'mapping' => ['v1'] + ], + 'privateKeyAlgorithm' => AlgorithmIdentifier::MAP, + 'privateKey' => PrivateKey::MAP, + 'attributes' => [ + 'constant' => 0, + 'optional' => true, + 'implicit' => true + ] + Attributes::MAP + ] + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PrivateKeyUsagePeriod.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PrivateKeyUsagePeriod.php new file mode 100644 index 00000000..5b87036e --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PrivateKeyUsagePeriod.php @@ -0,0 +1,40 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * PrivateKeyUsagePeriod + * + * @author Jim Wigginton + */ +abstract class PrivateKeyUsagePeriod +{ + const MAP = [ + 'type' => ASN1::TYPE_SEQUENCE, + 'children' => [ + 'notBefore' => [ + 'constant' => 0, + 'optional' => true, + 'implicit' => true, + 'type' => ASN1::TYPE_GENERALIZED_TIME], + 'notAfter' => [ + 'constant' => 1, + 'optional' => true, + 'implicit' => true, + 'type' => ASN1::TYPE_GENERALIZED_TIME] + ] + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PublicKey.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PublicKey.php new file mode 100644 index 00000000..48409204 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PublicKey.php @@ -0,0 +1,26 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * PublicKey + * + * @author Jim Wigginton + */ +abstract class PublicKey +{ + const MAP = ['type' => ASN1::TYPE_BIT_STRING]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PublicKeyAndChallenge.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PublicKeyAndChallenge.php new file mode 100644 index 00000000..432581e4 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PublicKeyAndChallenge.php @@ -0,0 +1,32 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * PublicKeyAndChallenge + * + * @author Jim Wigginton + */ +abstract class PublicKeyAndChallenge +{ + const MAP = [ + 'type' => ASN1::TYPE_SEQUENCE, + 'children' => [ + 'spki' => SubjectPublicKeyInfo::MAP, + 'challenge' => ['type' => ASN1::TYPE_IA5_STRING] + ] + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PublicKeyInfo.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PublicKeyInfo.php new file mode 100644 index 00000000..b39a341f --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PublicKeyInfo.php @@ -0,0 +1,35 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * PublicKeyInfo + * + * this format is not formally defined anywhere but is none-the-less the form you + * get when you do "openssl rsa -in private.pem -outform PEM -pubout" + * + * @author Jim Wigginton + */ +abstract class PublicKeyInfo +{ + const MAP = [ + 'type' => ASN1::TYPE_SEQUENCE, + 'children' => [ + 'publicKeyAlgorithm' => AlgorithmIdentifier::MAP, + 'publicKey' => ['type' => ASN1::TYPE_BIT_STRING] + ] + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/RC2CBCParameter.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/RC2CBCParameter.php new file mode 100644 index 00000000..48649abd --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/RC2CBCParameter.php @@ -0,0 +1,37 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * RC2CBCParameter + * + * from https://tools.ietf.org/html/rfc2898#appendix-A.3 + * + * @author Jim Wigginton + */ +abstract class RC2CBCParameter +{ + const MAP = [ + 'type' => ASN1::TYPE_SEQUENCE, + 'children' => [ + 'rc2ParametersVersion' => [ + 'type' => ASN1::TYPE_INTEGER, + 'optional' => true + ], + 'iv' => ['type' => ASN1::TYPE_OCTET_STRING] + ] + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/RDNSequence.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/RDNSequence.php new file mode 100644 index 00000000..04b071c2 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/RDNSequence.php @@ -0,0 +1,38 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * RDNSequence + * + * In practice, RDNs containing multiple name-value pairs (called "multivalued RDNs") are rare, + * but they can be useful at times when either there is no unique attribute in the entry or you + * want to ensure that the entry's DN contains some useful identifying information. + * + * - https://www.opends.org/wiki/page/DefinitionRelativeDistinguishedName + * + * @author Jim Wigginton + */ +abstract class RDNSequence +{ + const MAP = [ + 'type' => ASN1::TYPE_SEQUENCE, + // RDNSequence does not define a min or a max, which means it doesn't have one + 'min' => 0, + 'max' => -1, + 'children' => RelativeDistinguishedName::MAP + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/RSAPrivateKey.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/RSAPrivateKey.php new file mode 100644 index 00000000..8c19c658 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/RSAPrivateKey.php @@ -0,0 +1,44 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * RSAPrivateKey + * + * @author Jim Wigginton + */ +abstract class RSAPrivateKey +{ + // version must be multi if otherPrimeInfos present + const MAP = [ + 'type' => ASN1::TYPE_SEQUENCE, + 'children' => [ + 'version' => [ + 'type' => ASN1::TYPE_INTEGER, + 'mapping' => ['two-prime', 'multi'] + ], + 'modulus' => ['type' => ASN1::TYPE_INTEGER], // n + 'publicExponent' => ['type' => ASN1::TYPE_INTEGER], // e + 'privateExponent' => ['type' => ASN1::TYPE_INTEGER], // d + 'prime1' => ['type' => ASN1::TYPE_INTEGER], // p + 'prime2' => ['type' => ASN1::TYPE_INTEGER], // q + 'exponent1' => ['type' => ASN1::TYPE_INTEGER], // d mod (p-1) + 'exponent2' => ['type' => ASN1::TYPE_INTEGER], // d mod (q-1) + 'coefficient' => ['type' => ASN1::TYPE_INTEGER], // (inverse of q) mod p + 'otherPrimeInfos' => OtherPrimeInfos::MAP + ['optional' => true] + ] + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/RSAPublicKey.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/RSAPublicKey.php new file mode 100644 index 00000000..b14c32c4 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/RSAPublicKey.php @@ -0,0 +1,32 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * RSAPublicKey + * + * @author Jim Wigginton + */ +abstract class RSAPublicKey +{ + const MAP = [ + 'type' => ASN1::TYPE_SEQUENCE, + 'children' => [ + 'modulus' => ['type' => ASN1::TYPE_INTEGER], + 'publicExponent' => ['type' => ASN1::TYPE_INTEGER] + ] + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/RSASSA_PSS_params.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/RSASSA_PSS_params.php new file mode 100644 index 00000000..1a784bf4 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/RSASSA_PSS_params.php @@ -0,0 +1,58 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * RSASSA_PSS_params + * + * @author Jim Wigginton + */ +abstract class RSASSA_PSS_params +{ + const MAP = [ + 'type' => ASN1::TYPE_SEQUENCE, + 'children' => [ + 'hashAlgorithm' => [ + 'constant' => 0, + 'optional' => true, + 'explicit' => true, + //'default' => 'sha1Identifier' + ] + HashAlgorithm::MAP, + 'maskGenAlgorithm' => [ + 'constant' => 1, + 'optional' => true, + 'explicit' => true, + //'default' => 'mgf1SHA1Identifier' + ] + MaskGenAlgorithm::MAP, + 'saltLength' => [ + 'type' => ASN1::TYPE_INTEGER, + 'constant' => 2, + 'optional' => true, + 'explicit' => true, + 'default' => 20 + ], + 'trailerField' => [ + 'type' => ASN1::TYPE_INTEGER, + 'constant' => 3, + 'optional' => true, + 'explicit' => true, + 'default' => 1 + ] + ] + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ReasonFlags.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ReasonFlags.php new file mode 100644 index 00000000..2e62fcdb --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ReasonFlags.php @@ -0,0 +1,39 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * ReasonFlags + * + * @author Jim Wigginton + */ +abstract class ReasonFlags +{ + const MAP = [ + 'type' => ASN1::TYPE_BIT_STRING, + 'mapping' => [ + 'unused', + 'keyCompromise', + 'cACompromise', + 'affiliationChanged', + 'superseded', + 'cessationOfOperation', + 'certificateHold', + 'privilegeWithdrawn', + 'aACompromise' + ] + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/RelativeDistinguishedName.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/RelativeDistinguishedName.php new file mode 100644 index 00000000..a0421f73 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/RelativeDistinguishedName.php @@ -0,0 +1,37 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * RelativeDistinguishedName + * + * In practice, RDNs containing multiple name-value pairs (called "multivalued RDNs") are rare, + * but they can be useful at times when either there is no unique attribute in the entry or you + * want to ensure that the entry's DN contains some useful identifying information. + * + * - https://www.opends.org/wiki/page/DefinitionRelativeDistinguishedName + * + * @author Jim Wigginton + */ +abstract class RelativeDistinguishedName +{ + const MAP = [ + 'type' => ASN1::TYPE_SET, + 'min' => 1, + 'max' => -1, + 'children' => AttributeTypeAndValue::MAP + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/RevokedCertificate.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/RevokedCertificate.php new file mode 100644 index 00000000..ff759eb7 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/RevokedCertificate.php @@ -0,0 +1,35 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * RevokedCertificate + * + * @author Jim Wigginton + */ +abstract class RevokedCertificate +{ + const MAP = [ + 'type' => ASN1::TYPE_SEQUENCE, + 'children' => [ + 'userCertificate' => CertificateSerialNumber::MAP, + 'revocationDate' => Time::MAP, + 'crlEntryExtensions' => [ + 'optional' => true + ] + Extensions::MAP + ] + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/SignedPublicKeyAndChallenge.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/SignedPublicKeyAndChallenge.php new file mode 100644 index 00000000..0f482a26 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/SignedPublicKeyAndChallenge.php @@ -0,0 +1,33 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * SignedPublicKeyAndChallenge + * + * @author Jim Wigginton + */ +abstract class SignedPublicKeyAndChallenge +{ + const MAP = [ + 'type' => ASN1::TYPE_SEQUENCE, + 'children' => [ + 'publicKeyAndChallenge' => PublicKeyAndChallenge::MAP, + 'signatureAlgorithm' => AlgorithmIdentifier::MAP, + 'signature' => ['type' => ASN1::TYPE_BIT_STRING] + ] + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/SpecifiedECDomain.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/SpecifiedECDomain.php new file mode 100644 index 00000000..7408a563 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/SpecifiedECDomain.php @@ -0,0 +1,45 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * SpecifiedECDomain + * + * @author Jim Wigginton + */ +abstract class SpecifiedECDomain +{ + const MAP = [ + 'type' => ASN1::TYPE_SEQUENCE, + 'children' => [ + 'version' => [ + 'type' => ASN1::TYPE_INTEGER, + 'mapping' => [1 => 'ecdpVer1', 'ecdpVer2', 'ecdpVer3'] + ], + 'fieldID' => FieldID::MAP, + 'curve' => Curve::MAP, + 'base' => ECPoint::MAP, + 'order' => ['type' => ASN1::TYPE_INTEGER], + 'cofactor' => [ + 'type' => ASN1::TYPE_INTEGER, + 'optional' => true + ], + 'hash' => ['optional' => true] + HashAlgorithm::MAP + ] + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/SubjectAltName.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/SubjectAltName.php new file mode 100644 index 00000000..39138a94 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/SubjectAltName.php @@ -0,0 +1,24 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +/** + * SubjectAltName + * + * @author Jim Wigginton + */ +abstract class SubjectAltName +{ + const MAP = GeneralNames::MAP; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/SubjectDirectoryAttributes.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/SubjectDirectoryAttributes.php new file mode 100644 index 00000000..f2e206f6 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/SubjectDirectoryAttributes.php @@ -0,0 +1,31 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * SubjectDirectoryAttributes + * + * @author Jim Wigginton + */ +abstract class SubjectDirectoryAttributes +{ + const MAP = [ + 'type' => ASN1::TYPE_SEQUENCE, + 'min' => 1, + 'max' => -1, + 'children' => Attribute::MAP + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/SubjectInfoAccessSyntax.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/SubjectInfoAccessSyntax.php new file mode 100644 index 00000000..1ff241f7 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/SubjectInfoAccessSyntax.php @@ -0,0 +1,31 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * SubjectInfoAccessSyntax + * + * @author Jim Wigginton + */ +abstract class SubjectInfoAccessSyntax +{ + const MAP = [ + 'type' => ASN1::TYPE_SEQUENCE, + 'min' => 1, + 'max' => -1, + 'children' => AccessDescription::MAP + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/SubjectPublicKeyInfo.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/SubjectPublicKeyInfo.php new file mode 100644 index 00000000..0d53d540 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/SubjectPublicKeyInfo.php @@ -0,0 +1,32 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * SubjectPublicKeyInfo + * + * @author Jim Wigginton + */ +abstract class SubjectPublicKeyInfo +{ + const MAP = [ + 'type' => ASN1::TYPE_SEQUENCE, + 'children' => [ + 'algorithm' => AlgorithmIdentifier::MAP, + 'subjectPublicKey' => ['type' => ASN1::TYPE_BIT_STRING] + ] + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/TBSCertList.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/TBSCertList.php new file mode 100644 index 00000000..49b3cfc5 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/TBSCertList.php @@ -0,0 +1,54 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * TBSCertList + * + * @author Jim Wigginton + */ +abstract class TBSCertList +{ + const MAP = [ + 'type' => ASN1::TYPE_SEQUENCE, + 'children' => [ + 'version' => [ + 'type' => ASN1::TYPE_INTEGER, + 'mapping' => ['v1', 'v2', 'v3'], + 'optional' => true, + 'default' => 'v2' + ], + 'signature' => AlgorithmIdentifier::MAP, + 'issuer' => Name::MAP, + 'thisUpdate' => Time::MAP, + 'nextUpdate' => [ + 'optional' => true + ] + Time::MAP, + 'revokedCertificates' => [ + 'type' => ASN1::TYPE_SEQUENCE, + 'optional' => true, + 'min' => 0, + 'max' => -1, + 'children' => RevokedCertificate::MAP + ], + 'crlExtensions' => [ + 'constant' => 0, + 'optional' => true, + 'explicit' => true + ] + Extensions::MAP + ] + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/TBSCertificate.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/TBSCertificate.php new file mode 100644 index 00000000..007360c9 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/TBSCertificate.php @@ -0,0 +1,65 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * TBSCertificate + * + * @author Jim Wigginton + */ +abstract class TBSCertificate +{ + // assert($TBSCertificate['children']['signature'] == $Certificate['children']['signatureAlgorithm']) + const MAP = [ + 'type' => ASN1::TYPE_SEQUENCE, + 'children' => [ + // technically, default implies optional, but we'll define it as being optional, none-the-less, just to + // reenforce that fact + 'version' => [ + 'type' => ASN1::TYPE_INTEGER, + 'constant' => 0, + 'optional' => true, + 'explicit' => true, + 'mapping' => ['v1', 'v2', 'v3'], + 'default' => 'v1' + ], + 'serialNumber' => CertificateSerialNumber::MAP, + 'signature' => AlgorithmIdentifier::MAP, + 'issuer' => Name::MAP, + 'validity' => Validity::MAP, + 'subject' => Name::MAP, + 'subjectPublicKeyInfo' => SubjectPublicKeyInfo::MAP, + // implicit means that the T in the TLV structure is to be rewritten, regardless of the type + 'issuerUniqueID' => [ + 'constant' => 1, + 'optional' => true, + 'implicit' => true + ] + UniqueIdentifier::MAP, + 'subjectUniqueID' => [ + 'constant' => 2, + 'optional' => true, + 'implicit' => true + ] + UniqueIdentifier::MAP, + // doesn't use the EXPLICIT keyword but if + // it's not IMPLICIT, it's EXPLICIT + 'extensions' => [ + 'constant' => 3, + 'optional' => true, + 'explicit' => true + ] + Extensions::MAP + ] + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/TerminalIdentifier.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/TerminalIdentifier.php new file mode 100644 index 00000000..7f6d9d2e --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/TerminalIdentifier.php @@ -0,0 +1,26 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * TerminalIdentifier + * + * @author Jim Wigginton + */ +abstract class TerminalIdentifier +{ + const MAP = ['type' => ASN1::TYPE_PRINTABLE_STRING]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Time.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Time.php new file mode 100644 index 00000000..744ee704 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Time.php @@ -0,0 +1,32 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * Time + * + * @author Jim Wigginton + */ +abstract class Time +{ + const MAP = [ + 'type' => ASN1::TYPE_CHOICE, + 'children' => [ + 'utcTime' => ['type' => ASN1::TYPE_UTC_TIME], + 'generalTime' => ['type' => ASN1::TYPE_GENERALIZED_TIME] + ] + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Trinomial.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Trinomial.php new file mode 100644 index 00000000..33baa91e --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Trinomial.php @@ -0,0 +1,26 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * Trinomial + * + * @author Jim Wigginton + */ +abstract class Trinomial +{ + const MAP = ['type' => ASN1::TYPE_INTEGER]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/UniqueIdentifier.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/UniqueIdentifier.php new file mode 100644 index 00000000..f4c954bb --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/UniqueIdentifier.php @@ -0,0 +1,26 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * UniqueIdentifier + * + * @author Jim Wigginton + */ +abstract class UniqueIdentifier +{ + const MAP = ['type' => ASN1::TYPE_BIT_STRING]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/UserNotice.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/UserNotice.php new file mode 100644 index 00000000..98d527b7 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/UserNotice.php @@ -0,0 +1,38 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * UserNotice + * + * @author Jim Wigginton + */ +abstract class UserNotice +{ + const MAP = [ + 'type' => ASN1::TYPE_SEQUENCE, + 'children' => [ + 'noticeRef' => [ + 'optional' => true, + 'implicit' => true + ] + NoticeReference::MAP, + 'explicitText' => [ + 'optional' => true, + 'implicit' => true + ] + DisplayText::MAP + ] + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Validity.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Validity.php new file mode 100644 index 00000000..8ef64cf5 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Validity.php @@ -0,0 +1,32 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * Validity + * + * @author Jim Wigginton + */ +abstract class Validity +{ + const MAP = [ + 'type' => ASN1::TYPE_SEQUENCE, + 'children' => [ + 'notBefore' => Time::MAP, + 'notAfter' => Time::MAP + ] + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/netscape_ca_policy_url.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/netscape_ca_policy_url.php new file mode 100644 index 00000000..2ab15728 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/netscape_ca_policy_url.php @@ -0,0 +1,26 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * netscape_ca_policy_url + * + * @author Jim Wigginton + */ +abstract class netscape_ca_policy_url +{ + const MAP = ['type' => ASN1::TYPE_IA5_STRING]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/netscape_cert_type.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/netscape_cert_type.php new file mode 100644 index 00000000..49e8da4b --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/netscape_cert_type.php @@ -0,0 +1,40 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * netscape_cert_type + * + * mapping is from + * + * @author Jim Wigginton + */ +abstract class netscape_cert_type +{ + const MAP = [ + 'type' => ASN1::TYPE_BIT_STRING, + 'mapping' => [ + 'SSLClient', + 'SSLServer', + 'Email', + 'ObjectSigning', + 'Reserved', + 'SSLCA', + 'EmailCA', + 'ObjectSigningCA' + ] + ]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/netscape_comment.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/netscape_comment.php new file mode 100644 index 00000000..d3ff4ddf --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/netscape_comment.php @@ -0,0 +1,26 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\ASN1\Maps; + +use phpseclib3\File\ASN1; + +/** + * netscape_comment + * + * @author Jim Wigginton + */ +abstract class netscape_comment +{ + const MAP = ['type' => ASN1::TYPE_IA5_STRING]; +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/X509.php b/vendor/phpseclib/phpseclib/phpseclib/File/X509.php index 7b1b1cfa..695e5a80 100644 --- a/vendor/phpseclib/phpseclib/phpseclib/File/X509.php +++ b/vendor/phpseclib/phpseclib/phpseclib/File/X509.php @@ -16,30 +16,33 @@ * be encoded. It can be encoded explicitly or left out all together. This would effect the signature value and thus may invalidate the * the certificate all together unless the certificate is re-signed. * - * @category File - * @package X509 * @author Jim Wigginton * @copyright 2012 Jim Wigginton * @license http://www.opensource.org/licenses/mit-license.html MIT License * @link http://phpseclib.sourceforge.net */ -namespace phpseclib\File; - -use phpseclib\Crypt\Hash; -use phpseclib\Crypt\Random; -use phpseclib\Crypt\RSA; -use phpseclib\File\ASN1\Element; -use phpseclib\Math\BigInteger; -use DateTime; -use DateTimeZone; +namespace phpseclib3\File; + +use phpseclib3\Common\Functions\Strings; +use phpseclib3\Crypt\Common\PrivateKey; +use phpseclib3\Crypt\Common\PublicKey; +use phpseclib3\Crypt\DSA; +use phpseclib3\Crypt\EC; +use phpseclib3\Crypt\Hash; +use phpseclib3\Crypt\PublicKeyLoader; +use phpseclib3\Crypt\Random; +use phpseclib3\Crypt\RSA; +use phpseclib3\Crypt\RSA\Formats\Keys\PSS; +use phpseclib3\Exception\UnsupportedAlgorithmException; +use phpseclib3\File\ASN1\Element; +use phpseclib3\File\ASN1\Maps; +use phpseclib3\Math\BigInteger; /** * Pure-PHP X.509 Parser * - * @package X509 * @author Jim Wigginton - * @access public */ class X509 { @@ -48,59 +51,71 @@ class X509 * * Not really used anymore but retained all the same to suppress E_NOTICEs from old installs * - * @access public */ const VALIDATE_SIGNATURE_BY_CA = 1; - /**#@+ - * @access public - * @see \phpseclib\File\X509::getDN() - */ /** * Return internal array representation + * + * @see \phpseclib3\File\X509::getDN() */ const DN_ARRAY = 0; /** * Return string + * + * @see \phpseclib3\File\X509::getDN() */ const DN_STRING = 1; /** * Return ASN.1 name string + * + * @see \phpseclib3\File\X509::getDN() */ const DN_ASN1 = 2; /** * Return OpenSSL compatible array + * + * @see \phpseclib3\File\X509::getDN() */ const DN_OPENSSL = 3; /** * Return canonical ASN.1 RDNs string + * + * @see \phpseclib3\File\X509::getDN() */ const DN_CANON = 4; /** * Return name hash for file indexing + * + * @see \phpseclib3\File\X509::getDN() */ const DN_HASH = 5; - /**#@-*/ - - /**#@+ - * @access public - * @see \phpseclib\File\X509::saveX509() - * @see \phpseclib\File\X509::saveCSR() - * @see \phpseclib\File\X509::saveCRL() - */ + /** * Save as PEM * * ie. a base64-encoded PEM with a header and a footer + * + * @see \phpseclib3\File\X509::saveX509() + * @see \phpseclib3\File\X509::saveCSR() + * @see \phpseclib3\File\X509::saveCRL() */ const FORMAT_PEM = 0; /** * Save as DER + * + * @see \phpseclib3\File\X509::saveX509() + * @see \phpseclib3\File\X509::saveCSR() + * @see \phpseclib3\File\X509::saveCRL() */ const FORMAT_DER = 1; /** * Save as a SPKAC * + * @see \phpseclib3\File\X509::saveX509() + * @see \phpseclib3\File\X509::saveCSR() + * @see \phpseclib3\File\X509::saveCRL() + * * Only works on CSRs. Not currently supported. */ const FORMAT_SPKAC = 2; @@ -108,9 +123,12 @@ class X509 * Auto-detect the format * * Used only by the load*() functions + * + * @see \phpseclib3\File\X509::saveX509() + * @see \phpseclib3\File\X509::saveCSR() + * @see \phpseclib3\File\X509::saveCRL() */ const FORMAT_AUTO_DETECT = 3; - /**#@-*/ /** * Attribute value disposition. @@ -120,163 +138,71 @@ class X509 const ATTR_APPEND = -2; // Add a value. const ATTR_REPLACE = -3; // Clear first, then add a value. - /** - * ASN.1 syntax for X.509 certificates - * - * @var array - * @access private - */ - var $Certificate; - - /**#@+ - * ASN.1 syntax for various extensions - * - * @access private - */ - var $DirectoryString; - var $PKCS9String; - var $AttributeValue; - var $Extensions; - var $KeyUsage; - var $ExtKeyUsageSyntax; - var $BasicConstraints; - var $KeyIdentifier; - var $CRLDistributionPoints; - var $AuthorityKeyIdentifier; - var $CertificatePolicies; - var $AuthorityInfoAccessSyntax; - var $SubjectAltName; - var $SubjectDirectoryAttributes; - var $PrivateKeyUsagePeriod; - var $IssuerAltName; - var $PolicyMappings; - var $NameConstraints; - - var $CPSuri; - var $UserNotice; - - var $netscape_cert_type; - var $netscape_comment; - var $netscape_ca_policy_url; - - var $Name; - var $RelativeDistinguishedName; - var $CRLNumber; - var $CRLReason; - var $IssuingDistributionPoint; - var $InvalidityDate; - var $CertificateIssuer; - var $HoldInstructionCode; - var $SignedPublicKeyAndChallenge; - /**#@-*/ - - /**#@+ - * ASN.1 syntax for various DN attributes - * - * @access private - */ - var $PostalAddress; - /**#@-*/ - - /** - * ASN.1 syntax for Certificate Signing Requests (RFC2986) - * - * @var array - * @access private - */ - var $CertificationRequest; - - /** - * ASN.1 syntax for Certificate Revocation Lists (RFC5280) - * - * @var array - * @access private - */ - var $CertificateList; - /** * Distinguished Name * * @var array - * @access private */ - var $dn; + private $dn; /** * Public key * - * @var string - * @access private + * @var string|PublicKey */ - var $publicKey; + private $publicKey; /** * Private key * - * @var string - * @access private - */ - var $privateKey; - - /** - * Object identifiers for X.509 certificates - * - * @var array - * @access private - * @link http://en.wikipedia.org/wiki/Object_identifier + * @var string|PrivateKey */ - var $oids; + private $privateKey; /** * The certificate authorities * * @var array - * @access private */ - var $CAs; + private $CAs; /** * The currently loaded certificate * * @var array - * @access private */ - var $currentCert; + private $currentCert; /** * The signature subject * - * There's no guarantee \phpseclib\File\X509 is going to re-encode an X.509 cert in the same way it was originally + * There's no guarantee \phpseclib3\File\X509 is going to re-encode an X.509 cert in the same way it was originally * encoded so we take save the portion of the original cert that the signature would have made for. * * @var string - * @access private */ - var $signatureSubject; + private $signatureSubject; /** * Certificate Start Date * * @var string - * @access private */ - var $startDate; + private $startDate; /** * Certificate End Date * - * @var string - * @access private + * @var string|Element */ - var $endDate; + private $endDate; /** * Serial Number * * @var string - * @access private */ - var $serialNumber; + private $serialNumber; /** * Key Identifier @@ -285,1171 +211,209 @@ class X509 * {@link http://tools.ietf.org/html/rfc5280#section-4.2.1.2 RFC5280#section-4.2.1.2}. * * @var string - * @access private */ - var $currentKeyIdentifier; + private $currentKeyIdentifier; /** * CA Flag * * @var bool - * @access private */ - var $caFlag = false; + private $caFlag = false; /** * SPKAC Challenge * * @var string - * @access private */ - var $challenge; + private $challenge; + + /** + * @var array + */ + private $extensionValues = []; + + /** + * OIDs loaded + * + * @var bool + */ + private static $oidsLoaded = false; /** * Recursion Limit * * @var int - * @access private */ - static $recur_limit = 5; + private static $recur_limit = 5; /** * URL fetch flag * * @var bool - * @access private */ - static $disable_url_fetch = false; + private static $disable_url_fetch = false; + + /** + * @var array + */ + private static $extensions = []; + + /** + * @var ?array + */ + private $ipAddresses = null; + + /** + * @var ?array + */ + private $domains = null; /** * Default Constructor. * - * @return \phpseclib\File\X509 - * @access public + * @return \phpseclib3\File\X509 */ - function __construct() + public function __construct() { // Explicitly Tagged Module, 1988 Syntax // http://tools.ietf.org/html/rfc5280#appendix-A.1 - $this->DirectoryString = array( - 'type' => ASN1::TYPE_CHOICE, - 'children' => array( - 'teletexString' => array('type' => ASN1::TYPE_TELETEX_STRING), - 'printableString' => array('type' => ASN1::TYPE_PRINTABLE_STRING), - 'universalString' => array('type' => ASN1::TYPE_UNIVERSAL_STRING), - 'utf8String' => array('type' => ASN1::TYPE_UTF8_STRING), - 'bmpString' => array('type' => ASN1::TYPE_BMP_STRING) - ) - ); - - $this->PKCS9String = array( - 'type' => ASN1::TYPE_CHOICE, - 'children' => array( - 'ia5String' => array('type' => ASN1::TYPE_IA5_STRING), - 'directoryString' => $this->DirectoryString - ) - ); - - $this->AttributeValue = array('type' => ASN1::TYPE_ANY); - - $AttributeType = array('type' => ASN1::TYPE_OBJECT_IDENTIFIER); - - $AttributeTypeAndValue = array( - 'type' => ASN1::TYPE_SEQUENCE, - 'children' => array( - 'type' => $AttributeType, - 'value'=> $this->AttributeValue - ) - ); - - /* - In practice, RDNs containing multiple name-value pairs (called "multivalued RDNs") are rare, - but they can be useful at times when either there is no unique attribute in the entry or you - want to ensure that the entry's DN contains some useful identifying information. - - - https://www.opends.org/wiki/page/DefinitionRelativeDistinguishedName - */ - $this->RelativeDistinguishedName = array( - 'type' => ASN1::TYPE_SET, - 'min' => 1, - 'max' => -1, - 'children' => $AttributeTypeAndValue - ); - - // http://tools.ietf.org/html/rfc5280#section-4.1.2.4 - $RDNSequence = array( - 'type' => ASN1::TYPE_SEQUENCE, - // RDNSequence does not define a min or a max, which means it doesn't have one - 'min' => 0, - 'max' => -1, - 'children' => $this->RelativeDistinguishedName - ); - - $this->Name = array( - 'type' => ASN1::TYPE_CHOICE, - 'children' => array( - 'rdnSequence' => $RDNSequence - ) - ); - - // http://tools.ietf.org/html/rfc5280#section-4.1.1.2 - $AlgorithmIdentifier = array( - 'type' => ASN1::TYPE_SEQUENCE, - 'children' => array( - 'algorithm' => array('type' => ASN1::TYPE_OBJECT_IDENTIFIER), - 'parameters' => array( - 'type' => ASN1::TYPE_ANY, - 'optional' => true - ) - ) - ); - - /* - A certificate using system MUST reject the certificate if it encounters - a critical extension it does not recognize; however, a non-critical - extension may be ignored if it is not recognized. - - http://tools.ietf.org/html/rfc5280#section-4.2 - */ - $Extension = array( - 'type' => ASN1::TYPE_SEQUENCE, - 'children' => array( - 'extnId' => array('type' => ASN1::TYPE_OBJECT_IDENTIFIER), - 'critical' => array( - 'type' => ASN1::TYPE_BOOLEAN, - 'optional' => true, - 'default' => false - ), - 'extnValue' => array('type' => ASN1::TYPE_OCTET_STRING) - ) - ); - - $this->Extensions = array( - 'type' => ASN1::TYPE_SEQUENCE, - 'min' => 1, - // technically, it's MAX, but we'll assume anything < 0 is MAX - 'max' => -1, - // if 'children' isn't an array then 'min' and 'max' must be defined - 'children' => $Extension - ); - - $SubjectPublicKeyInfo = array( - 'type' => ASN1::TYPE_SEQUENCE, - 'children' => array( - 'algorithm' => $AlgorithmIdentifier, - 'subjectPublicKey' => array('type' => ASN1::TYPE_BIT_STRING) - ) - ); - - $UniqueIdentifier = array('type' => ASN1::TYPE_BIT_STRING); - - $Time = array( - 'type' => ASN1::TYPE_CHOICE, - 'children' => array( - 'utcTime' => array('type' => ASN1::TYPE_UTC_TIME), - 'generalTime' => array('type' => ASN1::TYPE_GENERALIZED_TIME) - ) - ); - - // http://tools.ietf.org/html/rfc5280#section-4.1.2.5 - $Validity = array( - 'type' => ASN1::TYPE_SEQUENCE, - 'children' => array( - 'notBefore' => $Time, - 'notAfter' => $Time - ) - ); - - $CertificateSerialNumber = array('type' => ASN1::TYPE_INTEGER); - - $Version = array( - 'type' => ASN1::TYPE_INTEGER, - 'mapping' => array('v1', 'v2', 'v3') - ); - - // assert($TBSCertificate['children']['signature'] == $Certificate['children']['signatureAlgorithm']) - $TBSCertificate = array( - 'type' => ASN1::TYPE_SEQUENCE, - 'children' => array( - // technically, default implies optional, but we'll define it as being optional, none-the-less, just to - // reenforce that fact - 'version' => array( - 'constant' => 0, - 'optional' => true, - 'explicit' => true, - 'default' => 'v1' - ) + $Version, - 'serialNumber' => $CertificateSerialNumber, - 'signature' => $AlgorithmIdentifier, - 'issuer' => $this->Name, - 'validity' => $Validity, - 'subject' => $this->Name, - 'subjectPublicKeyInfo' => $SubjectPublicKeyInfo, - // implicit means that the T in the TLV structure is to be rewritten, regardless of the type - 'issuerUniqueID' => array( - 'constant' => 1, - 'optional' => true, - 'implicit' => true - ) + $UniqueIdentifier, - 'subjectUniqueID' => array( - 'constant' => 2, - 'optional' => true, - 'implicit' => true - ) + $UniqueIdentifier, - // doesn't use the EXPLICIT keyword but if - // it's not IMPLICIT, it's EXPLICIT - 'extensions' => array( - 'constant' => 3, - 'optional' => true, - 'explicit' => true - ) + $this->Extensions - ) - ); - - $this->Certificate = array( - 'type' => ASN1::TYPE_SEQUENCE, - 'children' => array( - 'tbsCertificate' => $TBSCertificate, - 'signatureAlgorithm' => $AlgorithmIdentifier, - 'signature' => array('type' => ASN1::TYPE_BIT_STRING) - ) - ); - - $this->KeyUsage = array( - 'type' => ASN1::TYPE_BIT_STRING, - 'mapping' => array( - 'digitalSignature', - 'nonRepudiation', - 'keyEncipherment', - 'dataEncipherment', - 'keyAgreement', - 'keyCertSign', - 'cRLSign', - 'encipherOnly', - 'decipherOnly' - ) - ); - - $this->BasicConstraints = array( - 'type' => ASN1::TYPE_SEQUENCE, - 'children' => array( - 'cA' => array( - 'type' => ASN1::TYPE_BOOLEAN, - 'optional' => true, - 'default' => false - ), - 'pathLenConstraint' => array( - 'type' => ASN1::TYPE_INTEGER, - 'optional' => true - ) - ) - ); - - $this->KeyIdentifier = array('type' => ASN1::TYPE_OCTET_STRING); - - $OrganizationalUnitNames = array( - 'type' => ASN1::TYPE_SEQUENCE, - 'min' => 1, - 'max' => 4, // ub-organizational-units - 'children' => array('type' => ASN1::TYPE_PRINTABLE_STRING) - ); - - $PersonalName = array( - 'type' => ASN1::TYPE_SET, - 'children' => array( - 'surname' => array( - 'type' => ASN1::TYPE_PRINTABLE_STRING, - 'constant' => 0, - 'optional' => true, - 'implicit' => true - ), - 'given-name' => array( - 'type' => ASN1::TYPE_PRINTABLE_STRING, - 'constant' => 1, - 'optional' => true, - 'implicit' => true - ), - 'initials' => array( - 'type' => ASN1::TYPE_PRINTABLE_STRING, - 'constant' => 2, - 'optional' => true, - 'implicit' => true - ), - 'generation-qualifier' => array( - 'type' => ASN1::TYPE_PRINTABLE_STRING, - 'constant' => 3, - 'optional' => true, - 'implicit' => true - ) - ) - ); - - $NumericUserIdentifier = array('type' => ASN1::TYPE_NUMERIC_STRING); - - $OrganizationName = array('type' => ASN1::TYPE_PRINTABLE_STRING); - - $PrivateDomainName = array( - 'type' => ASN1::TYPE_CHOICE, - 'children' => array( - 'numeric' => array('type' => ASN1::TYPE_NUMERIC_STRING), - 'printable' => array('type' => ASN1::TYPE_PRINTABLE_STRING) - ) - ); - - $TerminalIdentifier = array('type' => ASN1::TYPE_PRINTABLE_STRING); - - $NetworkAddress = array('type' => ASN1::TYPE_NUMERIC_STRING); - - $AdministrationDomainName = array( - 'type' => ASN1::TYPE_CHOICE, - // if class isn't present it's assumed to be \phpseclib\File\ASN1::CLASS_UNIVERSAL or - // (if constant is present) \phpseclib\File\ASN1::CLASS_CONTEXT_SPECIFIC - 'class' => ASN1::CLASS_APPLICATION, - 'cast' => 2, - 'children' => array( - 'numeric' => array('type' => ASN1::TYPE_NUMERIC_STRING), - 'printable' => array('type' => ASN1::TYPE_PRINTABLE_STRING) - ) - ); - - $CountryName = array( - 'type' => ASN1::TYPE_CHOICE, - // if class isn't present it's assumed to be \phpseclib\File\ASN1::CLASS_UNIVERSAL or - // (if constant is present) \phpseclib\File\ASN1::CLASS_CONTEXT_SPECIFIC - 'class' => ASN1::CLASS_APPLICATION, - 'cast' => 1, - 'children' => array( - 'x121-dcc-code' => array('type' => ASN1::TYPE_NUMERIC_STRING), - 'iso-3166-alpha2-code' => array('type' => ASN1::TYPE_PRINTABLE_STRING) - ) - ); - - $AnotherName = array( - 'type' => ASN1::TYPE_SEQUENCE, - 'children' => array( - 'type-id' => array('type' => ASN1::TYPE_OBJECT_IDENTIFIER), - 'value' => array( - 'type' => ASN1::TYPE_ANY, - 'constant' => 0, - 'optional' => true, - 'explicit' => true - ) - ) - ); - - $ExtensionAttribute = array( - 'type' => ASN1::TYPE_SEQUENCE, - 'children' => array( - 'extension-attribute-type' => array( - 'type' => ASN1::TYPE_PRINTABLE_STRING, - 'constant' => 0, - 'optional' => true, - 'implicit' => true - ), - 'extension-attribute-value' => array( - 'type' => ASN1::TYPE_ANY, - 'constant' => 1, - 'optional' => true, - 'explicit' => true - ) - ) - ); - - $ExtensionAttributes = array( - 'type' => ASN1::TYPE_SET, - 'min' => 1, - 'max' => 256, // ub-extension-attributes - 'children' => $ExtensionAttribute - ); - - $BuiltInDomainDefinedAttribute = array( - 'type' => ASN1::TYPE_SEQUENCE, - 'children' => array( - 'type' => array('type' => ASN1::TYPE_PRINTABLE_STRING), - 'value' => array('type' => ASN1::TYPE_PRINTABLE_STRING) - ) - ); - - $BuiltInDomainDefinedAttributes = array( - 'type' => ASN1::TYPE_SEQUENCE, - 'min' => 1, - 'max' => 4, // ub-domain-defined-attributes - 'children' => $BuiltInDomainDefinedAttribute - ); - - $BuiltInStandardAttributes = array( - 'type' => ASN1::TYPE_SEQUENCE, - 'children' => array( - 'country-name' => array('optional' => true) + $CountryName, - 'administration-domain-name' => array('optional' => true) + $AdministrationDomainName, - 'network-address' => array( - 'constant' => 0, - 'optional' => true, - 'implicit' => true - ) + $NetworkAddress, - 'terminal-identifier' => array( - 'constant' => 1, - 'optional' => true, - 'implicit' => true - ) + $TerminalIdentifier, - 'private-domain-name' => array( - 'constant' => 2, - 'optional' => true, - 'explicit' => true - ) + $PrivateDomainName, - 'organization-name' => array( - 'constant' => 3, - 'optional' => true, - 'implicit' => true - ) + $OrganizationName, - 'numeric-user-identifier' => array( - 'constant' => 4, - 'optional' => true, - 'implicit' => true - ) + $NumericUserIdentifier, - 'personal-name' => array( - 'constant' => 5, - 'optional' => true, - 'implicit' => true - ) + $PersonalName, - 'organizational-unit-names' => array( - 'constant' => 6, - 'optional' => true, - 'implicit' => true - ) + $OrganizationalUnitNames - ) - ); - - $ORAddress = array( - 'type' => ASN1::TYPE_SEQUENCE, - 'children' => array( - 'built-in-standard-attributes' => $BuiltInStandardAttributes, - 'built-in-domain-defined-attributes' => array('optional' => true) + $BuiltInDomainDefinedAttributes, - 'extension-attributes' => array('optional' => true) + $ExtensionAttributes - ) - ); - - $EDIPartyName = array( - 'type' => ASN1::TYPE_SEQUENCE, - 'children' => array( - 'nameAssigner' => array( - 'constant' => 0, - 'optional' => true, - 'implicit' => true - ) + $this->DirectoryString, - // partyName is technically required but \phpseclib\File\ASN1 doesn't currently support non-optional constants and - // setting it to optional gets the job done in any event. - 'partyName' => array( - 'constant' => 1, - 'optional' => true, - 'implicit' => true - ) + $this->DirectoryString - ) - ); - - $GeneralName = array( - 'type' => ASN1::TYPE_CHOICE, - 'children' => array( - 'otherName' => array( - 'constant' => 0, - 'optional' => true, - 'implicit' => true - ) + $AnotherName, - 'rfc822Name' => array( - 'type' => ASN1::TYPE_IA5_STRING, - 'constant' => 1, - 'optional' => true, - 'implicit' => true - ), - 'dNSName' => array( - 'type' => ASN1::TYPE_IA5_STRING, - 'constant' => 2, - 'optional' => true, - 'implicit' => true - ), - 'x400Address' => array( - 'constant' => 3, - 'optional' => true, - 'implicit' => true - ) + $ORAddress, - 'directoryName' => array( - 'constant' => 4, - 'optional' => true, - 'explicit' => true - ) + $this->Name, - 'ediPartyName' => array( - 'constant' => 5, - 'optional' => true, - 'implicit' => true - ) + $EDIPartyName, - 'uniformResourceIdentifier' => array( - 'type' => ASN1::TYPE_IA5_STRING, - 'constant' => 6, - 'optional' => true, - 'implicit' => true - ), - 'iPAddress' => array( - 'type' => ASN1::TYPE_OCTET_STRING, - 'constant' => 7, - 'optional' => true, - 'implicit' => true - ), - 'registeredID' => array( - 'type' => ASN1::TYPE_OBJECT_IDENTIFIER, - 'constant' => 8, - 'optional' => true, - 'implicit' => true - ) - ) - ); - - $GeneralNames = array( - 'type' => ASN1::TYPE_SEQUENCE, - 'min' => 1, - 'max' => -1, - 'children' => $GeneralName - ); - - $this->IssuerAltName = $GeneralNames; - - $ReasonFlags = array( - 'type' => ASN1::TYPE_BIT_STRING, - 'mapping' => array( - 'unused', - 'keyCompromise', - 'cACompromise', - 'affiliationChanged', - 'superseded', - 'cessationOfOperation', - 'certificateHold', - 'privilegeWithdrawn', - 'aACompromise' - ) - ); - - $DistributionPointName = array( - 'type' => ASN1::TYPE_CHOICE, - 'children' => array( - 'fullName' => array( - 'constant' => 0, - 'optional' => true, - 'implicit' => true - ) + $GeneralNames, - 'nameRelativeToCRLIssuer' => array( - 'constant' => 1, - 'optional' => true, - 'implicit' => true - ) + $this->RelativeDistinguishedName - ) - ); - - $DistributionPoint = array( - 'type' => ASN1::TYPE_SEQUENCE, - 'children' => array( - 'distributionPoint' => array( - 'constant' => 0, - 'optional' => true, - 'explicit' => true - ) + $DistributionPointName, - 'reasons' => array( - 'constant' => 1, - 'optional' => true, - 'implicit' => true - ) + $ReasonFlags, - 'cRLIssuer' => array( - 'constant' => 2, - 'optional' => true, - 'implicit' => true - ) + $GeneralNames - ) - ); - - $this->CRLDistributionPoints = array( - 'type' => ASN1::TYPE_SEQUENCE, - 'min' => 1, - 'max' => -1, - 'children' => $DistributionPoint - ); - - $this->AuthorityKeyIdentifier = array( - 'type' => ASN1::TYPE_SEQUENCE, - 'children' => array( - 'keyIdentifier' => array( - 'constant' => 0, - 'optional' => true, - 'implicit' => true - ) + $this->KeyIdentifier, - 'authorityCertIssuer' => array( - 'constant' => 1, - 'optional' => true, - 'implicit' => true - ) + $GeneralNames, - 'authorityCertSerialNumber' => array( - 'constant' => 2, - 'optional' => true, - 'implicit' => true - ) + $CertificateSerialNumber - ) - ); - - $PolicyQualifierId = array('type' => ASN1::TYPE_OBJECT_IDENTIFIER); - - $PolicyQualifierInfo = array( - 'type' => ASN1::TYPE_SEQUENCE, - 'children' => array( - 'policyQualifierId' => $PolicyQualifierId, - 'qualifier' => array('type' => ASN1::TYPE_ANY) - ) - ); - - $CertPolicyId = array('type' => ASN1::TYPE_OBJECT_IDENTIFIER); - - $PolicyInformation = array( - 'type' => ASN1::TYPE_SEQUENCE, - 'children' => array( - 'policyIdentifier' => $CertPolicyId, - 'policyQualifiers' => array( - 'type' => ASN1::TYPE_SEQUENCE, - 'min' => 0, - 'max' => -1, - 'optional' => true, - 'children' => $PolicyQualifierInfo - ) - ) - ); - - $this->CertificatePolicies = array( - 'type' => ASN1::TYPE_SEQUENCE, - 'min' => 1, - 'max' => -1, - 'children' => $PolicyInformation - ); - - $this->PolicyMappings = array( - 'type' => ASN1::TYPE_SEQUENCE, - 'min' => 1, - 'max' => -1, - 'children' => array( - 'type' => ASN1::TYPE_SEQUENCE, - 'children' => array( - 'issuerDomainPolicy' => $CertPolicyId, - 'subjectDomainPolicy' => $CertPolicyId - ) - ) - ); - - $KeyPurposeId = array('type' => ASN1::TYPE_OBJECT_IDENTIFIER); - - $this->ExtKeyUsageSyntax = array( - 'type' => ASN1::TYPE_SEQUENCE, - 'min' => 1, - 'max' => -1, - 'children' => $KeyPurposeId - ); - - $AccessDescription = array( - 'type' => ASN1::TYPE_SEQUENCE, - 'children' => array( - 'accessMethod' => array('type' => ASN1::TYPE_OBJECT_IDENTIFIER), - 'accessLocation' => $GeneralName - ) - ); - - $this->AuthorityInfoAccessSyntax = array( - 'type' => ASN1::TYPE_SEQUENCE, - 'min' => 1, - 'max' => -1, - 'children' => $AccessDescription - ); - - $this->SubjectInfoAccessSyntax = array( - 'type' => ASN1::TYPE_SEQUENCE, - 'min' => 1, - 'max' => -1, - 'children' => $AccessDescription - ); - - $this->SubjectAltName = $GeneralNames; - - $this->PrivateKeyUsagePeriod = array( - 'type' => ASN1::TYPE_SEQUENCE, - 'children' => array( - 'notBefore' => array( - 'constant' => 0, - 'optional' => true, - 'implicit' => true, - 'type' => ASN1::TYPE_GENERALIZED_TIME), - 'notAfter' => array( - 'constant' => 1, - 'optional' => true, - 'implicit' => true, - 'type' => ASN1::TYPE_GENERALIZED_TIME) - ) - ); - - $BaseDistance = array('type' => ASN1::TYPE_INTEGER); - - $GeneralSubtree = array( - 'type' => ASN1::TYPE_SEQUENCE, - 'children' => array( - 'base' => $GeneralName, - 'minimum' => array( - 'constant' => 0, - 'optional' => true, - 'implicit' => true, - 'default' => new BigInteger(0) - ) + $BaseDistance, - 'maximum' => array( - 'constant' => 1, - 'optional' => true, - 'implicit' => true, - ) + $BaseDistance - ) - ); - - $GeneralSubtrees = array( - 'type' => ASN1::TYPE_SEQUENCE, - 'min' => 1, - 'max' => -1, - 'children' => $GeneralSubtree - ); - - $this->NameConstraints = array( - 'type' => ASN1::TYPE_SEQUENCE, - 'children' => array( - 'permittedSubtrees' => array( - 'constant' => 0, - 'optional' => true, - 'implicit' => true - ) + $GeneralSubtrees, - 'excludedSubtrees' => array( - 'constant' => 1, - 'optional' => true, - 'implicit' => true - ) + $GeneralSubtrees - ) - ); - - $this->CPSuri = array('type' => ASN1::TYPE_IA5_STRING); - - $DisplayText = array( - 'type' => ASN1::TYPE_CHOICE, - 'children' => array( - 'ia5String' => array('type' => ASN1::TYPE_IA5_STRING), - 'visibleString' => array('type' => ASN1::TYPE_VISIBLE_STRING), - 'bmpString' => array('type' => ASN1::TYPE_BMP_STRING), - 'utf8String' => array('type' => ASN1::TYPE_UTF8_STRING) - ) - ); - - $NoticeReference = array( - 'type' => ASN1::TYPE_SEQUENCE, - 'children' => array( - 'organization' => $DisplayText, - 'noticeNumbers' => array( - 'type' => ASN1::TYPE_SEQUENCE, - 'min' => 1, - 'max' => 200, - 'children' => array('type' => ASN1::TYPE_INTEGER) - ) - ) - ); - - $this->UserNotice = array( - 'type' => ASN1::TYPE_SEQUENCE, - 'children' => array( - 'noticeRef' => array( - 'optional' => true, - 'implicit' => true - ) + $NoticeReference, - 'explicitText' => array( - 'optional' => true, - 'implicit' => true - ) + $DisplayText - ) - ); - - // mapping is from - $this->netscape_cert_type = array( - 'type' => ASN1::TYPE_BIT_STRING, - 'mapping' => array( - 'SSLClient', - 'SSLServer', - 'Email', - 'ObjectSigning', - 'Reserved', - 'SSLCA', - 'EmailCA', - 'ObjectSigningCA' - ) - ); - - $this->netscape_comment = array('type' => ASN1::TYPE_IA5_STRING); - $this->netscape_ca_policy_url = array('type' => ASN1::TYPE_IA5_STRING); - - // attribute is used in RFC2986 but we're using the RFC5280 definition - - $Attribute = array( - 'type' => ASN1::TYPE_SEQUENCE, - 'children' => array( - 'type' => $AttributeType, - 'value'=> array( - 'type' => ASN1::TYPE_SET, - 'min' => 1, - 'max' => -1, - 'children' => $this->AttributeValue - ) - ) - ); - - $this->SubjectDirectoryAttributes = array( - 'type' => ASN1::TYPE_SEQUENCE, - 'min' => 1, - 'max' => -1, - 'children' => $Attribute - ); - - // adapted from - - $Attributes = array( - 'type' => ASN1::TYPE_SET, - 'min' => 1, - 'max' => -1, - 'children' => $Attribute - ); - - $CertificationRequestInfo = array( - 'type' => ASN1::TYPE_SEQUENCE, - 'children' => array( - 'version' => array( - 'type' => ASN1::TYPE_INTEGER, - 'mapping' => array('v1') - ), - 'subject' => $this->Name, - 'subjectPKInfo' => $SubjectPublicKeyInfo, - 'attributes' => array( - 'constant' => 0, - 'optional' => true, - 'implicit' => true - ) + $Attributes, - ) - ); - - $this->CertificationRequest = array( - 'type' => ASN1::TYPE_SEQUENCE, - 'children' => array( - 'certificationRequestInfo' => $CertificationRequestInfo, - 'signatureAlgorithm' => $AlgorithmIdentifier, - 'signature' => array('type' => ASN1::TYPE_BIT_STRING) - ) - ); - - $RevokedCertificate = array( - 'type' => ASN1::TYPE_SEQUENCE, - 'children' => array( - 'userCertificate' => $CertificateSerialNumber, - 'revocationDate' => $Time, - 'crlEntryExtensions' => array( - 'optional' => true - ) + $this->Extensions - ) - ); - - $TBSCertList = array( - 'type' => ASN1::TYPE_SEQUENCE, - 'children' => array( - 'version' => array( - 'optional' => true, - 'default' => 'v1' - ) + $Version, - 'signature' => $AlgorithmIdentifier, - 'issuer' => $this->Name, - 'thisUpdate' => $Time, - 'nextUpdate' => array( - 'optional' => true - ) + $Time, - 'revokedCertificates' => array( - 'type' => ASN1::TYPE_SEQUENCE, - 'optional' => true, - 'min' => 0, - 'max' => -1, - 'children' => $RevokedCertificate - ), - 'crlExtensions' => array( - 'constant' => 0, - 'optional' => true, - 'explicit' => true - ) + $this->Extensions - ) - ); - - $this->CertificateList = array( - 'type' => ASN1::TYPE_SEQUENCE, - 'children' => array( - 'tbsCertList' => $TBSCertList, - 'signatureAlgorithm' => $AlgorithmIdentifier, - 'signature' => array('type' => ASN1::TYPE_BIT_STRING) - ) - ); - - $this->CRLNumber = array('type' => ASN1::TYPE_INTEGER); - - $this->CRLReason = array('type' => ASN1::TYPE_ENUMERATED, - 'mapping' => array( - 'unspecified', - 'keyCompromise', - 'cACompromise', - 'affiliationChanged', - 'superseded', - 'cessationOfOperation', - 'certificateHold', - // Value 7 is not used. - 8 => 'removeFromCRL', - 'privilegeWithdrawn', - 'aACompromise' - ) - ); - - $this->IssuingDistributionPoint = array('type' => ASN1::TYPE_SEQUENCE, - 'children' => array( - 'distributionPoint' => array( - 'constant' => 0, - 'optional' => true, - 'explicit' => true - ) + $DistributionPointName, - 'onlyContainsUserCerts' => array( - 'type' => ASN1::TYPE_BOOLEAN, - 'constant' => 1, - 'optional' => true, - 'default' => false, - 'implicit' => true - ), - 'onlyContainsCACerts' => array( - 'type' => ASN1::TYPE_BOOLEAN, - 'constant' => 2, - 'optional' => true, - 'default' => false, - 'implicit' => true - ), - 'onlySomeReasons' => array( - 'constant' => 3, - 'optional' => true, - 'implicit' => true - ) + $ReasonFlags, - 'indirectCRL' => array( - 'type' => ASN1::TYPE_BOOLEAN, - 'constant' => 4, - 'optional' => true, - 'default' => false, - 'implicit' => true - ), - 'onlyContainsAttributeCerts' => array( - 'type' => ASN1::TYPE_BOOLEAN, - 'constant' => 5, - 'optional' => true, - 'default' => false, - 'implicit' => true - ) - ) - ); - - $this->InvalidityDate = array('type' => ASN1::TYPE_GENERALIZED_TIME); - - $this->CertificateIssuer = $GeneralNames; - - $this->HoldInstructionCode = array('type' => ASN1::TYPE_OBJECT_IDENTIFIER); - - $PublicKeyAndChallenge = array( - 'type' => ASN1::TYPE_SEQUENCE, - 'children' => array( - 'spki' => $SubjectPublicKeyInfo, - 'challenge' => array('type' => ASN1::TYPE_IA5_STRING) - ) - ); - - $this->SignedPublicKeyAndChallenge = array( - 'type' => ASN1::TYPE_SEQUENCE, - 'children' => array( - 'publicKeyAndChallenge' => $PublicKeyAndChallenge, - 'signatureAlgorithm' => $AlgorithmIdentifier, - 'signature' => array('type' => ASN1::TYPE_BIT_STRING) - ) - ); - - $this->PostalAddress = array( - 'type' => ASN1::TYPE_SEQUENCE, - 'optional' => true, - 'min' => 1, - 'max' => -1, - 'children' => $this->DirectoryString - ); - - // OIDs from RFC5280 and those RFCs mentioned in RFC5280#section-4.1.1.2 - $this->oids = array( - '1.3.6.1.5.5.7' => 'id-pkix', - '1.3.6.1.5.5.7.1' => 'id-pe', - '1.3.6.1.5.5.7.2' => 'id-qt', - '1.3.6.1.5.5.7.3' => 'id-kp', - '1.3.6.1.5.5.7.48' => 'id-ad', - '1.3.6.1.5.5.7.2.1' => 'id-qt-cps', - '1.3.6.1.5.5.7.2.2' => 'id-qt-unotice', - '1.3.6.1.5.5.7.48.1' =>'id-ad-ocsp', - '1.3.6.1.5.5.7.48.2' => 'id-ad-caIssuers', - '1.3.6.1.5.5.7.48.3' => 'id-ad-timeStamping', - '1.3.6.1.5.5.7.48.5' => 'id-ad-caRepository', - '2.5.4' => 'id-at', - '2.5.4.41' => 'id-at-name', - '2.5.4.4' => 'id-at-surname', - '2.5.4.42' => 'id-at-givenName', - '2.5.4.43' => 'id-at-initials', - '2.5.4.44' => 'id-at-generationQualifier', - '2.5.4.3' => 'id-at-commonName', - '2.5.4.7' => 'id-at-localityName', - '2.5.4.8' => 'id-at-stateOrProvinceName', - '2.5.4.10' => 'id-at-organizationName', - '2.5.4.11' => 'id-at-organizationalUnitName', - '2.5.4.12' => 'id-at-title', - '2.5.4.13' => 'id-at-description', - '2.5.4.46' => 'id-at-dnQualifier', - '2.5.4.6' => 'id-at-countryName', - '2.5.4.5' => 'id-at-serialNumber', - '2.5.4.65' => 'id-at-pseudonym', - '2.5.4.17' => 'id-at-postalCode', - '2.5.4.9' => 'id-at-streetAddress', - '2.5.4.45' => 'id-at-uniqueIdentifier', - '2.5.4.72' => 'id-at-role', - '2.5.4.16' => 'id-at-postalAddress', - - '0.9.2342.19200300.100.1.25' => 'id-domainComponent', - '1.2.840.113549.1.9' => 'pkcs-9', - '1.2.840.113549.1.9.1' => 'pkcs-9-at-emailAddress', - '2.5.29' => 'id-ce', - '2.5.29.35' => 'id-ce-authorityKeyIdentifier', - '2.5.29.14' => 'id-ce-subjectKeyIdentifier', - '2.5.29.15' => 'id-ce-keyUsage', - '2.5.29.16' => 'id-ce-privateKeyUsagePeriod', - '2.5.29.32' => 'id-ce-certificatePolicies', - '2.5.29.32.0' => 'anyPolicy', - - '2.5.29.33' => 'id-ce-policyMappings', - '2.5.29.17' => 'id-ce-subjectAltName', - '2.5.29.18' => 'id-ce-issuerAltName', - '2.5.29.9' => 'id-ce-subjectDirectoryAttributes', - '2.5.29.19' => 'id-ce-basicConstraints', - '2.5.29.30' => 'id-ce-nameConstraints', - '2.5.29.36' => 'id-ce-policyConstraints', - '2.5.29.31' => 'id-ce-cRLDistributionPoints', - '2.5.29.37' => 'id-ce-extKeyUsage', - '2.5.29.37.0' => 'anyExtendedKeyUsage', - '1.3.6.1.5.5.7.3.1' => 'id-kp-serverAuth', - '1.3.6.1.5.5.7.3.2' => 'id-kp-clientAuth', - '1.3.6.1.5.5.7.3.3' => 'id-kp-codeSigning', - '1.3.6.1.5.5.7.3.4' => 'id-kp-emailProtection', - '1.3.6.1.5.5.7.3.8' => 'id-kp-timeStamping', - '1.3.6.1.5.5.7.3.9' => 'id-kp-OCSPSigning', - '2.5.29.54' => 'id-ce-inhibitAnyPolicy', - '2.5.29.46' => 'id-ce-freshestCRL', - '1.3.6.1.5.5.7.1.1' => 'id-pe-authorityInfoAccess', - '1.3.6.1.5.5.7.1.11' => 'id-pe-subjectInfoAccess', - '2.5.29.20' => 'id-ce-cRLNumber', - '2.5.29.28' => 'id-ce-issuingDistributionPoint', - '2.5.29.27' => 'id-ce-deltaCRLIndicator', - '2.5.29.21' => 'id-ce-cRLReasons', - '2.5.29.29' => 'id-ce-certificateIssuer', - '2.5.29.23' => 'id-ce-holdInstructionCode', - '1.2.840.10040.2' => 'holdInstruction', - '1.2.840.10040.2.1' => 'id-holdinstruction-none', - '1.2.840.10040.2.2' => 'id-holdinstruction-callissuer', - '1.2.840.10040.2.3' => 'id-holdinstruction-reject', - '2.5.29.24' => 'id-ce-invalidityDate', - - '1.2.840.113549.2.2' => 'md2', - '1.2.840.113549.2.5' => 'md5', - '1.3.14.3.2.26' => 'id-sha1', - '1.2.840.10040.4.1' => 'id-dsa', - '1.2.840.10040.4.3' => 'id-dsa-with-sha1', - '1.2.840.113549.1.1' => 'pkcs-1', - '1.2.840.113549.1.1.1' => 'rsaEncryption', - '1.2.840.113549.1.1.2' => 'md2WithRSAEncryption', - '1.2.840.113549.1.1.4' => 'md5WithRSAEncryption', - '1.2.840.113549.1.1.5' => 'sha1WithRSAEncryption', - '1.2.840.10046.2.1' => 'dhpublicnumber', - '2.16.840.1.101.2.1.1.22' => 'id-keyExchangeAlgorithm', - '1.2.840.10045' => 'ansi-X9-62', - '1.2.840.10045.4' => 'id-ecSigType', - '1.2.840.10045.4.1' => 'ecdsa-with-SHA1', - '1.2.840.10045.1' => 'id-fieldType', - '1.2.840.10045.1.1' => 'prime-field', - '1.2.840.10045.1.2' => 'characteristic-two-field', - '1.2.840.10045.1.2.3' => 'id-characteristic-two-basis', - '1.2.840.10045.1.2.3.1' => 'gnBasis', - '1.2.840.10045.1.2.3.2' => 'tpBasis', - '1.2.840.10045.1.2.3.3' => 'ppBasis', - '1.2.840.10045.2' => 'id-publicKeyType', - '1.2.840.10045.2.1' => 'id-ecPublicKey', - '1.2.840.10045.3' => 'ellipticCurve', - '1.2.840.10045.3.0' => 'c-TwoCurve', - '1.2.840.10045.3.0.1' => 'c2pnb163v1', - '1.2.840.10045.3.0.2' => 'c2pnb163v2', - '1.2.840.10045.3.0.3' => 'c2pnb163v3', - '1.2.840.10045.3.0.4' => 'c2pnb176w1', - '1.2.840.10045.3.0.5' => 'c2pnb191v1', - '1.2.840.10045.3.0.6' => 'c2pnb191v2', - '1.2.840.10045.3.0.7' => 'c2pnb191v3', - '1.2.840.10045.3.0.8' => 'c2pnb191v4', - '1.2.840.10045.3.0.9' => 'c2pnb191v5', - '1.2.840.10045.3.0.10' => 'c2pnb208w1', - '1.2.840.10045.3.0.11' => 'c2pnb239v1', - '1.2.840.10045.3.0.12' => 'c2pnb239v2', - '1.2.840.10045.3.0.13' => 'c2pnb239v3', - '1.2.840.10045.3.0.14' => 'c2pnb239v4', - '1.2.840.10045.3.0.15' => 'c2pnb239v5', - '1.2.840.10045.3.0.16' => 'c2pnb272w1', - '1.2.840.10045.3.0.17' => 'c2pnb304w1', - '1.2.840.10045.3.0.18' => 'c2pnb359v1', - '1.2.840.10045.3.0.19' => 'c2pnb368w1', - '1.2.840.10045.3.0.20' => 'c2pnb431r1', - '1.2.840.10045.3.1' => 'primeCurve', - '1.2.840.10045.3.1.1' => 'prime192v1', - '1.2.840.10045.3.1.2' => 'prime192v2', - '1.2.840.10045.3.1.3' => 'prime192v3', - '1.2.840.10045.3.1.4' => 'prime239v1', - '1.2.840.10045.3.1.5' => 'prime239v2', - '1.2.840.10045.3.1.6' => 'prime239v3', - '1.2.840.10045.3.1.7' => 'prime256v1', - '1.2.840.113549.1.1.7' => 'id-RSAES-OAEP', - '1.2.840.113549.1.1.9' => 'id-pSpecified', - '1.2.840.113549.1.1.10' => 'id-RSASSA-PSS', - '1.2.840.113549.1.1.8' => 'id-mgf1', - '1.2.840.113549.1.1.14' => 'sha224WithRSAEncryption', - '1.2.840.113549.1.1.11' => 'sha256WithRSAEncryption', - '1.2.840.113549.1.1.12' => 'sha384WithRSAEncryption', - '1.2.840.113549.1.1.13' => 'sha512WithRSAEncryption', - '2.16.840.1.101.3.4.2.4' => 'id-sha224', - '2.16.840.1.101.3.4.2.1' => 'id-sha256', - '2.16.840.1.101.3.4.2.2' => 'id-sha384', - '2.16.840.1.101.3.4.2.3' => 'id-sha512', - '1.2.643.2.2.4' => 'id-GostR3411-94-with-GostR3410-94', - '1.2.643.2.2.3' => 'id-GostR3411-94-with-GostR3410-2001', - '1.2.643.2.2.20' => 'id-GostR3410-2001', - '1.2.643.2.2.19' => 'id-GostR3410-94', - // Netscape Object Identifiers from "Netscape Certificate Extensions" - '2.16.840.1.113730' => 'netscape', - '2.16.840.1.113730.1' => 'netscape-cert-extension', - '2.16.840.1.113730.1.1' => 'netscape-cert-type', - '2.16.840.1.113730.1.13' => 'netscape-comment', - '2.16.840.1.113730.1.8' => 'netscape-ca-policy-url', - // the following are X.509 extensions not supported by phpseclib - '1.3.6.1.5.5.7.1.12' => 'id-pe-logotype', - '1.2.840.113533.7.65.0' => 'entrustVersInfo', - '2.16.840.1.113733.1.6.9' => 'verisignPrivate', - // for Certificate Signing Requests - // see http://tools.ietf.org/html/rfc2985 - '1.2.840.113549.1.9.2' => 'pkcs-9-at-unstructuredName', // PKCS #9 unstructured name - '1.2.840.113549.1.9.7' => 'pkcs-9-at-challengePassword', // Challenge password for certificate revocations - '1.2.840.113549.1.9.14' => 'pkcs-9-at-extensionRequest' // Certificate extension request - ); + if (!self::$oidsLoaded) { + // OIDs from RFC5280 and those RFCs mentioned in RFC5280#section-4.1.1.2 + ASN1::loadOIDs([ + //'id-pkix' => '1.3.6.1.5.5.7', + //'id-pe' => '1.3.6.1.5.5.7.1', + //'id-qt' => '1.3.6.1.5.5.7.2', + //'id-kp' => '1.3.6.1.5.5.7.3', + //'id-ad' => '1.3.6.1.5.5.7.48', + 'id-qt-cps' => '1.3.6.1.5.5.7.2.1', + 'id-qt-unotice' => '1.3.6.1.5.5.7.2.2', + 'id-ad-ocsp' => '1.3.6.1.5.5.7.48.1', + 'id-ad-caIssuers' => '1.3.6.1.5.5.7.48.2', + 'id-ad-timeStamping' => '1.3.6.1.5.5.7.48.3', + 'id-ad-caRepository' => '1.3.6.1.5.5.7.48.5', + //'id-at' => '2.5.4', + 'id-at-name' => '2.5.4.41', + 'id-at-surname' => '2.5.4.4', + 'id-at-givenName' => '2.5.4.42', + 'id-at-initials' => '2.5.4.43', + 'id-at-generationQualifier' => '2.5.4.44', + 'id-at-commonName' => '2.5.4.3', + 'id-at-localityName' => '2.5.4.7', + 'id-at-stateOrProvinceName' => '2.5.4.8', + 'id-at-organizationName' => '2.5.4.10', + 'id-at-organizationalUnitName' => '2.5.4.11', + 'id-at-title' => '2.5.4.12', + 'id-at-description' => '2.5.4.13', + 'id-at-dnQualifier' => '2.5.4.46', + 'id-at-countryName' => '2.5.4.6', + 'id-at-serialNumber' => '2.5.4.5', + 'id-at-pseudonym' => '2.5.4.65', + 'id-at-postalCode' => '2.5.4.17', + 'id-at-streetAddress' => '2.5.4.9', + 'id-at-uniqueIdentifier' => '2.5.4.45', + 'id-at-role' => '2.5.4.72', + 'id-at-postalAddress' => '2.5.4.16', + + //'id-domainComponent' => '0.9.2342.19200300.100.1.25', + //'pkcs-9' => '1.2.840.113549.1.9', + 'pkcs-9-at-emailAddress' => '1.2.840.113549.1.9.1', + //'id-ce' => '2.5.29', + 'id-ce-authorityKeyIdentifier' => '2.5.29.35', + 'id-ce-subjectKeyIdentifier' => '2.5.29.14', + 'id-ce-keyUsage' => '2.5.29.15', + 'id-ce-privateKeyUsagePeriod' => '2.5.29.16', + 'id-ce-certificatePolicies' => '2.5.29.32', + //'anyPolicy' => '2.5.29.32.0', + + 'id-ce-policyMappings' => '2.5.29.33', + + 'id-ce-subjectAltName' => '2.5.29.17', + 'id-ce-issuerAltName' => '2.5.29.18', + 'id-ce-subjectDirectoryAttributes' => '2.5.29.9', + 'id-ce-basicConstraints' => '2.5.29.19', + 'id-ce-nameConstraints' => '2.5.29.30', + 'id-ce-policyConstraints' => '2.5.29.36', + 'id-ce-cRLDistributionPoints' => '2.5.29.31', + 'id-ce-extKeyUsage' => '2.5.29.37', + //'anyExtendedKeyUsage' => '2.5.29.37.0', + 'id-kp-serverAuth' => '1.3.6.1.5.5.7.3.1', + 'id-kp-clientAuth' => '1.3.6.1.5.5.7.3.2', + 'id-kp-codeSigning' => '1.3.6.1.5.5.7.3.3', + 'id-kp-emailProtection' => '1.3.6.1.5.5.7.3.4', + 'id-kp-timeStamping' => '1.3.6.1.5.5.7.3.8', + 'id-kp-OCSPSigning' => '1.3.6.1.5.5.7.3.9', + 'id-ce-inhibitAnyPolicy' => '2.5.29.54', + 'id-ce-freshestCRL' => '2.5.29.46', + 'id-pe-authorityInfoAccess' => '1.3.6.1.5.5.7.1.1', + 'id-pe-subjectInfoAccess' => '1.3.6.1.5.5.7.1.11', + 'id-ce-cRLNumber' => '2.5.29.20', + 'id-ce-issuingDistributionPoint' => '2.5.29.28', + 'id-ce-deltaCRLIndicator' => '2.5.29.27', + 'id-ce-cRLReasons' => '2.5.29.21', + 'id-ce-certificateIssuer' => '2.5.29.29', + 'id-ce-holdInstructionCode' => '2.5.29.23', + //'holdInstruction' => '1.2.840.10040.2', + 'id-holdinstruction-none' => '1.2.840.10040.2.1', + 'id-holdinstruction-callissuer' => '1.2.840.10040.2.2', + 'id-holdinstruction-reject' => '1.2.840.10040.2.3', + 'id-ce-invalidityDate' => '2.5.29.24', + + 'rsaEncryption' => '1.2.840.113549.1.1.1', + 'md2WithRSAEncryption' => '1.2.840.113549.1.1.2', + 'md5WithRSAEncryption' => '1.2.840.113549.1.1.4', + 'sha1WithRSAEncryption' => '1.2.840.113549.1.1.5', + 'sha224WithRSAEncryption' => '1.2.840.113549.1.1.14', + 'sha256WithRSAEncryption' => '1.2.840.113549.1.1.11', + 'sha384WithRSAEncryption' => '1.2.840.113549.1.1.12', + 'sha512WithRSAEncryption' => '1.2.840.113549.1.1.13', + + 'id-ecPublicKey' => '1.2.840.10045.2.1', + 'ecdsa-with-SHA1' => '1.2.840.10045.4.1', + // from https://tools.ietf.org/html/rfc5758#section-3.2 + 'ecdsa-with-SHA224' => '1.2.840.10045.4.3.1', + 'ecdsa-with-SHA256' => '1.2.840.10045.4.3.2', + 'ecdsa-with-SHA384' => '1.2.840.10045.4.3.3', + 'ecdsa-with-SHA512' => '1.2.840.10045.4.3.4', + + 'id-dsa' => '1.2.840.10040.4.1', + 'id-dsa-with-sha1' => '1.2.840.10040.4.3', + // from https://tools.ietf.org/html/rfc5758#section-3.1 + 'id-dsa-with-sha224' => '2.16.840.1.101.3.4.3.1', + 'id-dsa-with-sha256' => '2.16.840.1.101.3.4.3.2', + + // from https://tools.ietf.org/html/rfc8410: + 'id-Ed25519' => '1.3.101.112', + 'id-Ed448' => '1.3.101.113', + + 'id-RSASSA-PSS' => '1.2.840.113549.1.1.10', + + //'id-sha224' => '2.16.840.1.101.3.4.2.4', + //'id-sha256' => '2.16.840.1.101.3.4.2.1', + //'id-sha384' => '2.16.840.1.101.3.4.2.2', + //'id-sha512' => '2.16.840.1.101.3.4.2.3', + //'id-GostR3411-94-with-GostR3410-94' => '1.2.643.2.2.4', + //'id-GostR3411-94-with-GostR3410-2001' => '1.2.643.2.2.3', + //'id-GostR3410-2001' => '1.2.643.2.2.20', + //'id-GostR3410-94' => '1.2.643.2.2.19', + // Netscape Object Identifiers from "Netscape Certificate Extensions" + 'netscape' => '2.16.840.1.113730', + 'netscape-cert-extension' => '2.16.840.1.113730.1', + 'netscape-cert-type' => '2.16.840.1.113730.1.1', + 'netscape-comment' => '2.16.840.1.113730.1.13', + 'netscape-ca-policy-url' => '2.16.840.1.113730.1.8', + // the following are X.509 extensions not supported by phpseclib + 'id-pe-logotype' => '1.3.6.1.5.5.7.1.12', + 'entrustVersInfo' => '1.2.840.113533.7.65.0', + 'verisignPrivate' => '2.16.840.1.113733.1.6.9', + // for Certificate Signing Requests + // see http://tools.ietf.org/html/rfc2985 + 'pkcs-9-at-unstructuredName' => '1.2.840.113549.1.9.2', // PKCS #9 unstructured name + 'pkcs-9-at-challengePassword' => '1.2.840.113549.1.9.7', // Challenge password for certificate revocations + 'pkcs-9-at-extensionRequest' => '1.2.840.113549.1.9.14' // Certificate extension request + ]); + } } /** @@ -1457,12 +421,11 @@ function __construct() * * Returns an associative array describing the X.509 cert or a false if the cert failed to load * - * @param string $cert + * @param array|string $cert * @param int $mode - * @access public * @return mixed */ - function loadX509($cert, $mode = self::FORMAT_AUTO_DETECT) + public function loadX509($cert, $mode = self::FORMAT_AUTO_DETECT) { if (is_array($cert) && isset($cert['tbsCertificate'])) { unset($this->currentCert); @@ -1481,10 +444,8 @@ function loadX509($cert, $mode = self::FORMAT_AUTO_DETECT) return $cert; } - $asn1 = new ASN1(); - if ($mode != self::FORMAT_DER) { - $newcert = $this->_extractBER($cert); + $newcert = ASN1::extractBER($cert); if ($mode == self::FORMAT_PEM && $cert == $newcert) { return false; } @@ -1496,11 +457,10 @@ function loadX509($cert, $mode = self::FORMAT_AUTO_DETECT) return false; } - $asn1->loadOIDs($this->oids); - $decoded = $asn1->decodeBER($cert); + $decoded = ASN1::decodeBER($cert); - if (!empty($decoded)) { - $x509 = $asn1->asn1map($decoded[0], $this->Certificate); + if ($decoded) { + $x509 = ASN1::asn1map($decoded[0], Maps\Certificate::MAP); } if (!isset($x509) || $x509 === false) { $this->currentCert = false; @@ -1509,14 +469,18 @@ function loadX509($cert, $mode = self::FORMAT_AUTO_DETECT) $this->signatureSubject = substr($cert, $decoded[0]['content'][0]['start'], $decoded[0]['content'][0]['length']); - if ($this->_isSubArrayValid($x509, 'tbsCertificate/extensions')) { - $this->_mapInExtensions($x509, 'tbsCertificate/extensions', $asn1); + if ($this->isSubArrayValid($x509, 'tbsCertificate/extensions')) { + $this->mapInExtensions($x509, 'tbsCertificate/extensions'); } - $this->_mapInDNs($x509, 'tbsCertificate/issuer/rdnSequence', $asn1); - $this->_mapInDNs($x509, 'tbsCertificate/subject/rdnSequence', $asn1); + $this->mapInDNs($x509, 'tbsCertificate/issuer/rdnSequence'); + $this->mapInDNs($x509, 'tbsCertificate/subject/rdnSequence'); - $key = &$x509['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey']; - $key = $this->_reformatKey($x509['tbsCertificate']['subjectPublicKeyInfo']['algorithm']['algorithm'], $key); + $key = $x509['tbsCertificate']['subjectPublicKeyInfo']; + $key = ASN1::encodeDER($key, Maps\SubjectPublicKeyInfo::MAP); + $x509['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey'] = + "-----BEGIN PUBLIC KEY-----\r\n" . + chunk_split(base64_encode($key), 64) . + "-----END PUBLIC KEY-----"; $this->currentCert = $x509; $this->dn = $x509['tbsCertificate']['subject']; @@ -1532,10 +496,9 @@ function loadX509($cert, $mode = self::FORMAT_AUTO_DETECT) * * @param array $cert * @param int $format optional - * @access public * @return string */ - function saveX509($cert, $format = self::FORMAT_PEM) + public function saveX509(array $cert, $format = self::FORMAT_PEM) { if (!is_array($cert) || !isset($cert['tbsCertificate'])) { return false; @@ -1543,32 +506,22 @@ function saveX509($cert, $format = self::FORMAT_PEM) switch (true) { // "case !$a: case !$b: break; default: whatever();" is the same thing as "if ($a && $b) whatever()" - case !($algorithm = $this->_subArray($cert, 'tbsCertificate/subjectPublicKeyInfo/algorithm/algorithm')): + case !($algorithm = $this->subArray($cert, 'tbsCertificate/subjectPublicKeyInfo/algorithm/algorithm')): case is_object($cert['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey']): break; default: - switch ($algorithm) { - case 'rsaEncryption': - $cert['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey'] - = base64_encode("\0" . base64_decode(preg_replace('#-.+-|[\r\n]#', '', $cert['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey']))); - /* "[For RSA keys] the parameters field MUST have ASN.1 type NULL for this algorithm identifier." - -- https://tools.ietf.org/html/rfc3279#section-2.3.1 - - given that and the fact that RSA keys appear ot be the only key type for which the parameters field can be blank, - it seems like perhaps the ASN.1 description ought not say the parameters field is OPTIONAL, but whatever. - */ - $cert['tbsCertificate']['subjectPublicKeyInfo']['algorithm']['parameters'] = null; - // https://tools.ietf.org/html/rfc3279#section-2.2.1 - $cert['signatureAlgorithm']['parameters'] = null; - $cert['tbsCertificate']['signature']['parameters'] = null; - } + $cert['tbsCertificate']['subjectPublicKeyInfo'] = new Element( + base64_decode(preg_replace('#-.+-|[\r\n]#', '', $cert['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey'])) + ); } - $asn1 = new ASN1(); - $asn1->loadOIDs($this->oids); + if ($algorithm == 'rsaEncryption') { + $cert['signatureAlgorithm']['parameters'] = null; + $cert['tbsCertificate']['signature']['parameters'] = null; + } - $filters = array(); - $type_utf8_string = array('type' => ASN1::TYPE_UTF8_STRING); + $filters = []; + $type_utf8_string = ['type' => ASN1::TYPE_UTF8_STRING]; $filters['tbsCertificate']['signature']['parameters'] = $type_utf8_string; $filters['tbsCertificate']['signature']['issuer']['rdnSequence']['value'] = $type_utf8_string; $filters['tbsCertificate']['issuer']['rdnSequence']['value'] = $type_utf8_string; @@ -1580,27 +533,31 @@ function saveX509($cert, $format = self::FORMAT_PEM) $filters['distributionPoint']['fullName']['directoryName']['rdnSequence']['value'] = $type_utf8_string; $filters['directoryName']['rdnSequence']['value'] = $type_utf8_string; - /* in the case of policyQualifiers/qualifier, the type has to be \phpseclib\File\ASN1::TYPE_IA5_STRING. - \phpseclib\File\ASN1::TYPE_PRINTABLE_STRING will cause OpenSSL's X.509 parser to spit out random + foreach (self::$extensions as $extension) { + $filters['tbsCertificate']['extensions'][] = $extension; + } + + /* in the case of policyQualifiers/qualifier, the type has to be \phpseclib3\File\ASN1::TYPE_IA5_STRING. + \phpseclib3\File\ASN1::TYPE_PRINTABLE_STRING will cause OpenSSL's X.509 parser to spit out random characters. */ $filters['policyQualifiers']['qualifier'] - = array('type' => ASN1::TYPE_IA5_STRING); + = ['type' => ASN1::TYPE_IA5_STRING]; - $asn1->loadFilters($filters); + ASN1::setFilters($filters); - $this->_mapOutExtensions($cert, 'tbsCertificate/extensions', $asn1); - $this->_mapOutDNs($cert, 'tbsCertificate/issuer/rdnSequence', $asn1); - $this->_mapOutDNs($cert, 'tbsCertificate/subject/rdnSequence', $asn1); + $this->mapOutExtensions($cert, 'tbsCertificate/extensions'); + $this->mapOutDNs($cert, 'tbsCertificate/issuer/rdnSequence'); + $this->mapOutDNs($cert, 'tbsCertificate/subject/rdnSequence'); - $cert = $asn1->encodeDER($cert, $this->Certificate); + $cert = ASN1::encodeDER($cert, Maps\Certificate::MAP); switch ($format) { case self::FORMAT_DER: return $cert; // case self::FORMAT_PEM: default: - return "-----BEGIN CERTIFICATE-----\r\n" . chunk_split(base64_encode($cert), 64) . '-----END CERTIFICATE-----'; + return "-----BEGIN CERTIFICATE-----\r\n" . chunk_split(Strings::base64_encode($cert), 64) . '-----END CERTIFICATE-----'; } } @@ -1610,27 +567,27 @@ function saveX509($cert, $format = self::FORMAT_PEM) * * @param array $root (by reference) * @param string $path - * @param object $asn1 - * @access private */ - function _mapInExtensions(&$root, $path, $asn1) + private function mapInExtensions(array &$root, $path) { - $extensions = &$this->_subArrayUnchecked($root, $path); + $extensions = &$this->subArrayUnchecked($root, $path); if ($extensions) { for ($i = 0; $i < count($extensions); $i++) { $id = $extensions[$i]['extnId']; $value = &$extensions[$i]['extnValue']; - $value = base64_decode($value); - $decoded = $asn1->decodeBER($value); /* [extnValue] contains the DER encoding of an ASN.1 value corresponding to the extension type identified by extnID */ - $map = $this->_getMapping($id); + $map = $this->getMapping($id); if (!is_bool($map)) { $decoder = $id == 'id-ce-nameConstraints' ? - array($this, '_decodeNameConstraintIP') : - array($this, '_decodeIP'); - $mapped = $asn1->asn1map($decoded[0], $map, array('iPAddress' => $decoder)); + [static::class, 'decodeNameConstraintIP'] : + [static::class, 'decodeIP']; + $decoded = ASN1::decodeBER($value); + if (!$decoded) { + continue; + } + $mapped = ASN1::asn1map($decoded[0], $map, ['iPAddress' => $decoder]); $value = $mapped === false ? $decoded[0] : $mapped; if ($id == 'id-ce-certificatePolicies') { @@ -1640,18 +597,19 @@ function _mapInExtensions(&$root, $path, $asn1) } for ($k = 0; $k < count($value[$j]['policyQualifiers']); $k++) { $subid = $value[$j]['policyQualifiers'][$k]['policyQualifierId']; - $map = $this->_getMapping($subid); + $map = $this->getMapping($subid); $subvalue = &$value[$j]['policyQualifiers'][$k]['qualifier']; if ($map !== false) { - $decoded = $asn1->decodeBER($subvalue); - $mapped = $asn1->asn1map($decoded[0], $map); + $decoded = ASN1::decodeBER($subvalue); + if (!$decoded) { + continue; + } + $mapped = ASN1::asn1map($decoded[0], $map); $subvalue = $mapped === false ? $decoded[0] : $mapped; } } } } - } else { - $value = base64_encode($value); } } } @@ -1663,12 +621,28 @@ function _mapInExtensions(&$root, $path, $asn1) * * @param array $root (by reference) * @param string $path - * @param object $asn1 - * @access private */ - function _mapOutExtensions(&$root, $path, $asn1) - { - $extensions = &$this->_subArray($root, $path); + private function mapOutExtensions(array &$root, $path) + { + $extensions = &$this->subArray($root, $path, !empty($this->extensionValues)); + + foreach ($this->extensionValues as $id => $data) { + extract($data); + $newext = [ + 'extnId' => $id, + 'extnValue' => $value, + 'critical' => $critical + ]; + if ($replace) { + foreach ($extensions as $key => $value) { + if ($value['extnId'] == $id) { + $extensions[$key] = $newext; + continue 2; + } + } + } + $extensions[] = $newext; + } if (is_array($extensions)) { $size = count($extensions); @@ -1688,12 +662,12 @@ function _mapOutExtensions(&$root, $path, $asn1) } for ($k = 0; $k < count($value[$j]['policyQualifiers']); $k++) { $subid = $value[$j]['policyQualifiers'][$k]['policyQualifierId']; - $map = $this->_getMapping($subid); + $map = $this->getMapping($subid); $subvalue = &$value[$j]['policyQualifiers'][$k]['qualifier']; if ($map !== false) { - // by default \phpseclib\File\ASN1 will try to render qualifier as a \phpseclib\File\ASN1::TYPE_IA5_STRING since it's - // actual type is \phpseclib\File\ASN1::TYPE_ANY - $subvalue = new Element($asn1->encodeDER($subvalue, $map)); + // by default \phpseclib3\File\ASN1 will try to render qualifier as a \phpseclib3\File\ASN1::TYPE_IA5_STRING since it's + // actual type is \phpseclib3\File\ASN1::TYPE_ANY + $subvalue = new Element(ASN1::encodeDER($subvalue, $map)); } } } @@ -1709,15 +683,14 @@ function _mapOutExtensions(&$root, $path, $asn1) /* [extnValue] contains the DER encoding of an ASN.1 value corresponding to the extension type identified by extnID */ - $map = $this->_getMapping($id); + $map = $this->getMapping($id); if (is_bool($map)) { if (!$map) { - user_error($id . ' is not a currently supported extension'); + //user_error($id . ' is not a currently supported extension'); unset($extensions[$i]); } } else { - $temp = $asn1->encodeDER($value, $map, array('iPAddress' => array($this, '_encodeIP'))); - $value = base64_encode($temp); + $value = ASN1::encodeDER($value, $map, ['iPAddress' => [static::class, 'encodeIP']]); } } } @@ -1729,34 +702,35 @@ function _mapOutExtensions(&$root, $path, $asn1) * * @param array $root (by reference) * @param string $path - * @param object $asn1 - * @access private */ - function _mapInAttributes(&$root, $path, $asn1) + private function mapInAttributes(&$root, $path) { - $attributes = &$this->_subArray($root, $path); + $attributes = &$this->subArray($root, $path); if (is_array($attributes)) { for ($i = 0; $i < count($attributes); $i++) { $id = $attributes[$i]['type']; /* $value contains the DER encoding of an ASN.1 value corresponding to the attribute type identified by type */ - $map = $this->_getMapping($id); + $map = $this->getMapping($id); if (is_array($attributes[$i]['value'])) { $values = &$attributes[$i]['value']; for ($j = 0; $j < count($values); $j++) { - $value = $asn1->encodeDER($values[$j], $this->AttributeValue); - $decoded = $asn1->decodeBER($value); + $value = ASN1::encodeDER($values[$j], Maps\AttributeValue::MAP); + $decoded = ASN1::decodeBER($value); if (!is_bool($map)) { - $mapped = $asn1->asn1map($decoded[0], $map); + if (!$decoded) { + continue; + } + $mapped = ASN1::asn1map($decoded[0], $map); if ($mapped !== false) { $values[$j] = $mapped; } - if ($id == 'pkcs-9-at-extensionRequest' && $this->_isSubArrayValid($values, $j)) { - $this->_mapInExtensions($values, $j, $asn1); + if ($id == 'pkcs-9-at-extensionRequest' && $this->isSubArrayValid($values, $j)) { + $this->mapInExtensions($values, $j); } } elseif ($map) { - $values[$j] = base64_encode($value); + $values[$j] = $value; } } } @@ -1770,12 +744,10 @@ function _mapInAttributes(&$root, $path, $asn1) * * @param array $root (by reference) * @param string $path - * @param object $asn1 - * @access private */ - function _mapOutAttributes(&$root, $path, $asn1) + private function mapOutAttributes(&$root, $path) { - $attributes = &$this->_subArray($root, $path); + $attributes = &$this->subArray($root, $path); if (is_array($attributes)) { $size = count($attributes); @@ -1783,23 +755,26 @@ function _mapOutAttributes(&$root, $path, $asn1) /* [value] contains the DER encoding of an ASN.1 value corresponding to the attribute type identified by type */ $id = $attributes[$i]['type']; - $map = $this->_getMapping($id); + $map = $this->getMapping($id); if ($map === false) { - user_error($id . ' is not a currently supported attribute', E_USER_NOTICE); + //user_error($id . ' is not a currently supported attribute', E_USER_NOTICE); unset($attributes[$i]); } elseif (is_array($attributes[$i]['value'])) { $values = &$attributes[$i]['value']; for ($j = 0; $j < count($values); $j++) { switch ($id) { case 'pkcs-9-at-extensionRequest': - $this->_mapOutExtensions($values, $j, $asn1); + $this->mapOutExtensions($values, $j); break; } if (!is_bool($map)) { - $temp = $asn1->encodeDER($values[$j], $map); - $decoded = $asn1->decodeBER($temp); - $values[$j] = $asn1->asn1map($decoded[0], $this->AttributeValue); + $temp = ASN1::encodeDER($values[$j], $map); + $decoded = ASN1::decodeBER($temp); + if (!$decoded) { + continue; + } + $values[$j] = ASN1::asn1map($decoded[0], Maps\AttributeValue::MAP); } } } @@ -1813,12 +788,10 @@ function _mapOutAttributes(&$root, $path, $asn1) * * @param array $root (by reference) * @param string $path - * @param object $asn1 - * @access private */ - function _mapInDNs(&$root, $path, $asn1) + private function mapInDNs(array &$root, $path) { - $dns = &$this->_subArray($root, $path); + $dns = &$this->subArray($root, $path); if (is_array($dns)) { for ($i = 0; $i < count($dns); $i++) { @@ -1826,10 +799,13 @@ function _mapInDNs(&$root, $path, $asn1) $type = $dns[$i][$j]['type']; $value = &$dns[$i][$j]['value']; if (is_object($value) && $value instanceof Element) { - $map = $this->_getMapping($type); + $map = $this->getMapping($type); if (!is_bool($map)) { - $decoded = $asn1->decodeBER($value); - $value = $asn1->asn1map($decoded[0], $map); + $decoded = ASN1::decodeBER($value); + if (!$decoded) { + continue; + } + $value = ASN1::asn1map($decoded[0], $map); } } } @@ -1843,12 +819,10 @@ function _mapInDNs(&$root, $path, $asn1) * * @param array $root (by reference) * @param string $path - * @param object $asn1 - * @access private */ - function _mapOutDNs(&$root, $path, $asn1) + private function mapOutDNs(array &$root, $path) { - $dns = &$this->_subArray($root, $path); + $dns = &$this->subArray($root, $path); if (is_array($dns)) { $size = count($dns); @@ -1860,9 +834,9 @@ function _mapOutDNs(&$root, $path, $asn1) continue; } - $map = $this->_getMapping($type); + $map = $this->getMapping($type); if (!is_bool($map)) { - $value = new Element($asn1->encodeDER($value, $map)); + $value = new Element(ASN1::encodeDER($value, $map)); } } } @@ -1873,60 +847,61 @@ function _mapOutDNs(&$root, $path, $asn1) * Associate an extension ID to an extension mapping * * @param string $extnId - * @access private * @return mixed */ - function _getMapping($extnId) + private function getMapping($extnId) { - if (!is_string($extnId)) { // eg. if it's a \phpseclib\File\ASN1\Element object + if (!is_string($extnId)) { // eg. if it's a \phpseclib3\File\ASN1\Element object return true; } + if (isset(self::$extensions[$extnId])) { + return self::$extensions[$extnId]; + } + switch ($extnId) { case 'id-ce-keyUsage': - return $this->KeyUsage; + return Maps\KeyUsage::MAP; case 'id-ce-basicConstraints': - return $this->BasicConstraints; + return Maps\BasicConstraints::MAP; case 'id-ce-subjectKeyIdentifier': - return $this->KeyIdentifier; + return Maps\KeyIdentifier::MAP; case 'id-ce-cRLDistributionPoints': - return $this->CRLDistributionPoints; + return Maps\CRLDistributionPoints::MAP; case 'id-ce-authorityKeyIdentifier': - return $this->AuthorityKeyIdentifier; + return Maps\AuthorityKeyIdentifier::MAP; case 'id-ce-certificatePolicies': - return $this->CertificatePolicies; + return Maps\CertificatePolicies::MAP; case 'id-ce-extKeyUsage': - return $this->ExtKeyUsageSyntax; + return Maps\ExtKeyUsageSyntax::MAP; case 'id-pe-authorityInfoAccess': - return $this->AuthorityInfoAccessSyntax; - case 'id-pe-subjectInfoAccess': - return $this->SubjectInfoAccessSyntax; + return Maps\AuthorityInfoAccessSyntax::MAP; case 'id-ce-subjectAltName': - return $this->SubjectAltName; + return Maps\SubjectAltName::MAP; case 'id-ce-subjectDirectoryAttributes': - return $this->SubjectDirectoryAttributes; + return Maps\SubjectDirectoryAttributes::MAP; case 'id-ce-privateKeyUsagePeriod': - return $this->PrivateKeyUsagePeriod; + return Maps\PrivateKeyUsagePeriod::MAP; case 'id-ce-issuerAltName': - return $this->IssuerAltName; + return Maps\IssuerAltName::MAP; case 'id-ce-policyMappings': - return $this->PolicyMappings; + return Maps\PolicyMappings::MAP; case 'id-ce-nameConstraints': - return $this->NameConstraints; + return Maps\NameConstraints::MAP; case 'netscape-cert-type': - return $this->netscape_cert_type; + return Maps\netscape_cert_type::MAP; case 'netscape-comment': - return $this->netscape_comment; + return Maps\netscape_comment::MAP; case 'netscape-ca-policy-url': - return $this->netscape_ca_policy_url; + return Maps\netscape_ca_policy_url::MAP; // since id-qt-cps isn't a constructed type it will have already been decoded as a string by the time it gets // back around to asn1map() and we don't want it decoded again. //case 'id-qt-cps': - // return $this->CPSuri; + // return Maps\CPSuri::MAP; case 'id-qt-unotice': - return $this->UserNotice; + return Maps\UserNotice::MAP; // the following OIDs are unsupported but we don't want them to give notices when calling saveX509(). case 'id-pe-logotype': // http://www.ietf.org/rfc/rfc3709.txt @@ -1947,31 +922,31 @@ function _getMapping($extnId) // CSR attributes case 'pkcs-9-at-unstructuredName': - return $this->PKCS9String; + return Maps\PKCS9String::MAP; case 'pkcs-9-at-challengePassword': - return $this->DirectoryString; + return Maps\DirectoryString::MAP; case 'pkcs-9-at-extensionRequest': - return $this->Extensions; + return Maps\Extensions::MAP; // CRL extensions. case 'id-ce-cRLNumber': - return $this->CRLNumber; + return Maps\CRLNumber::MAP; case 'id-ce-deltaCRLIndicator': - return $this->CRLNumber; + return Maps\CRLNumber::MAP; case 'id-ce-issuingDistributionPoint': - return $this->IssuingDistributionPoint; + return Maps\IssuingDistributionPoint::MAP; case 'id-ce-freshestCRL': - return $this->CRLDistributionPoints; + return Maps\CRLDistributionPoints::MAP; case 'id-ce-cRLReasons': - return $this->CRLReason; + return Maps\CRLReason::MAP; case 'id-ce-invalidityDate': - return $this->InvalidityDate; + return Maps\InvalidityDate::MAP; case 'id-ce-certificateIssuer': - return $this->CertificateIssuer; + return Maps\CertificateIssuer::MAP; case 'id-ce-holdInstructionCode': - return $this->HoldInstructionCode; + return Maps\HoldInstructionCode::MAP; case 'id-at-postalAddress': - return $this->PostalAddress; + return Maps\PostalAddress::MAP; } return false; @@ -1981,10 +956,9 @@ function _getMapping($extnId) * Load an X.509 certificate as a certificate authority * * @param string $cert - * @access public * @return bool */ - function loadCA($cert) + public function loadCA($cert) { $olddn = $this->dn; $oldcert = $this->currentCert; @@ -2048,10 +1022,9 @@ function loadCA($cert) * not bar.foo.a.com. f*.com matches foo.com but not bar.com. * * @param string $url - * @access public * @return bool */ - function validateURL($url) + public function validateURL($url) { if (!is_array($this->currentCert) || !isset($this->currentCert['tbsCertificate'])) { return false; @@ -2065,7 +1038,7 @@ function validateURL($url) if ($names = $this->getExtension('id-ce-subjectAltName')) { foreach ($names as $name) { foreach ($name as $key => $value) { - $value = str_replace(array('.', '*'), array('\.', '[^.]*'), $value); + $value = str_replace(['.', '*'], ['\.', '[^.]*'], $value); switch ($key) { case 'dNSName': /* From RFC2818 "HTTP over TLS": @@ -2095,8 +1068,8 @@ function validateURL($url) } if ($value = $this->getDNProp('id-at-commonName')) { - $value = str_replace(array('.', '*'), array('\.', '[^.]*'), $value[0]); - return preg_match('#^' . $value . '$#', $components['host']); + $value = str_replace(['.', '*'], ['\.', '[^.]*'], $value[0]); + return preg_match('#^' . $value . '$#', $components['host']) === 1; } return false; @@ -2107,17 +1080,17 @@ function validateURL($url) * * If $date isn't defined it is assumed to be the current date. * - * @param \DateTime|string $date optional - * @access public + * @param \DateTimeInterface|string $date optional + * @return bool */ - function validateDate($date = null) + public function validateDate($date = null) { if (!is_array($this->currentCert) || !isset($this->currentCert['tbsCertificate'])) { return false; } if (!isset($date)) { - $date = new DateTime(null, new DateTimeZone(@date_default_timezone_get())); + $date = new \DateTimeImmutable('now', new \DateTimeZone(@date_default_timezone_get())); } $notBefore = $this->currentCert['tbsCertificate']['validity']['notBefore']; @@ -2127,29 +1100,22 @@ function validateDate($date = null) $notAfter = isset($notAfter['generalTime']) ? $notAfter['generalTime'] : $notAfter['utcTime']; if (is_string($date)) { - $date = new DateTime($date, new DateTimeZone(@date_default_timezone_get())); + $date = new \DateTimeImmutable($date, new \DateTimeZone(@date_default_timezone_get())); } - $notBefore = new DateTime($notBefore, new DateTimeZone(@date_default_timezone_get())); - $notAfter = new DateTime($notAfter, new DateTimeZone(@date_default_timezone_get())); - - switch (true) { - case $date < $notBefore: - case $date > $notAfter: - return false; - } + $notBefore = new \DateTimeImmutable($notBefore, new \DateTimeZone(@date_default_timezone_get())); + $notAfter = new \DateTimeImmutable($notAfter, new \DateTimeZone(@date_default_timezone_get())); - return true; + return $date >= $notBefore && $date <= $notAfter; } /** * Fetches a URL * * @param string $url - * @access private * @return bool|string */ - static function _fetchURL($url) + private static function fetchURL($url) { if (self::$disable_url_fetch) { return false; @@ -2163,7 +1129,11 @@ static function _fetchURL($url) if (!$fsock) { return false; } - fputs($fsock, "GET $parts[path] HTTP/1.0\r\n"); + $path = $parts['path']; + if (isset($parts['query'])) { + $path .= '?' . $parts['query']; + } + fputs($fsock, "GET $path HTTP/1.0\r\n"); fputs($fsock, "Host: $parts[host]\r\n\r\n"); $line = fgets($fsock, 1024); if (strlen($line) < 3) { @@ -2183,7 +1153,7 @@ static function _fetchURL($url) if ($temp === false) { return false; } - $data.= $temp; + $data .= $temp; } break; @@ -2202,10 +1172,9 @@ static function _fetchURL($url) * * @param bool $caonly * @param int $count - * @access private * @return bool */ - function _testForIntermediate($caonly, $count) + private function testForIntermediate($caonly, $count) { $opts = $this->getExtension('id-pe-authorityInfoAccess'); if (!is_array($opts)) { @@ -2227,7 +1196,7 @@ function _testForIntermediate($caonly, $count) return false; } - $cert = static::_fetchURL($url); + $cert = static::fetchURL($url); if (!is_string($cert)) { return false; } @@ -2247,7 +1216,7 @@ function _testForIntermediate($caonly, $count) return false; } - if (!$parent->_validateSignatureCountable($caonly, ++$count)) { + if (!$parent->validateSignatureCountable($caonly, ++$count)) { return false; } @@ -2269,12 +1238,11 @@ function _testForIntermediate($caonly, $count) * The behavior of this function is inspired by {@link http://php.net/openssl-verify openssl_verify}. * * @param bool $caonly optional - * @access public * @return mixed */ - function validateSignature($caonly = true) + public function validateSignature($caonly = true) { - return $this->_validateSignatureCountable($caonly, 0); + return $this->validateSignatureCountable($caonly, 0); } /** @@ -2284,10 +1252,9 @@ function validateSignature($caonly = true) * * @param bool $caonly * @param int $count - * @access private * @return mixed */ - function _validateSignatureCountable($caonly, $count) + private function validateSignatureCountable($caonly, $count) { if (!is_array($this->currentCert) || !isset($this->signatureSubject)) { return null; @@ -2342,32 +1309,32 @@ function _validateSignatureCountable($caonly, $count) } } if (count($this->CAs) == $i && $caonly) { - return $this->_testForIntermediate($caonly, $count) && $this->validateSignature($caonly); + return $this->testForIntermediate($caonly, $count) && $this->validateSignature($caonly); } } elseif (!isset($signingCert) || $caonly) { - return $this->_testForIntermediate($caonly, $count) && $this->validateSignature($caonly); + return $this->testForIntermediate($caonly, $count) && $this->validateSignature($caonly); } - return $this->_validateSignature( + return $this->validateSignatureHelper( $signingCert['tbsCertificate']['subjectPublicKeyInfo']['algorithm']['algorithm'], $signingCert['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey'], $this->currentCert['signatureAlgorithm']['algorithm'], - substr(base64_decode($this->currentCert['signature']), 1), + substr($this->currentCert['signature'], 1), $this->signatureSubject ); case isset($this->currentCert['certificationRequestInfo']): - return $this->_validateSignature( + return $this->validateSignatureHelper( $this->currentCert['certificationRequestInfo']['subjectPKInfo']['algorithm']['algorithm'], $this->currentCert['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey'], $this->currentCert['signatureAlgorithm']['algorithm'], - substr(base64_decode($this->currentCert['signature']), 1), + substr($this->currentCert['signature'], 1), $this->signatureSubject ); case isset($this->currentCert['publicKeyAndChallenge']): - return $this->_validateSignature( + return $this->validateSignatureHelper( $this->currentCert['publicKeyAndChallenge']['spki']['algorithm']['algorithm'], $this->currentCert['publicKeyAndChallenge']['spki']['subjectPublicKey'], $this->currentCert['signatureAlgorithm']['algorithm'], - substr(base64_decode($this->currentCert['signature']), 1), + substr($this->currentCert['signature'], 1), $this->signatureSubject ); case isset($this->currentCert['tbsCertList']): @@ -2395,11 +1362,11 @@ function _validateSignatureCountable($caonly, $count) if (!isset($signingCert)) { return false; } - return $this->_validateSignature( + return $this->validateSignatureHelper( $signingCert['tbsCertificate']['subjectPublicKeyInfo']['algorithm']['algorithm'], $signingCert['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey'], $this->currentCert['signatureAlgorithm']['algorithm'], - substr(base64_decode($this->currentCert['signature']), 1), + substr($this->currentCert['signature'], 1), $this->signatureSubject ); default: @@ -2410,23 +1377,25 @@ function _validateSignatureCountable($caonly, $count) /** * Validates a signature * - * Returns true if the signature is verified, false if it is not correct or null on error + * Returns true if the signature is verified and false if it is not correct. + * If the algorithms are unsupposed an exception is thrown. * * @param string $publicKeyAlgorithm * @param string $publicKey * @param string $signatureAlgorithm * @param string $signature * @param string $signatureSubject - * @access private - * @return int + * @throws \phpseclib3\Exception\UnsupportedAlgorithmException if the algorithm is unsupported + * @return bool */ - function _validateSignature($publicKeyAlgorithm, $publicKey, $signatureAlgorithm, $signature, $signatureSubject) + private function validateSignatureHelper($publicKeyAlgorithm, $publicKey, $signatureAlgorithm, $signature, $signatureSubject) { switch ($publicKeyAlgorithm) { + case 'id-RSASSA-PSS': + $key = RSA::loadFormat('PSS', $publicKey); + break; case 'rsaEncryption': - $rsa = new RSA(); - $rsa->loadKey($publicKey); - + $key = RSA::loadFormat('PKCS8', $publicKey); switch ($signatureAlgorithm) { case 'md2WithRSAEncryption': case 'md5WithRSAEncryption': @@ -2435,21 +1404,51 @@ function _validateSignature($publicKeyAlgorithm, $publicKey, $signatureAlgorithm case 'sha256WithRSAEncryption': case 'sha384WithRSAEncryption': case 'sha512WithRSAEncryption': - $rsa->setHash(preg_replace('#WithRSAEncryption$#', '', $signatureAlgorithm)); - $rsa->setSignatureMode(RSA::SIGNATURE_PKCS1); - if (!@$rsa->verify($signatureSubject, $signature)) { - return false; - } + $key = $key + ->withHash(preg_replace('#WithRSAEncryption$#', '', $signatureAlgorithm)) + ->withPadding(RSA::SIGNATURE_PKCS1); + break; + default: + throw new UnsupportedAlgorithmException('Signature algorithm unsupported'); + } + break; + case 'id-Ed25519': + case 'id-Ed448': + $key = EC::loadFormat('PKCS8', $publicKey); + break; + case 'id-ecPublicKey': + $key = EC::loadFormat('PKCS8', $publicKey); + switch ($signatureAlgorithm) { + case 'ecdsa-with-SHA1': + case 'ecdsa-with-SHA224': + case 'ecdsa-with-SHA256': + case 'ecdsa-with-SHA384': + case 'ecdsa-with-SHA512': + $key = $key + ->withHash(preg_replace('#^ecdsa-with-#', '', strtolower($signatureAlgorithm))); + break; + default: + throw new UnsupportedAlgorithmException('Signature algorithm unsupported'); + } + break; + case 'id-dsa': + $key = DSA::loadFormat('PKCS8', $publicKey); + switch ($signatureAlgorithm) { + case 'id-dsa-with-sha1': + case 'id-dsa-with-sha224': + case 'id-dsa-with-sha256': + $key = $key + ->withHash(preg_replace('#^id-dsa-with-#', '', strtolower($signatureAlgorithm))); break; default: - return null; + throw new UnsupportedAlgorithmException('Signature algorithm unsupported'); } break; default: - return null; + throw new UnsupportedAlgorithmException('Public key algorithm unsupported'); } - return true; + return $key->verify($signatureSubject, $signature); } /** @@ -2460,9 +1459,8 @@ function _validateSignature($publicKeyAlgorithm, $publicKey, $signatureAlgorithm * that we set a recursion limit. A negative number means that there is no recursion limit. * * @param int $count - * @access public */ - static function setRecurLimit($count) + public static function setRecurLimit($count) { self::$recur_limit = $count; } @@ -2470,9 +1468,8 @@ static function setRecurLimit($count) /** * Prevents URIs from being automatically retrieved * - * @access public */ - static function disableURLFetch() + public static function disableURLFetch() { self::$disable_url_fetch = true; } @@ -2480,51 +1477,23 @@ static function disableURLFetch() /** * Allows URIs to be automatically retrieved * - * @access public */ - static function enableURLFetch() + public static function enableURLFetch() { self::$disable_url_fetch = false; } - /** - * Reformat public keys - * - * Reformats a public key to a format supported by phpseclib (if applicable) - * - * @param string $algorithm - * @param string $key - * @access private - * @return string - */ - function _reformatKey($algorithm, $key) - { - switch ($algorithm) { - case 'rsaEncryption': - return - "-----BEGIN RSA PUBLIC KEY-----\r\n" . - // subjectPublicKey is stored as a bit string in X.509 certs. the first byte of a bit string represents how many bits - // in the last byte should be ignored. the following only supports non-zero stuff but as none of the X.509 certs Firefox - // uses as a cert authority actually use a non-zero bit I think it's safe to assume that none do. - chunk_split(base64_encode(substr(base64_decode($key), 1)), 64) . - '-----END RSA PUBLIC KEY-----'; - default: - return $key; - } - } - /** * Decodes an IP address * * Takes in a base64 encoded "blob" and returns a human readable IP address * * @param string $ip - * @access private * @return string */ - function _decodeIP($ip) + public static function decodeIP($ip) { - return inet_ntop(base64_decode($ip)); + return inet_ntop($ip); } /** @@ -2533,16 +1502,14 @@ function _decodeIP($ip) * Takes in a base64 encoded "blob" and returns a human readable IP address / mask * * @param string $ip - * @access private * @return array */ - function _decodeNameConstraintIP($ip) + public static function decodeNameConstraintIP($ip) { - $ip = base64_decode($ip); $size = strlen($ip) >> 1; $mask = substr($ip, $size); $ip = substr($ip, 0, $size); - return array(inet_ntop($ip), inet_ntop($mask)); + return [inet_ntop($ip), inet_ntop($mask)]; } /** @@ -2551,24 +1518,22 @@ function _decodeNameConstraintIP($ip) * Takes a human readable IP address into a base64-encoded "blob" * * @param string|array $ip - * @access private * @return string */ - function _encodeIP($ip) + public static function encodeIP($ip) { return is_string($ip) ? - base64_encode(inet_pton($ip)) : - base64_encode(inet_pton($ip[0]) . inet_pton($ip[1])); + inet_pton($ip) : + inet_pton($ip[0]) . inet_pton($ip[1]); } /** * "Normalizes" a Distinguished Name property * * @param string $propName - * @access private * @return mixed */ - function _translateDNProp($propName) + private function translateDNProp($propName) { switch (strtolower($propName)) { case 'id-at-countryname': @@ -2659,29 +1624,28 @@ function _translateDNProp($propName) * @param string $propName * @param mixed $propValue * @param string $type optional - * @access public * @return bool */ - function setDNProp($propName, $propValue, $type = 'utf8String') + public function setDNProp($propName, $propValue, $type = 'utf8String') { if (empty($this->dn)) { - $this->dn = array('rdnSequence' => array()); + $this->dn = ['rdnSequence' => []]; } - if (($propName = $this->_translateDNProp($propName)) === false) { + if (($propName = $this->translateDNProp($propName)) === false) { return false; } foreach ((array) $propValue as $v) { if (!is_array($v) && isset($type)) { - $v = array($type => $v); + $v = [$type => $v]; } - $this->dn['rdnSequence'][] = array( - array( + $this->dn['rdnSequence'][] = [ + [ 'type' => $propName, - 'value'=> $v - ) - ); + 'value' => $v + ] + ]; } return true; @@ -2691,15 +1655,14 @@ function setDNProp($propName, $propValue, $type = 'utf8String') * Remove Distinguished Name properties * * @param string $propName - * @access public */ - function removeDNProp($propName) + public function removeDNProp($propName) { if (empty($this->dn)) { return; } - if (($propName = $this->_translateDNProp($propName)) === false) { + if (($propName = $this->translateDNProp($propName)) === false) { return; } @@ -2725,9 +1688,8 @@ function removeDNProp($propName) * @param array $dn optional * @param bool $withType optional * @return mixed - * @access public */ - function getDNProp($propName, $dn = null, $withType = false) + public function getDNProp($propName, array $dn = null, $withType = false) { if (!isset($dn)) { $dn = $this->dn; @@ -2737,27 +1699,25 @@ function getDNProp($propName, $dn = null, $withType = false) return false; } - if (($propName = $this->_translateDNProp($propName)) === false) { + if (($propName = $this->translateDNProp($propName)) === false) { return false; } - $asn1 = new ASN1(); - $asn1->loadOIDs($this->oids); - $filters = array(); - $filters['value'] = array('type' => ASN1::TYPE_UTF8_STRING); - $asn1->loadFilters($filters); - $this->_mapOutDNs($dn, 'rdnSequence', $asn1); + $filters = []; + $filters['value'] = ['type' => ASN1::TYPE_UTF8_STRING]; + ASN1::setFilters($filters); + $this->mapOutDNs($dn, 'rdnSequence'); $dn = $dn['rdnSequence']; - $result = array(); + $result = []; for ($i = 0; $i < count($dn); $i++) { if ($dn[$i][0]['type'] == $propName) { $v = $dn[$i][0]['value']; if (!$withType) { if (is_array($v)) { foreach ($v as $type => $s) { - $type = array_search($type, $asn1->ANYmap, true); - if ($type !== false && isset($asn1->stringTypeSize[$type])) { - $s = $asn1->convert($s, $type); + $type = array_search($type, ASN1::ANY_MAP); + if ($type !== false && array_key_exists($type, ASN1::STRING_TYPE_SIZE)) { + $s = ASN1::convert($s, $type); if ($s !== false) { $v = $s; break; @@ -2768,10 +1728,13 @@ function getDNProp($propName, $dn = null, $withType = false) $v = array_pop($v); // Always strip data type. } } elseif (is_object($v) && $v instanceof Element) { - $map = $this->_getMapping($propName); + $map = $this->getMapping($propName); if (!is_bool($map)) { - $decoded = $asn1->decodeBER($v); - $v = $asn1->asn1map($decoded[0], $map); + $decoded = ASN1::decodeBER($v); + if (!$decoded) { + return false; + } + $v = ASN1::asn1map($decoded[0], $map); } } } @@ -2788,10 +1751,9 @@ function getDNProp($propName, $dn = null, $withType = false) * @param mixed $dn * @param bool $merge optional * @param string $type optional - * @access public * @return bool */ - function setDN($dn, $merge = false, $type = 'utf8String') + public function setDN($dn, $merge = false, $type = 'utf8String') { if (!$merge) { $this->dn = null; @@ -2814,7 +1776,7 @@ function setDN($dn, $merge = false, $type = 'utf8String') // handles everything else $results = preg_split('#((?:^|, *|/)(?:C=|O=|OU=|CN=|L=|ST=|SN=|postalCode=|streetAddress=|emailAddress=|serialNumber=|organizationalUnitName=|title=|description=|role=|x500UniqueIdentifier=|postalAddress=))#', $dn, -1, PREG_SPLIT_DELIM_CAPTURE); - for ($i = 1; $i < count($results); $i+=2) { + for ($i = 1; $i < count($results); $i += 2) { $prop = trim($results[$i], ', =/'); $value = $results[$i + 1]; if (!$this->setDNProp($prop, $value, $type)) { @@ -2830,10 +1792,9 @@ function setDN($dn, $merge = false, $type = 'utf8String') * * @param mixed $format optional * @param array $dn optional - * @access public - * @return bool + * @return array|bool|string */ - function getDN($format = self::DN_ARRAY, $dn = null) + public function getDN($format = self::DN_ARRAY, array $dn = null) { if (!isset($dn)) { $dn = isset($this->currentCert['tbsCertList']) ? $this->currentCert['tbsCertList']['issuer'] : $this->dn; @@ -2843,32 +1804,28 @@ function getDN($format = self::DN_ARRAY, $dn = null) case self::DN_ARRAY: return $dn; case self::DN_ASN1: - $asn1 = new ASN1(); - $asn1->loadOIDs($this->oids); - $filters = array(); - $filters['rdnSequence']['value'] = array('type' => ASN1::TYPE_UTF8_STRING); - $asn1->loadFilters($filters); - $this->_mapOutDNs($dn, 'rdnSequence', $asn1); - return $asn1->encodeDER($dn, $this->Name); + $filters = []; + $filters['rdnSequence']['value'] = ['type' => ASN1::TYPE_UTF8_STRING]; + ASN1::setFilters($filters); + $this->mapOutDNs($dn, 'rdnSequence'); + return ASN1::encodeDER($dn, Maps\Name::MAP); case self::DN_CANON: // No SEQUENCE around RDNs and all string values normalized as // trimmed lowercase UTF-8 with all spacing as one blank. // constructed RDNs will not be canonicalized - $asn1 = new ASN1(); - $asn1->loadOIDs($this->oids); - $filters = array(); - $filters['value'] = array('type' => ASN1::TYPE_UTF8_STRING); - $asn1->loadFilters($filters); + $filters = []; + $filters['value'] = ['type' => ASN1::TYPE_UTF8_STRING]; + ASN1::setFilters($filters); $result = ''; - $this->_mapOutDNs($dn, 'rdnSequence', $asn1); + $this->mapOutDNs($dn, 'rdnSequence'); foreach ($dn['rdnSequence'] as $rdn) { foreach ($rdn as $i => $attr) { $attr = &$rdn[$i]; if (is_array($attr['value'])) { foreach ($attr['value'] as $type => $v) { - $type = array_search($type, $asn1->ANYmap, true); - if ($type !== false && isset($asn1->stringTypeSize[$type])) { - $v = $asn1->convert($v, $type); + $type = array_search($type, ASN1::ANY_MAP, true); + if ($type !== false && array_key_exists($type, ASN1::STRING_TYPE_SIZE)) { + $v = ASN1::convert($v, $type); if ($v !== false) { $v = preg_replace('/\s+/', ' ', $v); $attr['value'] = strtolower(trim($v)); @@ -2878,7 +1835,7 @@ function getDN($format = self::DN_ARRAY, $dn = null) } } } - $result .= $asn1->encodeDER($rdn, $this->RelativeDistinguishedName); + $result .= ASN1::encodeDER($rdn, Maps\RelativeDistinguishedName::MAP); } return $result; case self::DN_HASH: @@ -2886,20 +1843,18 @@ function getDN($format = self::DN_ARRAY, $dn = null) $hash = new Hash('sha1'); $hash = $hash->hash($dn); extract(unpack('Vhash', $hash)); - return strtolower(bin2hex(pack('N', $hash))); + return strtolower(Strings::bin2hex(pack('N', $hash))); } // Default is to return a string. $start = true; $output = ''; - $result = array(); - $asn1 = new ASN1(); - $asn1->loadOIDs($this->oids); - $filters = array(); - $filters['rdnSequence']['value'] = array('type' => ASN1::TYPE_UTF8_STRING); - $asn1->loadFilters($filters); - $this->_mapOutDNs($dn, 'rdnSequence', $asn1); + $result = []; + $filters = []; + $filters['rdnSequence']['value'] = ['type' => ASN1::TYPE_UTF8_STRING]; + ASN1::setFilters($filters); + $this->mapOutDNs($dn, 'rdnSequence'); foreach ($dn['rdnSequence'] as $field) { $prop = $field[0]['type']; @@ -2942,13 +1897,13 @@ function getDN($format = self::DN_ARRAY, $dn = null) } if (!$start) { - $output.= $delim; + $output .= $delim; } if (is_array($value)) { foreach ($value as $type => $v) { - $type = array_search($type, $asn1->ANYmap, true); - if ($type !== false && isset($asn1->stringTypeSize[$type])) { - $v = $asn1->convert($v, $type); + $type = array_search($type, ASN1::ANY_MAP, true); + if ($type !== false && array_key_exists($type, ASN1::STRING_TYPE_SIZE)) { + $v = ASN1::convert($v, $type); if ($v !== false) { $value = $v; break; @@ -2960,13 +1915,13 @@ function getDN($format = self::DN_ARRAY, $dn = null) } } elseif (is_object($value) && $value instanceof Element) { $callback = function ($x) { - return "\x" . bin2hex($x[0]); + return '\x' . bin2hex($x[0]); }; $value = strtoupper(preg_replace_callback('#[^\x20-\x7E]#', $callback, $value->element)); } - $output.= $desc . '=' . $value; + $output .= $desc . '=' . $value; $result[$desc] = isset($result[$desc]) ? - array_merge((array) $result[$desc], array($value)) : + array_merge((array) $result[$desc], [$value]) : $value; $start = false; } @@ -2978,10 +1933,9 @@ function getDN($format = self::DN_ARRAY, $dn = null) * Get the Distinguished Name for a certificate/crl issuer * * @param int $format optional - * @access public * @return mixed */ - function getIssuerDN($format = self::DN_ARRAY) + public function getIssuerDN($format = self::DN_ARRAY) { switch (true) { case !isset($this->currentCert) || !is_array($this->currentCert): @@ -3000,10 +1954,9 @@ function getIssuerDN($format = self::DN_ARRAY) * Alias of getDN() * * @param int $format optional - * @access public * @return mixed */ - function getSubjectDN($format = self::DN_ARRAY) + public function getSubjectDN($format = self::DN_ARRAY) { switch (true) { case !empty($this->dn): @@ -3024,10 +1977,9 @@ function getSubjectDN($format = self::DN_ARRAY) * * @param string $propName * @param bool $withType optional - * @access public * @return mixed */ - function getIssuerDNProp($propName, $withType = false) + public function getIssuerDNProp($propName, $withType = false) { switch (true) { case !isset($this->currentCert) || !is_array($this->currentCert): @@ -3046,10 +1998,9 @@ function getIssuerDNProp($propName, $withType = false) * * @param string $propName * @param bool $withType optional - * @access public * @return mixed */ - function getSubjectDNProp($propName, $withType = false) + public function getSubjectDNProp($propName, $withType = false) { switch (true) { case !empty($this->dn): @@ -3068,12 +2019,11 @@ function getSubjectDNProp($propName, $withType = false) /** * Get the certificate chain for the current cert * - * @access public * @return mixed */ - function getChain() + public function getChain() { - $chain = array($this->currentCert); + $chain = [$this->currentCert]; if (!is_array($this->currentCert) || !isset($this->currentCert['tbsCertificate'])) { return false; @@ -3110,30 +2060,37 @@ function getChain() return $chain; } + /** + * Returns the current cert + * + * @return array|bool + */ + public function &getCurrentCert() + { + return $this->currentCert; + } + /** * Set public key * - * Key needs to be a \phpseclib\Crypt\RSA object + * Key needs to be a \phpseclib3\Crypt\RSA object * - * @param object $key - * @access public - * @return bool + * @param PublicKey $key + * @return void */ - function setPublicKey($key) + public function setPublicKey(PublicKey $key) { - $key->setPublicKey(); $this->publicKey = $key; } /** * Set private key * - * Key needs to be a \phpseclib\Crypt\RSA object + * Key needs to be a \phpseclib3\Crypt\RSA object * - * @param object $key - * @access public + * @param PrivateKey $key */ - function setPrivateKey($key) + public function setPrivateKey(PrivateKey $key) { $this->privateKey = $key; } @@ -3144,9 +2101,8 @@ function setPrivateKey($key) * Used for SPKAC CSR's * * @param string $challenge - * @access public */ - function setChallenge($challenge) + public function setChallenge($challenge) { $this->challenge = $challenge; } @@ -3154,20 +2110,24 @@ function setChallenge($challenge) /** * Gets the public key * - * Returns a \phpseclib\Crypt\RSA object or a false. + * Returns a \phpseclib3\Crypt\RSA object or a false. * - * @access public * @return mixed */ - function getPublicKey() + public function getPublicKey() { if (isset($this->publicKey)) { return $this->publicKey; } if (isset($this->currentCert) && is_array($this->currentCert)) { - foreach (array('tbsCertificate/subjectPublicKeyInfo', 'certificationRequestInfo/subjectPKInfo') as $path) { - $keyinfo = $this->_subArray($this->currentCert, $path); + $paths = [ + 'tbsCertificate/subjectPublicKeyInfo', + 'certificationRequestInfo/subjectPKInfo', + 'publicKeyAndChallenge/spki' + ]; + foreach ($paths as $path) { + $keyinfo = $this->subArray($this->currentCert, $path); if (!empty($keyinfo)) { break; } @@ -3180,27 +2140,29 @@ function getPublicKey() $key = $keyinfo['subjectPublicKey']; switch ($keyinfo['algorithm']['algorithm']) { + case 'id-RSASSA-PSS': + return RSA::loadFormat('PSS', $key); case 'rsaEncryption': - $publicKey = new RSA(); - $publicKey->loadKey($key); - $publicKey->setPublicKey(); - break; - default: - return false; + return RSA::loadFormat('PKCS8', $key)->withPadding(RSA::SIGNATURE_PKCS1); + case 'id-ecPublicKey': + case 'id-Ed25519': + case 'id-Ed448': + return EC::loadFormat('PKCS8', $key); + case 'id-dsa': + return DSA::loadFormat('PKCS8', $key); } - return $publicKey; + return false; } /** * Load a Certificate Signing Request * - * @param string|array $csr + * @param string $csr * @param int $mode - * @access public * @return mixed */ - function loadCSR($csr, $mode = self::FORMAT_AUTO_DETECT) + public function loadCSR($csr, $mode = self::FORMAT_AUTO_DETECT) { if (is_array($csr) && isset($csr['certificationRequestInfo'])) { unset($this->currentCert); @@ -3217,10 +2179,8 @@ function loadCSR($csr, $mode = self::FORMAT_AUTO_DETECT) // see http://tools.ietf.org/html/rfc2986 - $asn1 = new ASN1(); - if ($mode != self::FORMAT_DER) { - $newcsr = $this->_extractBER($csr); + $newcsr = ASN1::extractBER($csr); if ($mode == self::FORMAT_PEM && $csr == $newcsr) { return false; } @@ -3233,44 +2193,39 @@ function loadCSR($csr, $mode = self::FORMAT_AUTO_DETECT) return false; } - $asn1->loadOIDs($this->oids); - $decoded = $asn1->decodeBER($csr); + $decoded = ASN1::decodeBER($csr); - if (empty($decoded)) { + if (!$decoded) { $this->currentCert = false; return false; } - $csr = $asn1->asn1map($decoded[0], $this->CertificationRequest); + $csr = ASN1::asn1map($decoded[0], Maps\CertificationRequest::MAP); if (!isset($csr) || $csr === false) { $this->currentCert = false; return false; } - $this->_mapInAttributes($csr, 'certificationRequestInfo/attributes', $asn1); - $this->_mapInDNs($csr, 'certificationRequestInfo/subject/rdnSequence', $asn1); + $this->mapInAttributes($csr, 'certificationRequestInfo/attributes'); + $this->mapInDNs($csr, 'certificationRequestInfo/subject/rdnSequence'); $this->dn = $csr['certificationRequestInfo']['subject']; $this->signatureSubject = substr($orig, $decoded[0]['content'][0]['start'], $decoded[0]['content'][0]['length']); - $algorithm = &$csr['certificationRequestInfo']['subjectPKInfo']['algorithm']['algorithm']; - $key = &$csr['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey']; - $key = $this->_reformatKey($algorithm, $key); - - switch ($algorithm) { - case 'rsaEncryption': - $this->publicKey = new RSA(); - $this->publicKey->loadKey($key); - $this->publicKey->setPublicKey(); - break; - default: - $this->publicKey = null; - } + $key = $csr['certificationRequestInfo']['subjectPKInfo']; + $key = ASN1::encodeDER($key, Maps\SubjectPublicKeyInfo::MAP); + $csr['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey'] = + "-----BEGIN PUBLIC KEY-----\r\n" . + chunk_split(base64_encode($key), 64) . + "-----END PUBLIC KEY-----"; $this->currentKeyIdentifier = null; $this->currentCert = $csr; + $this->publicKey = null; + $this->publicKey = $this->getPublicKey(); + return $csr; } @@ -3279,50 +2234,40 @@ function loadCSR($csr, $mode = self::FORMAT_AUTO_DETECT) * * @param array $csr * @param int $format optional - * @access public * @return string */ - function saveCSR($csr, $format = self::FORMAT_PEM) + public function saveCSR(array $csr, $format = self::FORMAT_PEM) { if (!is_array($csr) || !isset($csr['certificationRequestInfo'])) { return false; } switch (true) { - case !($algorithm = $this->_subArray($csr, 'certificationRequestInfo/subjectPKInfo/algorithm/algorithm')): + case !($algorithm = $this->subArray($csr, 'certificationRequestInfo/subjectPKInfo/algorithm/algorithm')): case is_object($csr['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey']): break; default: - switch ($algorithm) { - case 'rsaEncryption': - $csr['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey'] - = base64_encode("\0" . base64_decode(preg_replace('#-.+-|[\r\n]#', '', $csr['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey']))); - $csr['certificationRequestInfo']['subjectPKInfo']['algorithm']['parameters'] = null; - $csr['signatureAlgorithm']['parameters'] = null; - $csr['certificationRequestInfo']['signature']['parameters'] = null; - } + $csr['certificationRequestInfo']['subjectPKInfo'] = new Element( + base64_decode(preg_replace('#-.+-|[\r\n]#', '', $csr['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey'])) + ); } - $asn1 = new ASN1(); - - $asn1->loadOIDs($this->oids); - - $filters = array(); + $filters = []; $filters['certificationRequestInfo']['subject']['rdnSequence']['value'] - = array('type' => ASN1::TYPE_UTF8_STRING); + = ['type' => ASN1::TYPE_UTF8_STRING]; - $asn1->loadFilters($filters); + ASN1::setFilters($filters); - $this->_mapOutDNs($csr, 'certificationRequestInfo/subject/rdnSequence', $asn1); - $this->_mapOutAttributes($csr, 'certificationRequestInfo/attributes', $asn1); - $csr = $asn1->encodeDER($csr, $this->CertificationRequest); + $this->mapOutDNs($csr, 'certificationRequestInfo/subject/rdnSequence'); + $this->mapOutAttributes($csr, 'certificationRequestInfo/attributes'); + $csr = ASN1::encodeDER($csr, Maps\CertificationRequest::MAP); switch ($format) { case self::FORMAT_DER: return $csr; // case self::FORMAT_PEM: default: - return "-----BEGIN CERTIFICATE REQUEST-----\r\n" . chunk_split(base64_encode($csr), 64) . '-----END CERTIFICATE REQUEST-----'; + return "-----BEGIN CERTIFICATE REQUEST-----\r\n" . chunk_split(Strings::base64_encode($csr), 64) . '-----END CERTIFICATE REQUEST-----'; } } @@ -3333,11 +2278,10 @@ function saveCSR($csr, $format = self::FORMAT_PEM) * * https://developer.mozilla.org/en-US/docs/HTML/Element/keygen * - * @param string|array $spkac - * @access public + * @param string $spkac * @return mixed */ - function loadSPKAC($spkac) + public function loadSPKAC($spkac) { if (is_array($spkac) && isset($spkac['publicKeyAndChallenge'])) { unset($this->currentCert); @@ -3349,11 +2293,9 @@ function loadSPKAC($spkac) // see http://www.w3.org/html/wg/drafts/html/master/forms.html#signedpublickeyandchallenge - $asn1 = new ASN1(); - // OpenSSL produces SPKAC's that are preceded by the string SPKAC= $temp = preg_replace('#(?:SPKAC=)|[ \r\n\\\]#', '', $spkac); - $temp = preg_match('#^[a-zA-Z\d/+]*={0,2}$#', $temp) ? base64_decode($temp) : false; + $temp = preg_match('#^[a-zA-Z\d/+]*={0,2}$#', $temp) ? Strings::base64_decode($temp) : false; if ($temp != false) { $spkac = $temp; } @@ -3364,74 +2306,63 @@ function loadSPKAC($spkac) return false; } - $asn1->loadOIDs($this->oids); - $decoded = $asn1->decodeBER($spkac); + $decoded = ASN1::decodeBER($spkac); - if (empty($decoded)) { + if (!$decoded) { $this->currentCert = false; return false; } - $spkac = $asn1->asn1map($decoded[0], $this->SignedPublicKeyAndChallenge); + $spkac = ASN1::asn1map($decoded[0], Maps\SignedPublicKeyAndChallenge::MAP); - if (!isset($spkac) || $spkac === false) { + if (!isset($spkac) || !is_array($spkac)) { $this->currentCert = false; return false; } $this->signatureSubject = substr($orig, $decoded[0]['content'][0]['start'], $decoded[0]['content'][0]['length']); - $algorithm = &$spkac['publicKeyAndChallenge']['spki']['algorithm']['algorithm']; - $key = &$spkac['publicKeyAndChallenge']['spki']['subjectPublicKey']; - $key = $this->_reformatKey($algorithm, $key); - - switch ($algorithm) { - case 'rsaEncryption': - $this->publicKey = new RSA(); - $this->publicKey->loadKey($key); - $this->publicKey->setPublicKey(); - break; - default: - $this->publicKey = null; - } + $key = $spkac['publicKeyAndChallenge']['spki']; + $key = ASN1::encodeDER($key, Maps\SubjectPublicKeyInfo::MAP); + $spkac['publicKeyAndChallenge']['spki']['subjectPublicKey'] = + "-----BEGIN PUBLIC KEY-----\r\n" . + chunk_split(base64_encode($key), 64) . + "-----END PUBLIC KEY-----"; $this->currentKeyIdentifier = null; $this->currentCert = $spkac; + $this->publicKey = null; + $this->publicKey = $this->getPublicKey(); + return $spkac; } /** * Save a SPKAC CSR request * - * @param string|array $spkac + * @param array $spkac * @param int $format optional - * @access public * @return string */ - function saveSPKAC($spkac, $format = self::FORMAT_PEM) + public function saveSPKAC(array $spkac, $format = self::FORMAT_PEM) { if (!is_array($spkac) || !isset($spkac['publicKeyAndChallenge'])) { return false; } - $algorithm = $this->_subArray($spkac, 'publicKeyAndChallenge/spki/algorithm/algorithm'); + $algorithm = $this->subArray($spkac, 'publicKeyAndChallenge/spki/algorithm/algorithm'); switch (true) { case !$algorithm: case is_object($spkac['publicKeyAndChallenge']['spki']['subjectPublicKey']): break; default: - switch ($algorithm) { - case 'rsaEncryption': - $spkac['publicKeyAndChallenge']['spki']['subjectPublicKey'] - = base64_encode("\0" . base64_decode(preg_replace('#-.+-|[\r\n]#', '', $spkac['publicKeyAndChallenge']['spki']['subjectPublicKey']))); - } + $spkac['publicKeyAndChallenge']['spki'] = new Element( + base64_decode(preg_replace('#-.+-|[\r\n]#', '', $spkac['publicKeyAndChallenge']['spki']['subjectPublicKey'])) + ); } - $asn1 = new ASN1(); - - $asn1->loadOIDs($this->oids); - $spkac = $asn1->encodeDER($spkac, $this->SignedPublicKeyAndChallenge); + $spkac = ASN1::encodeDER($spkac, Maps\SignedPublicKeyAndChallenge::MAP); switch ($format) { case self::FORMAT_DER: @@ -3440,7 +2371,7 @@ function saveSPKAC($spkac, $format = self::FORMAT_PEM) default: // OpenSSL's implementation of SPKAC requires the SPKAC be preceded by SPKAC= and since there are pretty much // no other SPKAC decoders phpseclib will use that same format - return 'SPKAC=' . base64_encode($spkac); + return 'SPKAC=' . Strings::base64_encode($spkac); } } @@ -3449,10 +2380,9 @@ function saveSPKAC($spkac, $format = self::FORMAT_PEM) * * @param string $crl * @param int $mode - * @access public * @return mixed */ - function loadCRL($crl, $mode = self::FORMAT_AUTO_DETECT) + public function loadCRL($crl, $mode = self::FORMAT_AUTO_DETECT) { if (is_array($crl) && isset($crl['tbsCertList'])) { $this->currentCert = $crl; @@ -3460,10 +2390,8 @@ function loadCRL($crl, $mode = self::FORMAT_AUTO_DETECT) return $crl; } - $asn1 = new ASN1(); - if ($mode != self::FORMAT_DER) { - $newcrl = $this->_extractBER($crl); + $newcrl = ASN1::extractBER($crl); if ($mode == self::FORMAT_PEM && $crl == $newcrl) { return false; } @@ -3476,15 +2404,14 @@ function loadCRL($crl, $mode = self::FORMAT_AUTO_DETECT) return false; } - $asn1->loadOIDs($this->oids); - $decoded = $asn1->decodeBER($crl); + $decoded = ASN1::decodeBER($crl); - if (empty($decoded)) { + if (!$decoded) { $this->currentCert = false; return false; } - $crl = $asn1->asn1map($decoded[0], $this->CertificateList); + $crl = ASN1::asn1map($decoded[0], Maps\CertificateList::MAP); if (!isset($crl) || $crl === false) { $this->currentCert = false; return false; @@ -3492,17 +2419,17 @@ function loadCRL($crl, $mode = self::FORMAT_AUTO_DETECT) $this->signatureSubject = substr($orig, $decoded[0]['content'][0]['start'], $decoded[0]['content'][0]['length']); - $this->_mapInDNs($crl, 'tbsCertList/issuer/rdnSequence', $asn1); - if ($this->_isSubArrayValid($crl, 'tbsCertList/crlExtensions')) { - $this->_mapInExtensions($crl, 'tbsCertList/crlExtensions', $asn1); + $this->mapInDNs($crl, 'tbsCertList/issuer/rdnSequence'); + if ($this->isSubArrayValid($crl, 'tbsCertList/crlExtensions')) { + $this->mapInExtensions($crl, 'tbsCertList/crlExtensions'); } - if ($this->_isSubArrayValid($crl, 'tbsCertList/revokedCertificates')) { - $rclist_ref = &$this->_subArrayUnchecked($crl, 'tbsCertList/revokedCertificates'); + if ($this->isSubArrayValid($crl, 'tbsCertList/revokedCertificates')) { + $rclist_ref = &$this->subArrayUnchecked($crl, 'tbsCertList/revokedCertificates'); if ($rclist_ref) { $rclist = $crl['tbsCertList']['revokedCertificates']; foreach ($rclist as $i => $extension) { - if ($this->_isSubArrayValid($rclist, "$i/crlEntryExtensions", $asn1)) { - $this->_mapInExtensions($rclist_ref, "$i/crlEntryExtensions", $asn1); + if ($this->isSubArrayValid($rclist, "$i/crlEntryExtensions")) { + $this->mapInExtensions($rclist_ref, "$i/crlEntryExtensions"); } } } @@ -3519,56 +2446,51 @@ function loadCRL($crl, $mode = self::FORMAT_AUTO_DETECT) * * @param array $crl * @param int $format optional - * @access public * @return string */ - function saveCRL($crl, $format = self::FORMAT_PEM) + public function saveCRL(array $crl, $format = self::FORMAT_PEM) { if (!is_array($crl) || !isset($crl['tbsCertList'])) { return false; } - $asn1 = new ASN1(); - - $asn1->loadOIDs($this->oids); - - $filters = array(); + $filters = []; $filters['tbsCertList']['issuer']['rdnSequence']['value'] - = array('type' => ASN1::TYPE_UTF8_STRING); + = ['type' => ASN1::TYPE_UTF8_STRING]; $filters['tbsCertList']['signature']['parameters'] - = array('type' => ASN1::TYPE_UTF8_STRING); + = ['type' => ASN1::TYPE_UTF8_STRING]; $filters['signatureAlgorithm']['parameters'] - = array('type' => ASN1::TYPE_UTF8_STRING); + = ['type' => ASN1::TYPE_UTF8_STRING]; if (empty($crl['tbsCertList']['signature']['parameters'])) { $filters['tbsCertList']['signature']['parameters'] - = array('type' => ASN1::TYPE_NULL); + = ['type' => ASN1::TYPE_NULL]; } if (empty($crl['signatureAlgorithm']['parameters'])) { $filters['signatureAlgorithm']['parameters'] - = array('type' => ASN1::TYPE_NULL); + = ['type' => ASN1::TYPE_NULL]; } - $asn1->loadFilters($filters); + ASN1::setFilters($filters); - $this->_mapOutDNs($crl, 'tbsCertList/issuer/rdnSequence', $asn1); - $this->_mapOutExtensions($crl, 'tbsCertList/crlExtensions', $asn1); - $rclist = &$this->_subArray($crl, 'tbsCertList/revokedCertificates'); + $this->mapOutDNs($crl, 'tbsCertList/issuer/rdnSequence'); + $this->mapOutExtensions($crl, 'tbsCertList/crlExtensions'); + $rclist = &$this->subArray($crl, 'tbsCertList/revokedCertificates'); if (is_array($rclist)) { foreach ($rclist as $i => $extension) { - $this->_mapOutExtensions($rclist, "$i/crlEntryExtensions", $asn1); + $this->mapOutExtensions($rclist, "$i/crlEntryExtensions"); } } - $crl = $asn1->encodeDER($crl, $this->CertificateList); + $crl = ASN1::encodeDER($crl, Maps\CertificateList::MAP); switch ($format) { case self::FORMAT_DER: return $crl; // case self::FORMAT_PEM: default: - return "-----BEGIN X509 CRL-----\r\n" . chunk_split(base64_encode($crl), 64) . '-----END X509 CRL-----'; + return "-----BEGIN X509 CRL-----\r\n" . chunk_split(Strings::base64_encode($crl), 64) . '-----END X509 CRL-----'; } } @@ -3581,20 +2503,19 @@ function saveCRL($crl, $format = self::FORMAT_PEM) * by choosing utcTime iff year of date given is before 2050 and generalTime else. * * @param string $date in format date('D, d M Y H:i:s O') - * @access private - * @return array + * @return array|Element */ - function _timeField($date) + private function timeField($date) { if ($date instanceof Element) { return $date; } - $dateObj = new DateTime($date, new DateTimeZone('GMT')); + $dateObj = new \DateTimeImmutable($date, new \DateTimeZone('GMT')); $year = $dateObj->format('Y'); // the same way ASN1.php parses this if ($year < 2050) { - return array('utcTime' => $date); + return ['utcTime' => $date]; } else { - return array('generalTime' => $date); + return ['generalTime' => $date]; } } @@ -3605,35 +2526,42 @@ function _timeField($date) * $subject can be either an existing X.509 cert (if you want to resign it), * a CSR or something with the DN and public key explicitly set. * - * @param \phpseclib\File\X509 $issuer - * @param \phpseclib\File\X509 $subject - * @param string $signatureAlgorithm optional - * @access public * @return mixed */ - function sign($issuer, $subject, $signatureAlgorithm = 'sha1WithRSAEncryption') + public function sign(X509 $issuer, X509 $subject) { if (!is_object($issuer->privateKey) || empty($issuer->dn)) { return false; } - if (isset($subject->publicKey) && !($subjectPublicKey = $subject->_formatSubjectPublicKey())) { + if (isset($subject->publicKey) && !($subjectPublicKey = $subject->formatSubjectPublicKey())) { return false; } $currentCert = isset($this->currentCert) ? $this->currentCert : null; - $signatureSubject = isset($this->signatureSubject) ? $this->signatureSubject: null; + $signatureSubject = isset($this->signatureSubject) ? $this->signatureSubject : null; + $signatureAlgorithm = self::identifySignatureAlgorithm($issuer->privateKey); + if ($signatureAlgorithm != 'id-RSASSA-PSS') { + $signatureAlgorithm = ['algorithm' => $signatureAlgorithm]; + } else { + $r = PSS::load($issuer->privateKey->withPassword()->toString('PSS')); + $signatureAlgorithm = [ + 'algorithm' => 'id-RSASSA-PSS', + 'parameters' => PSS::savePSSParams($r) + ]; + } if (isset($subject->currentCert) && is_array($subject->currentCert) && isset($subject->currentCert['tbsCertificate'])) { $this->currentCert = $subject->currentCert; - $this->currentCert['tbsCertificate']['signature']['algorithm'] = $signatureAlgorithm; - $this->currentCert['signatureAlgorithm']['algorithm'] = $signatureAlgorithm; + $this->currentCert['tbsCertificate']['signature'] = $signatureAlgorithm; + $this->currentCert['signatureAlgorithm'] = $signatureAlgorithm; + if (!empty($this->startDate)) { - $this->currentCert['tbsCertificate']['validity']['notBefore'] = $this->_timeField($this->startDate); + $this->currentCert['tbsCertificate']['validity']['notBefore'] = $this->timeField($this->startDate); } if (!empty($this->endDate)) { - $this->currentCert['tbsCertificate']['validity']['notAfter'] = $this->_timeField($this->endDate); + $this->currentCert['tbsCertificate']['validity']['notAfter'] = $this->timeField($this->endDate); } if (!empty($this->serialNumber)) { $this->currentCert['tbsCertificate']['serialNumber'] = $this->serialNumber; @@ -3655,10 +2583,10 @@ function sign($issuer, $subject, $signatureAlgorithm = 'sha1WithRSAEncryption') return false; } - $startDate = new DateTime('now', new DateTimeZone(@date_default_timezone_get())); + $startDate = new \DateTimeImmutable('now', new \DateTimeZone(@date_default_timezone_get())); $startDate = !empty($this->startDate) ? $this->startDate : $startDate->format('D, d M Y H:i:s O'); - $endDate = new DateTime('+1 year', new DateTimeZone(@date_default_timezone_get())); + $endDate = new \DateTimeImmutable('+1 year', new \DateTimeZone(@date_default_timezone_get())); $endDate = !empty($this->endDate) ? $this->endDate : $endDate->format('D, d M Y H:i:s O'); /* "The serial number MUST be a positive integer" @@ -3672,23 +2600,23 @@ function sign($issuer, $subject, $signatureAlgorithm = 'sha1WithRSAEncryption') $this->serialNumber : new BigInteger(Random::string(20) & ("\x7F" . str_repeat("\xFF", 19)), 256); - $this->currentCert = array( + $this->currentCert = [ 'tbsCertificate' => - array( + [ 'version' => 'v3', 'serialNumber' => $serialNumber, // $this->setSerialNumber() - 'signature' => array('algorithm' => $signatureAlgorithm), + 'signature' => $signatureAlgorithm, 'issuer' => false, // this is going to be overwritten later - 'validity' => array( - 'notBefore' => $this->_timeField($startDate), // $this->setStartDate() - 'notAfter' => $this->_timeField($endDate) // $this->setEndDate() - ), + 'validity' => [ + 'notBefore' => $this->timeField($startDate), // $this->setStartDate() + 'notAfter' => $this->timeField($endDate) // $this->setEndDate() + ], 'subject' => $subject->dn, 'subjectPublicKeyInfo' => $subjectPublicKey - ), - 'signatureAlgorithm' => array('algorithm' => $signatureAlgorithm), + ], + 'signatureAlgorithm' => $signatureAlgorithm, 'signature' => false // this is going to be overwritten later - ); + ]; // Copy extensions from CSR. $csrexts = $subject->getAttribute('pkcs-9-at-extensionRequest', 0); @@ -3701,14 +2629,14 @@ function sign($issuer, $subject, $signatureAlgorithm = 'sha1WithRSAEncryption') $this->currentCert['tbsCertificate']['issuer'] = $issuer->dn; if (isset($issuer->currentKeyIdentifier)) { - $this->setExtension('id-ce-authorityKeyIdentifier', array( + $this->setExtension('id-ce-authorityKeyIdentifier', [ //'authorityCertIssuer' => array( // array( // 'directoryName' => $issuer->dn // ) //), 'keyIdentifier' => $issuer->currentKeyIdentifier - )); + ]); //$extensions = &$this->currentCert['tbsCertificate']['extensions']; //if (isset($issuer->serialNumber)) { // $extensions[count($extensions) - 1]['authorityCertSerialNumber'] = $issuer->serialNumber; @@ -3720,18 +2648,18 @@ function sign($issuer, $subject, $signatureAlgorithm = 'sha1WithRSAEncryption') $this->setExtension('id-ce-subjectKeyIdentifier', $subject->currentKeyIdentifier); } - $altName = array(); + $altName = []; if (isset($subject->domains) && count($subject->domains)) { - $altName = array_map(array('\phpseclib\File\X509', '_dnsName'), $subject->domains); + $altName = array_map(['\phpseclib3\File\X509', 'dnsName'], $subject->domains); } if (isset($subject->ipAddresses) && count($subject->ipAddresses)) { // should an IP address appear as the CN if no domain name is specified? idk //$ips = count($subject->domains) ? $subject->ipAddresses : array_slice($subject->ipAddresses, 1); - $ipAddresses = array(); + $ipAddresses = []; foreach ($subject->ipAddresses as $ipAddress) { - $encoded = $subject->_ipAddress($ipAddress); + $encoded = $subject->ipAddress($ipAddress); if ($encoded !== false) { $ipAddresses[] = $encoded; } @@ -3748,36 +2676,37 @@ function sign($issuer, $subject, $signatureAlgorithm = 'sha1WithRSAEncryption') if ($this->caFlag) { $keyUsage = $this->getExtension('id-ce-keyUsage'); if (!$keyUsage) { - $keyUsage = array(); + $keyUsage = []; } $this->setExtension( 'id-ce-keyUsage', - array_values(array_unique(array_merge($keyUsage, array('cRLSign', 'keyCertSign')))) + array_values(array_unique(array_merge($keyUsage, ['cRLSign', 'keyCertSign']))) ); $basicConstraints = $this->getExtension('id-ce-basicConstraints'); if (!$basicConstraints) { - $basicConstraints = array(); + $basicConstraints = []; } $this->setExtension( 'id-ce-basicConstraints', - array_unique(array_merge(array('cA' => true), $basicConstraints)), + array_merge(['cA' => true], $basicConstraints), true ); if (!isset($subject->currentKeyIdentifier)) { - $this->setExtension('id-ce-subjectKeyIdentifier', base64_encode($this->computeKeyIdentifier($this->currentCert)), false, false); + $this->setExtension('id-ce-subjectKeyIdentifier', $this->computeKeyIdentifier($this->currentCert), false, false); } } // resync $this->signatureSubject - // save $tbsCertificate in case there are any \phpseclib\File\ASN1\Element objects in it + // save $tbsCertificate in case there are any \phpseclib3\File\ASN1\Element objects in it $tbsCertificate = $this->currentCert['tbsCertificate']; $this->loadX509($this->saveX509($this->currentCert)); - $result = $this->_sign($issuer->privateKey, $signatureAlgorithm); + $result = $this->currentCert; + $this->currentCert['signature'] = $result['signature'] = "\0" . $issuer->privateKey->sign($this->signatureSubject); $result['tbsCertificate'] = $tbsCertificate; $this->currentCert = $currentCert; @@ -3789,27 +2718,22 @@ function sign($issuer, $subject, $signatureAlgorithm = 'sha1WithRSAEncryption') /** * Sign a CSR * - * @access public * @return mixed */ - function signCSR($signatureAlgorithm = 'sha1WithRSAEncryption') + public function signCSR() { if (!is_object($this->privateKey) || empty($this->dn)) { return false; } $origPublicKey = $this->publicKey; - $class = get_class($this->privateKey); - $this->publicKey = new $class(); - $this->publicKey->loadKey($this->privateKey->getPublicKey()); - $this->publicKey->setPublicKey(); - if (!($publicKey = $this->_formatSubjectPublicKey())) { - return false; - } + $this->publicKey = $this->privateKey->getPublicKey(); + $publicKey = $this->formatSubjectPublicKey(); $this->publicKey = $origPublicKey; $currentCert = isset($this->currentCert) ? $this->currentCert : null; - $signatureSubject = isset($this->signatureSubject) ? $this->signatureSubject: null; + $signatureSubject = isset($this->signatureSubject) ? $this->signatureSubject : null; + $signatureAlgorithm = self::identifySignatureAlgorithm($this->privateKey); if (isset($this->currentCert) && is_array($this->currentCert) && isset($this->currentCert['certificationRequestInfo'])) { $this->currentCert['signatureAlgorithm']['algorithm'] = $signatureAlgorithm; @@ -3818,24 +2742,25 @@ function signCSR($signatureAlgorithm = 'sha1WithRSAEncryption') } $this->currentCert['certificationRequestInfo']['subjectPKInfo'] = $publicKey; } else { - $this->currentCert = array( + $this->currentCert = [ 'certificationRequestInfo' => - array( + [ 'version' => 'v1', 'subject' => $this->dn, 'subjectPKInfo' => $publicKey - ), - 'signatureAlgorithm' => array('algorithm' => $signatureAlgorithm), + ], + 'signatureAlgorithm' => ['algorithm' => $signatureAlgorithm], 'signature' => false // this is going to be overwritten later - ); + ]; } // resync $this->signatureSubject - // save $certificationRequestInfo in case there are any \phpseclib\File\ASN1\Element objects in it + // save $certificationRequestInfo in case there are any \phpseclib3\File\ASN1\Element objects in it $certificationRequestInfo = $this->currentCert['certificationRequestInfo']; $this->loadCSR($this->saveCSR($this->currentCert)); - $result = $this->_sign($this->privateKey, $signatureAlgorithm); + $result = $this->currentCert; + $this->currentCert['signature'] = $result['signature'] = "\0" . $this->privateKey->sign($this->signatureSubject); $result['certificationRequestInfo'] = $certificationRequestInfo; $this->currentCert = $currentCert; @@ -3847,28 +2772,22 @@ function signCSR($signatureAlgorithm = 'sha1WithRSAEncryption') /** * Sign a SPKAC * - * @access public * @return mixed */ - function signSPKAC($signatureAlgorithm = 'sha1WithRSAEncryption') + public function signSPKAC() { if (!is_object($this->privateKey)) { return false; } $origPublicKey = $this->publicKey; - $class = get_class($this->privateKey); - $this->publicKey = new $class(); - $this->publicKey->loadKey($this->privateKey->getPublicKey()); - $this->publicKey->setPublicKey(); - $publicKey = $this->_formatSubjectPublicKey(); - if (!$publicKey) { - return false; - } + $this->publicKey = $this->privateKey->getPublicKey(); + $publicKey = $this->formatSubjectPublicKey(); $this->publicKey = $origPublicKey; $currentCert = isset($this->currentCert) ? $this->currentCert : null; - $signatureSubject = isset($this->signatureSubject) ? $this->signatureSubject: null; + $signatureSubject = isset($this->signatureSubject) ? $this->signatureSubject : null; + $signatureAlgorithm = self::identifySignatureAlgorithm($this->privateKey); // re-signing a SPKAC seems silly but since everything else supports re-signing why not? if (isset($this->currentCert) && is_array($this->currentCert) && isset($this->currentCert['publicKeyAndChallenge'])) { @@ -3879,9 +2798,9 @@ function signSPKAC($signatureAlgorithm = 'sha1WithRSAEncryption') $this->currentCert['publicKeyAndChallenge']['challenge'] = $this->challenge & str_repeat("\x7F", strlen($this->challenge)); } } else { - $this->currentCert = array( + $this->currentCert = [ 'publicKeyAndChallenge' => - array( + [ 'spki' => $publicKey, // quoting , // "A challenge string that is submitted along with the public key. Defaults to an empty string if not specified." @@ -3889,18 +2808,19 @@ function signSPKAC($signatureAlgorithm = 'sha1WithRSAEncryption') // we could alternatively do this instead if we ignored the specs: // Random::string(8) & str_repeat("\x7F", 8) 'challenge' => !empty($this->challenge) ? $this->challenge : '' - ), - 'signatureAlgorithm' => array('algorithm' => $signatureAlgorithm), + ], + 'signatureAlgorithm' => ['algorithm' => $signatureAlgorithm], 'signature' => false // this is going to be overwritten later - ); + ]; } // resync $this->signatureSubject - // save $publicKeyAndChallenge in case there are any \phpseclib\File\ASN1\Element objects in it + // save $publicKeyAndChallenge in case there are any \phpseclib3\File\ASN1\Element objects in it $publicKeyAndChallenge = $this->currentCert['publicKeyAndChallenge']; $this->loadSPKAC($this->saveSPKAC($this->currentCert)); - $result = $this->_sign($this->privateKey, $signatureAlgorithm); + $result = $this->currentCert; + $this->currentCert['signature'] = $result['signature'] = "\0" . $this->privateKey->sign($this->signatureSubject); $result['publicKeyAndChallenge'] = $publicKeyAndChallenge; $this->currentCert = $currentCert; @@ -3914,13 +2834,9 @@ function signSPKAC($signatureAlgorithm = 'sha1WithRSAEncryption') * * $issuer's private key needs to be loaded. * - * @param \phpseclib\File\X509 $issuer - * @param \phpseclib\File\X509 $crl - * @param string $signatureAlgorithm optional - * @access public * @return mixed */ - function signCRL($issuer, $crl, $signatureAlgorithm = 'sha1WithRSAEncryption') + public function signCRL(X509 $issuer, X509 $crl) { if (!is_object($issuer->privateKey) || empty($issuer->dn)) { return false; @@ -3928,8 +2844,9 @@ function signCRL($issuer, $crl, $signatureAlgorithm = 'sha1WithRSAEncryption') $currentCert = isset($this->currentCert) ? $this->currentCert : null; $signatureSubject = isset($this->signatureSubject) ? $this->signatureSubject : null; + $signatureAlgorithm = self::identifySignatureAlgorithm($issuer->privateKey); - $thisUpdate = new DateTime('now', new DateTimeZone(@date_default_timezone_get())); + $thisUpdate = new \DateTimeImmutable('now', new \DateTimeZone(@date_default_timezone_get())); $thisUpdate = !empty($this->startDate) ? $this->startDate : $thisUpdate->format('D, d M Y H:i:s O'); if (isset($crl->currentCert) && is_array($crl->currentCert) && isset($crl->currentCert['tbsCertList'])) { @@ -3937,25 +2854,25 @@ function signCRL($issuer, $crl, $signatureAlgorithm = 'sha1WithRSAEncryption') $this->currentCert['tbsCertList']['signature']['algorithm'] = $signatureAlgorithm; $this->currentCert['signatureAlgorithm']['algorithm'] = $signatureAlgorithm; } else { - $this->currentCert = array( + $this->currentCert = [ 'tbsCertList' => - array( + [ 'version' => 'v2', - 'signature' => array('algorithm' => $signatureAlgorithm), + 'signature' => ['algorithm' => $signatureAlgorithm], 'issuer' => false, // this is going to be overwritten later - 'thisUpdate' => $this->_timeField($thisUpdate) // $this->setStartDate() - ), - 'signatureAlgorithm' => array('algorithm' => $signatureAlgorithm), + 'thisUpdate' => $this->timeField($thisUpdate) // $this->setStartDate() + ], + 'signatureAlgorithm' => ['algorithm' => $signatureAlgorithm], 'signature' => false // this is going to be overwritten later - ); + ]; } $tbsCertList = &$this->currentCert['tbsCertList']; $tbsCertList['issuer'] = $issuer->dn; - $tbsCertList['thisUpdate'] = $this->_timeField($thisUpdate); + $tbsCertList['thisUpdate'] = $this->timeField($thisUpdate); if (!empty($this->endDate)) { - $tbsCertList['nextUpdate'] = $this->_timeField($this->endDate); // $this->setEndDate() + $tbsCertList['nextUpdate'] = $this->timeField($this->endDate); // $this->setEndDate() } else { unset($tbsCertList['nextUpdate']); } @@ -4000,14 +2917,14 @@ function signCRL($issuer, $crl, $signatureAlgorithm = 'sha1WithRSAEncryption') } if (isset($issuer->currentKeyIdentifier)) { - $this->setExtension('id-ce-authorityKeyIdentifier', array( + $this->setExtension('id-ce-authorityKeyIdentifier', [ //'authorityCertIssuer' => array( - // array( + // ] // 'directoryName' => $issuer->dn - // ) + // ] //), 'keyIdentifier' => $issuer->currentKeyIdentifier - )); + ]); //$extensions = &$tbsCertList['crlExtensions']; //if (isset($issuer->serialNumber)) { // $extensions[count($extensions) - 1]['authorityCertSerialNumber'] = $issuer->serialNumber; @@ -4029,11 +2946,12 @@ function signCRL($issuer, $crl, $signatureAlgorithm = 'sha1WithRSAEncryption') unset($tbsCertList); // resync $this->signatureSubject - // save $tbsCertList in case there are any \phpseclib\File\ASN1\Element objects in it + // save $tbsCertList in case there are any \phpseclib3\File\ASN1\Element objects in it $tbsCertList = $this->currentCert['tbsCertList']; $this->loadCRL($this->saveCRL($this->currentCert)); - $result = $this->_sign($issuer->privateKey, $signatureAlgorithm); + $result = $this->currentCert; + $this->currentCert['signature'] = $result['signature'] = "\0" . $issuer->privateKey->sign($this->signatureSubject); $result['tbsCertList'] = $tbsCertList; $this->currentCert = $currentCert; @@ -4043,45 +2961,70 @@ function signCRL($issuer, $crl, $signatureAlgorithm = 'sha1WithRSAEncryption') } /** - * X.509 certificate signing helper function. + * Identify signature algorithm from key settings * - * @param \phpseclib\File\X509 $key - * @param string $signatureAlgorithm - * @access public - * @return mixed + * @param PrivateKey $key + * @throws \phpseclib3\Exception\UnsupportedAlgorithmException if the algorithm is unsupported + * @return string */ - function _sign($key, $signatureAlgorithm) + private static function identifySignatureAlgorithm(PrivateKey $key) { if ($key instanceof RSA) { - switch ($signatureAlgorithm) { - case 'md2WithRSAEncryption': - case 'md5WithRSAEncryption': - case 'sha1WithRSAEncryption': - case 'sha224WithRSAEncryption': - case 'sha256WithRSAEncryption': - case 'sha384WithRSAEncryption': - case 'sha512WithRSAEncryption': - $key->setHash(preg_replace('#WithRSAEncryption$#', '', $signatureAlgorithm)); - $key->setSignatureMode(RSA::SIGNATURE_PKCS1); - - $this->currentCert['signature'] = base64_encode("\0" . $key->sign($this->signatureSubject)); - return $this->currentCert; + if ($key->getPadding() & RSA::SIGNATURE_PSS) { + return 'id-RSASSA-PSS'; + } + switch ($key->getHash()) { + case 'md2': + case 'md5': + case 'sha1': + case 'sha224': + case 'sha256': + case 'sha384': + case 'sha512': + return $key->getHash() . 'WithRSAEncryption'; } + throw new UnsupportedAlgorithmException('The only supported hash algorithms for RSA are: md2, md5, sha1, sha224, sha256, sha384, sha512'); } - return false; + if ($key instanceof DSA) { + switch ($key->getHash()) { + case 'sha1': + case 'sha224': + case 'sha256': + return 'id-dsa-with-' . $key->getHash(); + } + throw new UnsupportedAlgorithmException('The only supported hash algorithms for DSA are: sha1, sha224, sha256'); + } + + if ($key instanceof EC) { + switch ($key->getCurve()) { + case 'Ed25519': + case 'Ed448': + return 'id-' . $key->getCurve(); + } + switch ($key->getHash()) { + case 'sha1': + case 'sha224': + case 'sha256': + case 'sha384': + case 'sha512': + return 'ecdsa-with-' . strtoupper($key->getHash()); + } + throw new UnsupportedAlgorithmException('The only supported hash algorithms for EC are: sha1, sha224, sha256, sha384, sha512'); + } + + throw new UnsupportedAlgorithmException('The only supported public key classes are: RSA, DSA, EC'); } /** * Set certificate start date * - * @param string $date - * @access public + * @param \DateTimeInterface|string $date */ - function setStartDate($date) + public function setStartDate($date) { - if (!is_object($date) || !is_a($date, 'DateTime')) { - $date = new DateTime($date, new DateTimeZone(@date_default_timezone_get())); + if (!is_object($date) || !($date instanceof \DateTimeInterface)) { + $date = new \DateTimeImmutable($date, new \DateTimeZone(@date_default_timezone_get())); } $this->startDate = $date->format('D, d M Y H:i:s O'); @@ -4090,10 +3033,9 @@ function setStartDate($date) /** * Set certificate end date * - * @param string $date - * @access public + * @param \DateTimeInterface|string $date */ - function setEndDate($date) + public function setEndDate($date) { /* To indicate that a certificate has no well-defined expiration date, @@ -4102,14 +3044,13 @@ function setEndDate($date) -- http://tools.ietf.org/html/rfc5280#section-4.1.2.5 */ - if (strtolower($date) == 'lifetime') { + if (is_string($date) && strtolower($date) === 'lifetime') { $temp = '99991231235959Z'; - $asn1 = new ASN1(); - $temp = chr(ASN1::TYPE_GENERALIZED_TIME) . $asn1->_encodeLength(strlen($temp)) . $temp; + $temp = chr(ASN1::TYPE_GENERALIZED_TIME) . ASN1::encodeLength(strlen($temp)) . $temp; $this->endDate = new Element($temp); } else { - if (!is_object($date) || !is_a($date, 'DateTime')) { - $date = new DateTime($date, new DateTimeZone(@date_default_timezone_get())); + if (!is_object($date) || !($date instanceof \DateTimeInterface)) { + $date = new \DateTimeImmutable($date, new \DateTimeZone(@date_default_timezone_get())); } $this->endDate = $date->format('D, d M Y H:i:s O'); @@ -4121,9 +3062,8 @@ function setEndDate($date) * * @param string $serial * @param int $base optional - * @access public */ - function setSerialNumber($serial, $base = -256) + public function setSerialNumber($serial, $base = -256) { $this->serialNumber = new BigInteger($serial, $base); } @@ -4131,9 +3071,8 @@ function setSerialNumber($serial, $base = -256) /** * Turns the certificate into a certificate authority * - * @access public */ - function makeCA() + public function makeCA() { $this->caFlag = true; } @@ -4148,9 +3087,8 @@ function makeCA() * @param array $root * @param string $path * @return boolean - * @access private */ - function _isSubArrayValid($root, $path) + private function isSubArrayValid(array $root, $path) { if (!is_array($root)) { return false; @@ -4184,10 +3122,9 @@ function _isSubArrayValid($root, $path) * @param array $root * @param string $path absolute path with / as component separator * @param bool $create optional - * @access private * @return array|false */ - function &_subArrayUnchecked(&$root, $path, $create = false) + private function &subArrayUnchecked(array &$root, $path, $create = false) { $false = false; @@ -4197,7 +3134,7 @@ function &_subArrayUnchecked(&$root, $path, $create = false) return $false; } - $root[$i] = array(); + $root[$i] = []; } $root = &$root[$i]; @@ -4212,10 +3149,9 @@ function &_subArrayUnchecked(&$root, $path, $create = false) * @param array $root * @param string $path absolute path with / as component separator * @param bool $create optional - * @access private * @return array|false */ - function &_subArray(&$root, $path, $create = false) + private function &subArray(array &$root = null, $path, $create = false) { $false = false; @@ -4233,7 +3169,7 @@ function &_subArray(&$root, $path, $create = false) return $false; } - $root[$i] = array(); + $root[$i] = []; } $root = &$root[$i]; @@ -4248,10 +3184,9 @@ function &_subArray(&$root, $path, $create = false) * @param array $root * @param string $path optional absolute path with / as component separator * @param bool $create optional - * @access private * @return array|false */ - function &_extensions(&$root, $path = null, $create = false) + private function &extensions(array &$root = null, $path = null, $create = false) { if (!isset($root)) { $root = $this->currentCert; @@ -4269,7 +3204,7 @@ function &_extensions(&$root, $path = null, $create = false) break; case isset($root['certificationRequestInfo']): $pth = 'certificationRequestInfo/attributes'; - $attributes = &$this->_subArray($root, $pth, $create); + $attributes = &$this->subArray($root, $pth, $create); if (is_array($attributes)) { foreach ($attributes as $key => $value) { @@ -4280,14 +3215,14 @@ function &_extensions(&$root, $path = null, $create = false) } if ($create) { $key = count($attributes); - $attributes[] = array('type' => 'pkcs-9-at-extensionRequest', 'value' => array()); + $attributes[] = ['type' => 'pkcs-9-at-extensionRequest', 'value' => []]; $path = "$pth/$key/value/0"; } } break; } - $extensions = &$this->_subArray($root, $path, $create); + $extensions = &$this->subArray($root, $path, $create); if (!is_array($extensions)) { $false = false; @@ -4302,12 +3237,11 @@ function &_extensions(&$root, $path = null, $create = false) * * @param string $id * @param string $path optional - * @access private * @return bool */ - function _removeExtension($id, $path = null) + private function removeExtensionHelper($id, $path = null) { - $extensions = &$this->_extensions($this->currentCert, $path); + $extensions = &$this->extensions($this->currentCert, $path); if (!is_array($extensions)) { return false; @@ -4337,12 +3271,11 @@ function _removeExtension($id, $path = null) * @param string $id * @param array $cert optional * @param string $path optional - * @access private * @return mixed */ - function _getExtension($id, $cert = null, $path = null) + private function getExtensionHelper($id, array $cert = null, $path = null) { - $extensions = $this->_extensions($cert, $path); + $extensions = $this->extensions($cert, $path); if (!is_array($extensions)) { return false; @@ -4362,13 +3295,12 @@ function _getExtension($id, $cert = null, $path = null) * * @param array $cert optional * @param string $path optional - * @access private * @return array */ - function _getExtensions($cert = null, $path = null) + private function getExtensionsHelper(array $cert = null, $path = null) { - $exts = $this->_extensions($cert, $path); - $extensions = array(); + $exts = $this->extensions($cert, $path); + $extensions = []; if (is_array($exts)) { foreach ($exts as $extension) { @@ -4387,18 +3319,17 @@ function _getExtensions($cert = null, $path = null) * @param bool $critical optional * @param bool $replace optional * @param string $path optional - * @access private * @return bool */ - function _setExtension($id, $value, $critical = false, $replace = true, $path = null) + private function setExtensionHelper($id, $value, $critical = false, $replace = true, $path = null) { - $extensions = &$this->_extensions($this->currentCert, $path, true); + $extensions = &$this->extensions($this->currentCert, $path, true); if (!is_array($extensions)) { return false; } - $newext = array('extnId' => $id, 'critical' => $critical, 'extnValue' => $value); + $newext = ['extnId' => $id, 'critical' => $critical, 'extnValue' => $value]; foreach ($extensions as $key => $value) { if ($value['extnId'] == $id) { @@ -4419,12 +3350,11 @@ function _setExtension($id, $value, $critical = false, $replace = true, $path = * Remove a certificate, CSR or CRL Extension * * @param string $id - * @access public * @return bool */ - function removeExtension($id) + public function removeExtension($id) { - return $this->_removeExtension($id); + return $this->removeExtensionHelper($id); } /** @@ -4434,24 +3364,24 @@ function removeExtension($id) * * @param string $id * @param array $cert optional - * @access public + * @param string $path * @return mixed */ - function getExtension($id, $cert = null) + public function getExtension($id, array $cert = null, $path = null) { - return $this->_getExtension($id, $cert); + return $this->getExtensionHelper($id, $cert, $path); } /** * Returns a list of all extensions in use in certificate, CSR or CRL * * @param array $cert optional - * @access public + * @param string $path optional * @return array */ - function getExtensions($cert = null) + public function getExtensions(array $cert = null, $path = null) { - return $this->_getExtensions($cert); + return $this->getExtensionsHelper($cert, $path); } /** @@ -4461,12 +3391,11 @@ function getExtensions($cert = null) * @param mixed $value * @param bool $critical optional * @param bool $replace optional - * @access public * @return bool */ - function setExtension($id, $value, $critical = false, $replace = true) + public function setExtension($id, $value, $critical = false, $replace = true) { - return $this->_setExtension($id, $value, $critical, $replace); + return $this->setExtensionHelper($id, $value, $critical, $replace); } /** @@ -4474,12 +3403,11 @@ function setExtension($id, $value, $critical = false, $replace = true) * * @param string $id * @param int $disposition optional - * @access public * @return bool */ - function removeAttribute($id, $disposition = self::ATTR_ALL) + public function removeAttribute($id, $disposition = self::ATTR_ALL) { - $attributes = &$this->_subArray($this->currentCert, 'certificationRequestInfo/attributes'); + $attributes = &$this->subArray($this->currentCert, 'certificationRequestInfo/attributes'); if (!is_array($attributes)) { return false; @@ -4525,16 +3453,15 @@ function removeAttribute($id, $disposition = self::ATTR_ALL) * @param string $id * @param int $disposition optional * @param array $csr optional - * @access public * @return mixed */ - function getAttribute($id, $disposition = self::ATTR_ALL, $csr = null) + public function getAttribute($id, $disposition = self::ATTR_ALL, array $csr = null) { if (empty($csr)) { $csr = $this->currentCert; } - $attributes = $this->_subArray($csr, 'certificationRequestInfo/attributes'); + $attributes = $this->subArray($csr, 'certificationRequestInfo/attributes'); if (!is_array($attributes)) { return false; @@ -4565,17 +3492,16 @@ function getAttribute($id, $disposition = self::ATTR_ALL, $csr = null) * Returns a list of all CSR attributes in use * * @param array $csr optional - * @access public * @return array */ - function getAttributes($csr = null) + public function getAttributes(array $csr = null) { if (empty($csr)) { $csr = $this->currentCert; } - $attributes = $this->_subArray($csr, 'certificationRequestInfo/attributes'); - $attrs = array(); + $attributes = $this->subArray($csr, 'certificationRequestInfo/attributes'); + $attrs = []; if (is_array($attributes)) { foreach ($attributes as $attribute) { @@ -4591,13 +3517,12 @@ function getAttributes($csr = null) * * @param string $id * @param mixed $value - * @param bool $disposition optional - * @access public + * @param int $disposition optional * @return bool */ - function setAttribute($id, $value, $disposition = self::ATTR_ALL) + public function setAttribute($id, $value, $disposition = self::ATTR_ALL) { - $attributes = &$this->_subArray($this->currentCert, 'certificationRequestInfo/attributes', true); + $attributes = &$this->subArray($this->currentCert, 'certificationRequestInfo/attributes', true); if (!is_array($attributes)) { return false; @@ -4606,6 +3531,7 @@ function setAttribute($id, $value, $disposition = self::ATTR_ALL) switch ($disposition) { case self::ATTR_REPLACE: $disposition = self::ATTR_APPEND; + // fall-through case self::ATTR_ALL: $this->removeAttribute($id); break; @@ -4635,7 +3561,7 @@ function setAttribute($id, $value, $disposition = self::ATTR_ALL) $attributes[$last]['value'][] = $value; break; default: - $attributes[] = array('type' => $id, 'value' => $disposition == self::ATTR_ALL ? $value: array($value)); + $attributes[] = ['type' => $id, 'value' => $disposition == self::ATTR_ALL ? $value : [$value]]; break; } @@ -4648,14 +3574,13 @@ function setAttribute($id, $value, $disposition = self::ATTR_ALL) * This is used by the id-ce-authorityKeyIdentifier and the id-ce-subjectKeyIdentifier extensions. * * @param string $value - * @access public */ - function setKeyIdentifier($value) + public function setKeyIdentifier($value) { if (empty($value)) { unset($this->currentKeyIdentifier); } else { - $this->currentKeyIdentifier = base64_encode($value); + $this->currentKeyIdentifier = $value; } } @@ -4667,17 +3592,16 @@ function setKeyIdentifier($value) * recommended methods (4.2.1.2 RFC 3280). * Highly polymorphic: try to accept all possible forms of key: * - Key object - * - \phpseclib\File\X509 object with public or private key defined + * - \phpseclib3\File\X509 object with public or private key defined * - Certificate or CSR array - * - \phpseclib\File\ASN1\Element object + * - \phpseclib3\File\ASN1\Element object * - PEM or DER string * * @param mixed $key optional * @param int $method optional - * @access public * @return string binary key identifier */ - function computeKeyIdentifier($key = null, $method = 1) + public function computeKeyIdentifier($key = null, $method = 1) { if (is_null($key)) { $key = $this; @@ -4694,25 +3618,20 @@ function computeKeyIdentifier($key = null, $method = 1) return false; case $key instanceof Element: // Assume the element is a bitstring-packed key. - $asn1 = new ASN1(); - $decoded = $asn1->decodeBER($key->element); - if (empty($decoded)) { + $decoded = ASN1::decodeBER($key->element); + if (!$decoded) { return false; } - $raw = $asn1->asn1map($decoded[0], array('type' => ASN1::TYPE_BIT_STRING)); + $raw = ASN1::asn1map($decoded[0], ['type' => ASN1::TYPE_BIT_STRING]); if (empty($raw)) { return false; } - $raw = base64_decode($raw); // If the key is private, compute identifier from its corresponding public key. - $key = new RSA(); - if (!$key->loadKey($raw)) { - return false; // Not an unencrypted RSA key. - } - if ($key->getPrivateKey() !== false) { // If private. + $key = PublicKeyLoader::load($raw); + if ($key instanceof PrivateKey) { // If private. return $this->computeKeyIdentifier($key, $method); } - $key = $raw; // Is a public key. + $key = $raw; // Is a public key. break; case $key instanceof X509: if (isset($key->publicKey)) { @@ -4725,13 +3644,13 @@ function computeKeyIdentifier($key = null, $method = 1) return $this->computeKeyIdentifier($key->currentCert, $method); } return false; - default: // Should be a key object (i.e.: \phpseclib\Crypt\RSA). - $key = $key->getPublicKey(RSA::PUBLIC_FORMAT_PKCS1); + default: // Should be a key object (i.e.: \phpseclib3\Crypt\RSA). + $key = $key->getPublicKey(); break; } // If in PEM format, convert to binary. - $key = $this->_extractBER($key); + $key = ASN1::extractBER($key); // Now we have the key string: compute its sha-1 sum. $hash = new Hash('sha1'); @@ -4748,33 +3667,39 @@ function computeKeyIdentifier($key = null, $method = 1) /** * Format a public key as appropriate * - * @access private - * @return array + * @return array|false */ - function _formatSubjectPublicKey() + private function formatSubjectPublicKey() { - if ($this->publicKey instanceof RSA) { - // the following two return statements do the same thing. i dunno.. i just prefer the later for some reason. - // the former is a good example of how to do fuzzing on the public key - //return new Element(base64_decode(preg_replace('#-.+-|[\r\n]#', '', $this->publicKey->getPublicKey()))); - return array( - 'algorithm' => array('algorithm' => 'rsaEncryption'), - 'subjectPublicKey' => $this->publicKey->getPublicKey(RSA::PUBLIC_FORMAT_PKCS1) - ); + $format = $this->publicKey instanceof RSA && ($this->publicKey->getPadding() & RSA::SIGNATURE_PSS) ? + 'PSS' : + 'PKCS8'; + + $publicKey = base64_decode(preg_replace('#-.+-|[\r\n]#', '', $this->publicKey->toString($format))); + + $decoded = ASN1::decodeBER($publicKey); + if (!$decoded) { + return false; + } + $mapped = ASN1::asn1map($decoded[0], Maps\SubjectPublicKeyInfo::MAP); + if (!is_array($mapped)) { + return false; } - return false; + $mapped['subjectPublicKey'] = $this->publicKey->toString($format); + + return $mapped; } /** * Set the domain name's which the cert is to be valid for * - * @access public - * @return array + * @param mixed ...$domains + * @return void */ - function setDomain() + public function setDomain(...$domains) { - $this->domains = func_get_args(); + $this->domains = $domains; $this->removeDNProp('id-at-commonName'); $this->setDNProp('id-at-commonName', $this->domains[0]); } @@ -4782,11 +3707,11 @@ function setDomain() /** * Set the IP Addresses's which the cert is to be valid for * - * @access public + * @param mixed[] ...$ipAddresses */ - function setIPAddress() + public function setIPAddress(...$ipAddresses) { - $this->ipAddresses = func_get_args(); + $this->ipAddresses = $ipAddresses; /* if (!isset($this->domains)) { $this->removeDNProp('id-at-commonName'); @@ -4798,13 +3723,12 @@ function setIPAddress() /** * Helper function to build domain array * - * @access private * @param string $domain * @return array */ - function _dnsName($domain) + private static function dnsName($domain) { - return array('dNSName' => $domain); + return ['dNSName' => $domain]; } /** @@ -4812,13 +3736,12 @@ function _dnsName($domain) * * (IPv6 is not currently supported) * - * @access private * @param string $address * @return array */ - function _iPAddress($address) + private function iPAddress($address) { - return array('iPAddress' => $address); + return ['iPAddress' => $address]; } /** @@ -4827,10 +3750,9 @@ function _iPAddress($address) * @param array $rclist * @param string $serial * @param bool $create optional - * @access private * @return int|false */ - function _revokedCertificate(&$rclist, $serial, $create = false) + private function revokedCertificate(array &$rclist, $serial, $create = false) { $serial = new BigInteger($serial); @@ -4845,9 +3767,9 @@ function _revokedCertificate(&$rclist, $serial, $create = false) } $i = count($rclist); - $revocationDate = new DateTime('now', new DateTimeZone(@date_default_timezone_get())); - $rclist[] = array('userCertificate' => $serial, - 'revocationDate' => $this->_timeField($revocationDate->format('D, d M Y H:i:s O'))); + $revocationDate = new \DateTimeImmutable('now', new \DateTimeZone(@date_default_timezone_get())); + $rclist[] = ['userCertificate' => $serial, + 'revocationDate' => $this->timeField($revocationDate->format('D, d M Y H:i:s O'))]; return $i; } @@ -4856,17 +3778,16 @@ function _revokedCertificate(&$rclist, $serial, $create = false) * * @param string $serial * @param string $date optional - * @access public * @return bool */ - function revoke($serial, $date = null) + public function revoke($serial, $date = null) { if (isset($this->currentCert['tbsCertList'])) { - if (is_array($rclist = &$this->_subArray($this->currentCert, 'tbsCertList/revokedCertificates', true))) { - if ($this->_revokedCertificate($rclist, $serial) === false) { // If not yet revoked - if (($i = $this->_revokedCertificate($rclist, $serial, true)) !== false) { + if (is_array($rclist = &$this->subArray($this->currentCert, 'tbsCertList/revokedCertificates', true))) { + if ($this->revokedCertificate($rclist, $serial) === false) { // If not yet revoked + if (($i = $this->revokedCertificate($rclist, $serial, true)) !== false) { if (!empty($date)) { - $rclist[$i]['revocationDate'] = $this->_timeField($date); + $rclist[$i]['revocationDate'] = $this->timeField($date); } return true; @@ -4882,13 +3803,12 @@ function revoke($serial, $date = null) * Unrevoke a certificate. * * @param string $serial - * @access public * @return bool */ - function unrevoke($serial) + public function unrevoke($serial) { - if (is_array($rclist = &$this->_subArray($this->currentCert, 'tbsCertList/revokedCertificates'))) { - if (($i = $this->_revokedCertificate($rclist, $serial)) !== false) { + if (is_array($rclist = &$this->subArray($this->currentCert, 'tbsCertList/revokedCertificates'))) { + if (($i = $this->revokedCertificate($rclist, $serial)) !== false) { unset($rclist[$i]); $rclist = array_values($rclist); return true; @@ -4902,13 +3822,12 @@ function unrevoke($serial) * Get a revoked certificate. * * @param string $serial - * @access public * @return mixed */ - function getRevoked($serial) + public function getRevoked($serial) { - if (is_array($rclist = $this->_subArray($this->currentCert, 'tbsCertList/revokedCertificates'))) { - if (($i = $this->_revokedCertificate($rclist, $serial)) !== false) { + if (is_array($rclist = $this->subArray($this->currentCert, 'tbsCertList/revokedCertificates'))) { + if (($i = $this->revokedCertificate($rclist, $serial)) !== false) { return $rclist[$i]; } } @@ -4920,10 +3839,9 @@ function getRevoked($serial) * List revoked certificates * * @param array $crl optional - * @access public - * @return array + * @return array|bool */ - function listRevoked($crl = null) + public function listRevoked(array $crl = null) { if (!isset($crl)) { $crl = $this->currentCert; @@ -4933,9 +3851,9 @@ function listRevoked($crl = null) return false; } - $result = array(); + $result = []; - if (is_array($rclist = $this->_subArray($crl, 'tbsCertList/revokedCertificates'))) { + if (is_array($rclist = $this->subArray($crl, 'tbsCertList/revokedCertificates'))) { foreach ($rclist as $rc) { $result[] = $rc['userCertificate']->toString(); } @@ -4949,14 +3867,13 @@ function listRevoked($crl = null) * * @param string $serial * @param string $id - * @access public * @return bool */ - function removeRevokedCertificateExtension($serial, $id) + public function removeRevokedCertificateExtension($serial, $id) { - if (is_array($rclist = &$this->_subArray($this->currentCert, 'tbsCertList/revokedCertificates'))) { - if (($i = $this->_revokedCertificate($rclist, $serial)) !== false) { - return $this->_removeExtension($id, "tbsCertList/revokedCertificates/$i/crlEntryExtensions"); + if (is_array($rclist = &$this->subArray($this->currentCert, 'tbsCertList/revokedCertificates'))) { + if (($i = $this->revokedCertificate($rclist, $serial)) !== false) { + return $this->removeExtensionHelper($id, "tbsCertList/revokedCertificates/$i/crlEntryExtensions"); } } @@ -4971,18 +3888,17 @@ function removeRevokedCertificateExtension($serial, $id) * @param string $serial * @param string $id * @param array $crl optional - * @access public * @return mixed */ - function getRevokedCertificateExtension($serial, $id, $crl = null) + public function getRevokedCertificateExtension($serial, $id, array $crl = null) { if (!isset($crl)) { $crl = $this->currentCert; } - if (is_array($rclist = $this->_subArray($crl, 'tbsCertList/revokedCertificates'))) { - if (($i = $this->_revokedCertificate($rclist, $serial)) !== false) { - return $this->_getExtension($id, $crl, "tbsCertList/revokedCertificates/$i/crlEntryExtensions"); + if (is_array($rclist = $this->subArray($crl, 'tbsCertList/revokedCertificates'))) { + if (($i = $this->revokedCertificate($rclist, $serial)) !== false) { + return $this->getExtension($id, $crl, "tbsCertList/revokedCertificates/$i/crlEntryExtensions"); } } @@ -4994,18 +3910,17 @@ function getRevokedCertificateExtension($serial, $id, $crl = null) * * @param string $serial * @param array $crl optional - * @access public - * @return array + * @return array|bool */ - function getRevokedCertificateExtensions($serial, $crl = null) + public function getRevokedCertificateExtensions($serial, array $crl = null) { if (!isset($crl)) { $crl = $this->currentCert; } - if (is_array($rclist = $this->_subArray($crl, 'tbsCertList/revokedCertificates'))) { - if (($i = $this->_revokedCertificate($rclist, $serial)) !== false) { - return $this->_getExtensions($crl, "tbsCertList/revokedCertificates/$i/crlEntryExtensions"); + if (is_array($rclist = $this->subArray($crl, 'tbsCertList/revokedCertificates'))) { + if (($i = $this->revokedCertificate($rclist, $serial)) !== false) { + return $this->getExtensions($crl, "tbsCertList/revokedCertificates/$i/crlEntryExtensions"); } } @@ -5020,15 +3935,14 @@ function getRevokedCertificateExtensions($serial, $crl = null) * @param mixed $value * @param bool $critical optional * @param bool $replace optional - * @access public * @return bool */ - function setRevokedCertificateExtension($serial, $id, $value, $critical = false, $replace = true) + public function setRevokedCertificateExtension($serial, $id, $value, $critical = false, $replace = true) { if (isset($this->currentCert['tbsCertList'])) { - if (is_array($rclist = &$this->_subArray($this->currentCert, 'tbsCertList/revokedCertificates', true))) { - if (($i = $this->_revokedCertificate($rclist, $serial, true)) !== false) { - return $this->_setExtension($id, $value, $critical, $replace, "tbsCertList/revokedCertificates/$i/crlEntryExtensions"); + if (is_array($rclist = &$this->subArray($this->currentCert, 'tbsCertList/revokedCertificates', true))) { + if (($i = $this->revokedCertificate($rclist, $serial, true)) !== false) { + return $this->setExtensionHelper($id, $value, $critical, $replace, "tbsCertList/revokedCertificates/$i/crlEntryExtensions"); } } } @@ -5037,61 +3951,44 @@ function setRevokedCertificateExtension($serial, $id, $value, $critical = false, } /** - * Extract raw BER from Base64 encoding + * Register the mapping for a custom/unsupported extension. * - * @access private - * @param string $str - * @return string + * @param string $id + * @param array $mapping */ - function _extractBER($str) + public static function registerExtension($id, array $mapping) { - /* X.509 certs are assumed to be base64 encoded but sometimes they'll have additional things in them - * above and beyond the ceritificate. - * ie. some may have the following preceding the -----BEGIN CERTIFICATE----- line: - * - * Bag Attributes - * localKeyID: 01 00 00 00 - * subject=/O=organization/OU=org unit/CN=common name - * issuer=/O=organization/CN=common name - */ - if (strlen($str) > ini_get('pcre.backtrack_limit')) { - $temp = $str; - } else { - $temp = preg_replace('#.*?^-+[^-]+-+[\r\n ]*$#ms', '', $str, 1); - $temp = preg_replace('#-+END.*[\r\n ]*.*#ms', '', $str, 1); - } - // remove new lines - $temp = str_replace(array("\r", "\n", ' '), '', $temp); - // remove the -----BEGIN CERTIFICATE----- and -----END CERTIFICATE----- stuff - $temp = preg_replace('#^-+[^-]+-+|-+[^-]+-+$#', '', $temp); - $temp = preg_match('#^[a-zA-Z\d/+]*={0,2}$#', $temp) ? base64_decode($temp) : false; - return $temp != false ? $temp : $str; + if (isset(self::$extensions[$id]) && self::$extensions[$id] !== $mapping) { + throw new \RuntimeException( + 'Extension ' . $id . ' has already been defined with a different mapping.' + ); + } + + self::$extensions[$id] = $mapping; } /** - * Returns the OID corresponding to a name - * - * What's returned in the associative array returned by loadX509() (or load*()) is either a name or an OID if - * no OID to name mapping is available. The problem with this is that what may be an unmapped OID in one version - * of phpseclib may not be unmapped in the next version, so apps that are looking at this OID may not be able - * to work from version to version. + * Register the mapping for a custom/unsupported extension. * - * This method will return the OID if a name is passed to it and if no mapping is avialable it'll assume that - * what's being passed to it already is an OID and return that instead. A few examples. + * @param string $id * - * getOID('2.16.840.1.101.3.4.2.1') == '2.16.840.1.101.3.4.2.1' - * getOID('id-sha256') == '2.16.840.1.101.3.4.2.1' - * getOID('zzz') == 'zzz' + * @return array|null + */ + public static function getRegisteredExtension($id) + { + return isset(self::$extensions[$id]) ? self::$extensions[$id] : null; + } + + /** + * Register the mapping for a custom/unsupported extension. * - * @access public - * @return string + * @param string $id + * @param mixed $value + * @param bool $critical + * @param bool $replace */ - function getOID($name) + public function setExtensionValue($id, $value, $critical = false, $replace = false) { - static $reverseMap; - if (!isset($reverseMap)) { - $reverseMap = array_flip($this->oids); - } - return isset($reverseMap[$name]) ? $reverseMap[$name] : $name; + $this->extensionValues[$id] = compact('critical', 'replace', 'value'); } } diff --git a/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger.php b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger.php index fc24b914..b3ab84a8 100644 --- a/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger.php +++ b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger.php @@ -6,33 +6,13 @@ * Supports base-2, base-10, base-16, and base-256 numbers. Uses the GMP or BCMath extensions, if available, * and an internal implementation, otherwise. * - * PHP version 5 - * - * {@internal (all DocBlock comments regarding implementation - such as the one that follows - refer to the - * {@link self::MODE_INTERNAL self::MODE_INTERNAL} mode) - * - * BigInteger uses base-2**26 to perform operations such as multiplication and division and - * base-2**52 (ie. two base 2**26 digits) to perform addition and subtraction. Because the largest possible - * value when multiplying two base-2**26 numbers together is a base-2**52 number, double precision floating - * point numbers - numbers that should be supported on most hardware and whose significand is 53 bits - are - * used. As a consequence, bitwise operators such as >> and << cannot be used, nor can the modulo operator %, - * which only supports integers. Although this fact will slow this library down, the fact that such a high - * base is being used should more than compensate. - * - * Numbers are stored in {@link http://en.wikipedia.org/wiki/Endianness little endian} format. ie. - * (new \phpseclib\Math\BigInteger(pow(2, 26)))->value = array(0, 1) - * - * Useful resources are as follows: - * - * - {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf Handbook of Applied Cryptography (HAC)} - * - {@link http://math.libtomcrypt.com/files/tommath.pdf Multi-Precision Math (MPM)} - * - Java's BigInteger classes. See /j2se/src/share/classes/java/math in jdk-1_5_0-src-jrl.zip + * PHP version 5 and 7 * * Here's an example of how to use this library: * * add($b); * @@ -40,2646 +20,463 @@ * ?> * * - * @category Math - * @package BigInteger * @author Jim Wigginton - * @copyright 2006 Jim Wigginton + * @copyright 2017 Jim Wigginton * @license http://www.opensource.org/licenses/mit-license.html MIT License */ -namespace phpseclib\Math; +namespace phpseclib3\Math; -use phpseclib\Crypt\Random; +use phpseclib3\Exception\BadConfigurationException; +use phpseclib3\Math\BigInteger\Engines\Engine; /** * Pure-PHP arbitrary precision integer arithmetic library. Supports base-2, base-10, base-16, and base-256 * numbers. * - * @package BigInteger * @author Jim Wigginton - * @access public */ -class BigInteger +class BigInteger implements \JsonSerializable { - /**#@+ - * Reduction constants - * - * @access private - * @see BigInteger::_reduce() - */ - /** - * @see BigInteger::_montgomery() - * @see BigInteger::_prepMontgomery() - */ - const MONTGOMERY = 0; - /** - * @see BigInteger::_barrett() - */ - const BARRETT = 1; - /** - * @see BigInteger::_mod2() - */ - const POWEROF2 = 2; - /** - * @see BigInteger::_remainder() - */ - const CLASSIC = 3; - /** - * @see BigInteger::__clone() - */ - const NONE = 4; - /**#@-*/ - - /**#@+ - * Array constants - * - * Rather than create a thousands and thousands of new BigInteger objects in repeated function calls to add() and - * multiply() or whatever, we'll just work directly on arrays, taking them in as parameters and returning them. - * - * @access private - */ - /** - * $result[self::VALUE] contains the value. - */ - const VALUE = 0; - /** - * $result[self::SIGN] contains the sign. - */ - const SIGN = 1; - /**#@-*/ - - /**#@+ - * @access private - * @see BigInteger::_montgomery() - * @see BigInteger::_barrett() - */ - /** - * Cache constants - * - * $cache[self::VARIABLE] tells us whether or not the cached data is still valid. - */ - const VARIABLE = 0; - /** - * $cache[self::DATA] contains the cached data. - */ - const DATA = 1; - /**#@-*/ - - /**#@+ - * Mode constants. - * - * @access private - * @see BigInteger::__construct() - */ - /** - * To use the pure-PHP implementation - */ - const MODE_INTERNAL = 1; - /** - * To use the BCMath library - * - * (if enabled; otherwise, the internal implementation will be used) - */ - const MODE_BCMATH = 2; - /** - * To use the GMP library - * - * (if present; otherwise, either the BCMath or the internal implementation will be used) - */ - const MODE_GMP = 3; - /**#@-*/ - - /** - * Karatsuba Cutoff - * - * At what point do we switch between Karatsuba multiplication and schoolbook long multiplication? - * - * @access private - */ - const KARATSUBA_CUTOFF = 25; - - /**#@+ - * Static properties used by the pure-PHP implementation. - * - * @see __construct() - */ - protected static $base; - protected static $baseFull; - protected static $maxDigit; - protected static $msb; - /** - * $max10 in greatest $max10Len satisfying - * $max10 = 10**$max10Len <= 2**$base. - */ - protected static $max10; - - /** - * $max10Len in greatest $max10Len satisfying - * $max10 = 10**$max10Len <= 2**$base. - */ - protected static $max10Len; - protected static $maxDigit2; - /**#@-*/ - - /** - * Holds the BigInteger's value. + * Main Engine * - * @var array - * @access private + * @var class-string */ - var $value; + private static $mainEngine; /** - * Holds the BigInteger's magnitude. + * Selected Engines * - * @var bool - * @access private + * @var list */ - var $is_negative = false; + private static $engines; /** - * Precision + * The actual BigInteger object * - * @see self::setPrecision() - * @access private + * @var object */ - var $precision = -1; + private $value; /** - * Precision Bitmask + * Mode independent value used for serialization. * - * @see self::setPrecision() - * @access private + * @see self::__sleep() + * @see self::__wakeup() + * @var string */ - var $bitmask = false; + private $hex; /** - * Mode independent value used for serialization. - * - * If the bcmath or gmp extensions are installed $this->value will be a non-serializable resource, hence the need for - * a variable that'll be serializable regardless of whether or not extensions are being used. Unlike $this->value, - * however, $this->hex is only calculated when $this->__sleep() is called. + * Precision (used only for serialization) * * @see self::__sleep() * @see self::__wakeup() - * @var string - * @access private + * @var int */ - var $hex; + private $precision; /** - * Converts base-2, base-10, base-16, and binary strings (base-256) to BigIntegers. - * - * If the second parameter - $base - is negative, then it will be assumed that the number's are encoded using - * two's compliment. The sole exception to this is -10, which is treated the same as 10 is. - * - * Here's an example: - * - * toString(); // outputs 50 - * ?> - * + * Throws an exception if the type is invalid * - * @param int|string|resource $x base-10 number or base-$base number if $base set. - * @param int $base - * @return \phpseclib\Math\BigInteger - * @access public + * @param string $main + * @param list $modexps optional + * @return void */ - function __construct($x = 0, $base = 10) + public static function setEngine($main, array $modexps = ['DefaultEngine']) { - if (!defined('MATH_BIGINTEGER_MODE')) { - switch (true) { - case extension_loaded('gmp'): - define('MATH_BIGINTEGER_MODE', self::MODE_GMP); - break; - case extension_loaded('bcmath'): - define('MATH_BIGINTEGER_MODE', self::MODE_BCMATH); - break; - default: - define('MATH_BIGINTEGER_MODE', self::MODE_INTERNAL); - } - } + self::$engines = []; - if (extension_loaded('openssl') && !defined('MATH_BIGINTEGER_OPENSSL_DISABLE') && !defined('MATH_BIGINTEGER_OPENSSL_ENABLED')) { - // some versions of XAMPP have mismatched versions of OpenSSL which causes it not to work - $versions = array(); - - // avoid generating errors (even with suppression) when phpinfo() is disabled (common in production systems) - if (strpos(ini_get('disable_functions'), 'phpinfo') === false) { - ob_start(); - @phpinfo(); - $content = ob_get_contents(); - ob_end_clean(); - - preg_match_all('#OpenSSL (Header|Library) Version(.*)#im', $content, $matches); - - if (!empty($matches[1])) { - for ($i = 0; $i < count($matches[1]); $i++) { - $fullVersion = trim(str_replace('=>', '', strip_tags($matches[2][$i]))); - - // Remove letter part in OpenSSL version - if (!preg_match('/(\d+\.\d+\.\d+)/i', $fullVersion, $m)) { - $versions[$matches[1][$i]] = $fullVersion; - } else { - $versions[$matches[1][$i]] = $m[0]; - } - } - } - } - - // it doesn't appear that OpenSSL versions were reported upon until PHP 5.3+ - switch (true) { - case !isset($versions['Header']): - case !isset($versions['Library']): - case $versions['Header'] == $versions['Library']: - case version_compare($versions['Header'], '1.0.0') >= 0 && version_compare($versions['Library'], '1.0.0') >= 0: - define('MATH_BIGINTEGER_OPENSSL_ENABLED', true); - break; - default: - define('MATH_BIGINTEGER_OPENSSL_DISABLE', true); - } + $fqmain = 'phpseclib3\\Math\\BigInteger\\Engines\\' . $main; + if (!class_exists($fqmain) || !method_exists($fqmain, 'isValidEngine')) { + throw new \InvalidArgumentException("$main is not a valid engine"); } - - if (!defined('PHP_INT_SIZE')) { - define('PHP_INT_SIZE', 4); + if (!$fqmain::isValidEngine()) { + throw new BadConfigurationException("$main is not setup correctly on this system"); } + /** @var class-string $fqmain */ + self::$mainEngine = $fqmain; - if (empty(self::$base) && MATH_BIGINTEGER_MODE == self::MODE_INTERNAL) { - switch (PHP_INT_SIZE) { - case 8: // use 64-bit integers if int size is 8 bytes - self::$base = 31; - self::$baseFull = 0x80000000; - self::$maxDigit = 0x7FFFFFFF; - self::$msb = 0x40000000; - self::$max10 = 1000000000; - self::$max10Len = 9; - self::$maxDigit2 = pow(2, 62); - break; - //case 4: // use 64-bit floats if int size is 4 bytes - default: - self::$base = 26; - self::$baseFull = 0x4000000; - self::$maxDigit = 0x3FFFFFF; - self::$msb = 0x2000000; - self::$max10 = 10000000; - self::$max10Len = 7; - self::$maxDigit2 = pow(2, 52); // pow() prevents truncation - } + if (!in_array('Default', $modexps)) { + $modexps[] = 'DefaultEngine'; } - switch (MATH_BIGINTEGER_MODE) { - case self::MODE_GMP: - switch (true) { - case is_resource($x) && get_resource_type($x) == 'GMP integer': - // PHP 5.6 switched GMP from using resources to objects - case $x instanceof \GMP: - $this->value = $x; - return; - } - $this->value = gmp_init(0); - break; - case self::MODE_BCMATH: - $this->value = '0'; + $found = false; + foreach ($modexps as $modexp) { + try { + $fqmain::setModExpEngine($modexp); + $found = true; break; - default: - $this->value = array(); + } catch (\Exception $e) { + } } - // '0' counts as empty() but when the base is 256 '0' is equal to ord('0') or 48 - // '0' is the only value like this per http://php.net/empty - if (empty($x) && (abs($base) != 256 || $x !== '0')) { - return; + if (!$found) { + throw new BadConfigurationException("No valid modular exponentiation engine found for $main"); } - switch ($base) { - case -256: - if (ord($x[0]) & 0x80) { - $x = ~$x; - $this->is_negative = true; - } - case 256: - switch (MATH_BIGINTEGER_MODE) { - case self::MODE_GMP: - $this->value = function_exists('gmp_import') ? - gmp_import($x) : - gmp_init('0x' . bin2hex($x)); - if ($this->is_negative) { - $this->value = gmp_neg($this->value); - } - break; - case self::MODE_BCMATH: - // round $len to the nearest 4 (thanks, DavidMJ!) - $len = (strlen($x) + 3) & 0xFFFFFFFC; - - $x = str_pad($x, $len, chr(0), STR_PAD_LEFT); - - for ($i = 0; $i < $len; $i+= 4) { - $this->value = bcmul($this->value, '4294967296', 0); // 4294967296 == 2**32 - $this->value = bcadd($this->value, 0x1000000 * ord($x[$i]) + ((ord($x[$i + 1]) << 16) | (ord($x[$i + 2]) << 8) | ord($x[$i + 3])), 0); - } - - if ($this->is_negative) { - $this->value = '-' . $this->value; - } - - break; - // converts a base-2**8 (big endian / msb) number to base-2**26 (little endian / lsb) - default: - while (strlen($x)) { - $this->value[] = $this->_bytes2int($this->_base256_rshift($x, self::$base)); - } - } - - if ($this->is_negative) { - if (MATH_BIGINTEGER_MODE != self::MODE_INTERNAL) { - $this->is_negative = false; - } - $temp = $this->add(new static('-1')); - $this->value = $temp->value; - } - break; - case 16: - case -16: - if ($base > 0 && $x[0] == '-') { - $this->is_negative = true; - $x = substr($x, 1); - } - - $x = preg_replace('#^(?:0x)?([A-Fa-f0-9]*).*#', '$1', $x); - - $is_negative = false; - if ($base < 0 && hexdec($x[0]) >= 8) { - $this->is_negative = $is_negative = true; - $x = bin2hex(~pack('H*', $x)); - } - - switch (MATH_BIGINTEGER_MODE) { - case self::MODE_GMP: - $temp = $this->is_negative ? '-0x' . $x : '0x' . $x; - $this->value = gmp_init($temp); - $this->is_negative = false; - break; - case self::MODE_BCMATH: - $x = (strlen($x) & 1) ? '0' . $x : $x; - $temp = new static(pack('H*', $x), 256); - $this->value = $this->is_negative ? '-' . $temp->value : $temp->value; - $this->is_negative = false; - break; - default: - $x = (strlen($x) & 1) ? '0' . $x : $x; - $temp = new static(pack('H*', $x), 256); - $this->value = $temp->value; - } - - if ($is_negative) { - $temp = $this->add(new static('-1')); - $this->value = $temp->value; - } - break; - case 10: - case -10: - // (?value = gmp_init($x); - break; - case self::MODE_BCMATH: - // explicitly casting $x to a string is necessary, here, since doing $x[0] on -1 yields different - // results then doing it on '-1' does (modInverse does $x[0]) - $this->value = $x === '-' ? '0' : (string) $x; - break; - default: - $temp = new static(); - - $multiplier = new static(); - $multiplier->value = array(self::$max10); - - if ($x[0] == '-') { - $this->is_negative = true; - $x = substr($x, 1); - } - - $x = str_pad($x, strlen($x) + ((self::$max10Len - 1) * strlen($x)) % self::$max10Len, 0, STR_PAD_LEFT); - while (strlen($x)) { - $temp = $temp->multiply($multiplier); - $temp = $temp->add(new static($this->_int2bytes(substr($x, 0, self::$max10Len)), 256)); - $x = substr($x, self::$max10Len); - } - - $this->value = $temp->value; - } - break; - case 2: // base-2 support originally implemented by Lluis Pamies - thanks! - case -2: - if ($base > 0 && $x[0] == '-') { - $this->is_negative = true; - $x = substr($x, 1); - } - - $x = preg_replace('#^([01]*).*#', '$1', $x); - $x = str_pad($x, strlen($x) + (3 * strlen($x)) % 4, 0, STR_PAD_LEFT); - - $str = '0x'; - while (strlen($x)) { - $part = substr($x, 0, 4); - $str.= dechex(bindec($part)); - $x = substr($x, 4); - } - - if ($this->is_negative) { - $str = '-' . $str; - } - - $temp = new static($str, 8 * $base); // ie. either -16 or +16 - $this->value = $temp->value; - $this->is_negative = $temp->is_negative; - - break; - default: - // base not supported, so we'll let $this == 0 - } + self::$engines = [$main, $modexp]; } /** - * Converts a BigInteger to a byte string (eg. base-256). - * - * Negative numbers are saved as positive numbers, unless $twos_compliment is set to true, at which point, they're - * saved as two's compliment. - * - * Here's an example: - * - * toBytes(); // outputs chr(65) - * ?> - * + * Returns the engine type * - * @param bool $twos_compliment - * @return string - * @access public - * @internal Converts a base-2**26 number to base-2**8 + * @return string[] */ - function toBytes($twos_compliment = false) + public static function getEngine() { - if ($twos_compliment) { - $comparison = $this->compare(new static()); - if ($comparison == 0) { - return $this->precision > 0 ? str_repeat(chr(0), ($this->precision + 1) >> 3) : ''; - } - - $temp = $comparison < 0 ? $this->add(new static(1)) : $this->copy(); - $bytes = $temp->toBytes(); - - if (!strlen($bytes)) { // eg. if the number we're trying to convert is -1 - $bytes = chr(0); - } - - if ($this->precision <= 0 && (ord($bytes[0]) & 0x80)) { - $bytes = chr(0) . $bytes; - } - - return $comparison < 0 ? ~$bytes : $bytes; - } - - switch (MATH_BIGINTEGER_MODE) { - case self::MODE_GMP: - if (gmp_cmp($this->value, gmp_init(0)) == 0) { - return $this->precision > 0 ? str_repeat(chr(0), ($this->precision + 1) >> 3) : ''; - } - - if (function_exists('gmp_export')) { - $temp = gmp_export($this->value); - } else { - $temp = gmp_strval(gmp_abs($this->value), 16); - $temp = (strlen($temp) & 1) ? '0' . $temp : $temp; - $temp = pack('H*', $temp); - } - - return $this->precision > 0 ? - substr(str_pad($temp, $this->precision >> 3, chr(0), STR_PAD_LEFT), -($this->precision >> 3)) : - ltrim($temp, chr(0)); - case self::MODE_BCMATH: - if ($this->value === '0') { - return $this->precision > 0 ? str_repeat(chr(0), ($this->precision + 1) >> 3) : ''; - } - - $value = ''; - $current = $this->value; - - if ($current[0] == '-') { - $current = substr($current, 1); - } - - while (bccomp($current, '0', 0) > 0) { - $temp = bcmod($current, '16777216'); - $value = chr($temp >> 16) . chr($temp >> 8) . chr($temp) . $value; - $current = bcdiv($current, '16777216', 0); - } - - return $this->precision > 0 ? - substr(str_pad($value, $this->precision >> 3, chr(0), STR_PAD_LEFT), -($this->precision >> 3)) : - ltrim($value, chr(0)); - } - - if (!count($this->value)) { - return $this->precision > 0 ? str_repeat(chr(0), ($this->precision + 1) >> 3) : ''; - } - $result = $this->_int2bytes($this->value[count($this->value) - 1]); - - $temp = $this->copy(); + self::initialize_static_variables(); - for ($i = count($temp->value) - 2; $i >= 0; --$i) { - $temp->_base256_lshift($result, self::$base); - $result = $result | str_pad($temp->_int2bytes($temp->value[$i]), strlen($result), chr(0), STR_PAD_LEFT); - } - - return $this->precision > 0 ? - str_pad(substr($result, -(($this->precision + 7) >> 3)), ($this->precision + 7) >> 3, chr(0), STR_PAD_LEFT) : - $result; + return self::$engines; } /** - * Converts a BigInteger to a hex string (eg. base-16)). - * - * Negative numbers are saved as positive numbers, unless $twos_compliment is set to true, at which point, they're - * saved as two's compliment. - * - * Here's an example: - * - * toHex(); // outputs '41' - * ?> - * - * - * @param bool $twos_compliment - * @return string - * @access public - * @internal Converts a base-2**26 number to base-2**8 + * Initialize static variables */ - function toHex($twos_compliment = false) + private static function initialize_static_variables() { - return bin2hex($this->toBytes($twos_compliment)); + if (!isset(self::$mainEngine)) { + $engines = [ + ['GMP'], + ['PHP64', ['OpenSSL']], + ['BCMath', ['OpenSSL']], + ['PHP32', ['OpenSSL']] + ]; + foreach ($engines as $engine) { + try { + self::setEngine($engine[0], isset($engine[1]) ? $engine[1] : []); + break; + } catch (\Exception $e) { + } + } + } } /** - * Converts a BigInteger to a bit string (eg. base-2). - * - * Negative numbers are saved as positive numbers, unless $twos_compliment is set to true, at which point, they're - * saved as two's compliment. - * - * Here's an example: - * - * toBits(); // outputs '1000001' - * ?> - * + * If the second parameter - $base - is negative, then it will be assumed that the number's are encoded using + * two's compliment. The sole exception to this is -10, which is treated the same as 10 is. * - * @param bool $twos_compliment - * @return string - * @access public - * @internal Converts a base-2**26 number to base-2**2 + * @param string|int|BigInteger\Engines\Engine $x Base-10 number or base-$base number if $base set. + * @param int $base */ - function toBits($twos_compliment = false) + public function __construct($x = 0, $base = 10) { - $hex = $this->toHex($twos_compliment); - $bits = ''; - for ($i = strlen($hex) - 6, $start = strlen($hex) % 6; $i >= $start; $i-=6) { - $bits = str_pad(decbin(hexdec(substr($hex, $i, 6))), 24, '0', STR_PAD_LEFT) . $bits; - } - if ($start) { // hexdec('') == 0 - $bits = str_pad(decbin(hexdec(substr($hex, 0, $start))), 8 * $start, '0', STR_PAD_LEFT) . $bits; - } - $result = $this->precision > 0 ? substr($bits, -$this->precision) : ltrim($bits, '0'); + self::initialize_static_variables(); - if ($twos_compliment && $this->compare(new static()) > 0 && $this->precision <= 0) { - return '0' . $result; + if ($x instanceof self::$mainEngine) { + $this->value = clone $x; + } elseif ($x instanceof BigInteger\Engines\Engine) { + $this->value = new static("$x"); + $this->value->setPrecision($x->getPrecision()); + } else { + $this->value = new self::$mainEngine($x, $base); } - - return $result; } /** * Converts a BigInteger to a base-10 number. * - * Here's an example: - * - * toString(); // outputs 50 - * ?> - * - * * @return string - * @access public - * @internal Converts a base-2**26 number to base-10**7 (which is pretty much base-10) - */ - function toString() - { - switch (MATH_BIGINTEGER_MODE) { - case self::MODE_GMP: - return gmp_strval($this->value); - case self::MODE_BCMATH: - if ($this->value === '0') { - return '0'; - } - - return ltrim($this->value, '0'); - } - - if (!count($this->value)) { - return '0'; - } - - $temp = $this->copy(); - $temp->bitmask = false; - $temp->is_negative = false; - - $divisor = new static(); - $divisor->value = array(self::$max10); - $result = ''; - while (count($temp->value)) { - list($temp, $mod) = $temp->divide($divisor); - $result = str_pad(isset($mod->value[0]) ? $mod->value[0] : '', self::$max10Len, '0', STR_PAD_LEFT) . $result; - } - $result = ltrim($result, '0'); - if (empty($result)) { - $result = '0'; - } - - if ($this->is_negative) { - $result = '-' . $result; - } - - return $result; - } - - /** - * Copy an object - * - * PHP5 passes objects by reference while PHP4 passes by value. As such, we need a function to guarantee - * that all objects are passed by value, when appropriate. More information can be found here: - * - * {@link http://php.net/language.oop5.basic#51624} - * - * @access public - * @see self::__clone() - * @return \phpseclib\Math\BigInteger */ - function copy() + public function toString() { - $temp = new static(); - $temp->value = $this->value; - $temp->is_negative = $this->is_negative; - $temp->precision = $this->precision; - $temp->bitmask = $this->bitmask; - return $temp; + return $this->value->toString(); } /** * __toString() magic method - * - * Will be called, automatically, if you're supporting just PHP5. If you're supporting PHP4, you'll need to call - * toString(). - * - * @access public - * @internal Implemented per a suggestion by Techie-Michael - thanks! */ - function __toString() + public function __toString() { - return $this->toString(); + return (string)$this->value; } /** - * __clone() magic method - * - * Although you can call BigInteger::__toString() directly in PHP5, you cannot call BigInteger::__clone() directly - * in PHP5. You can in PHP4 since it's not a magic method, but in PHP5, you have to call it by using the PHP5 - * only syntax of $y = clone $x. As such, if you're trying to write an application that works on both PHP4 and - * PHP5, call BigInteger::copy(), instead. + * __debugInfo() magic method * - * @access public - * @see self::copy() - * @return \phpseclib\Math\BigInteger + * Will be called, automatically, when print_r() or var_dump() are called */ - function __clone() + public function __debugInfo() { - return $this->copy(); + return $this->value->__debugInfo(); } /** - * __sleep() magic method - * - * Will be called, automatically, when serialize() is called on a BigInteger object. + * Converts a BigInteger to a byte string (eg. base-256). * - * @see self::__wakeup() - * @access public + * @param bool $twos_compliment + * @return string */ - function __sleep() + public function toBytes($twos_compliment = false) { - $this->hex = $this->toHex(true); - $vars = array('hex'); - if ($this->precision > 0) { - $vars[] = 'precision'; - } - return $vars; + return $this->value->toBytes($twos_compliment); } /** - * __wakeup() magic method + * Converts a BigInteger to a hex string (eg. base-16). * - * Will be called, automatically, when unserialize() is called on a BigInteger object. - * - * @see self::__sleep() - * @access public + * @param bool $twos_compliment + * @return string */ - function __wakeup() + public function toHex($twos_compliment = false) { - $temp = new static($this->hex, -16); - $this->value = $temp->value; - $this->is_negative = $temp->is_negative; - if ($this->precision > 0) { - // recalculate $this->bitmask - $this->setPrecision($this->precision); - } + return $this->value->toHex($twos_compliment); } /** - * __debugInfo() magic method + * Converts a BigInteger to a bit string (eg. base-2). * - * Will be called, automatically, when print_r() or var_dump() are called + * Negative numbers are saved as positive numbers, unless $twos_compliment is set to true, at which point, they're + * saved as two's compliment. * - * @access public - */ - function __debugInfo() - { - $opts = array(); - switch (MATH_BIGINTEGER_MODE) { - case self::MODE_GMP: - $engine = 'gmp'; - break; - case self::MODE_BCMATH: - $engine = 'bcmath'; - break; - case self::MODE_INTERNAL: - $engine = 'internal'; - $opts[] = PHP_INT_SIZE == 8 ? '64-bit' : '32-bit'; - } - if (MATH_BIGINTEGER_MODE != self::MODE_GMP && defined('MATH_BIGINTEGER_OPENSSL_ENABLED')) { - $opts[] = 'OpenSSL'; - } - if (!empty($opts)) { - $engine.= ' (' . implode('.', $opts) . ')'; - } - return array( - 'value' => '0x' . $this->toHex(true), - 'engine' => $engine - ); - } - - /** - * Adds two BigIntegers. - * - * Here's an example: - * - * add($b); - * - * echo $c->toString(); // outputs 30 - * ?> - * - * - * @param \phpseclib\Math\BigInteger $y - * @return \phpseclib\Math\BigInteger - * @access public - * @internal Performs base-2**52 addition - */ - function add($y) - { - switch (MATH_BIGINTEGER_MODE) { - case self::MODE_GMP: - $temp = new static(); - $temp->value = gmp_add($this->value, $y->value); - - return $this->_normalize($temp); - case self::MODE_BCMATH: - $temp = new static(); - $temp->value = bcadd($this->value, $y->value, 0); - - return $this->_normalize($temp); - } - - $temp = $this->_add($this->value, $this->is_negative, $y->value, $y->is_negative); - - $result = new static(); - $result->value = $temp[self::VALUE]; - $result->is_negative = $temp[self::SIGN]; - - return $this->_normalize($result); - } - - /** - * Performs addition. - * - * @param array $x_value - * @param bool $x_negative - * @param array $y_value - * @param bool $y_negative - * @return array - * @access private - */ - function _add($x_value, $x_negative, $y_value, $y_negative) - { - $x_size = count($x_value); - $y_size = count($y_value); - - if ($x_size == 0) { - return array( - self::VALUE => $y_value, - self::SIGN => $y_negative - ); - } elseif ($y_size == 0) { - return array( - self::VALUE => $x_value, - self::SIGN => $x_negative - ); - } - - // subtract, if appropriate - if ($x_negative != $y_negative) { - if ($x_value == $y_value) { - return array( - self::VALUE => array(), - self::SIGN => false - ); - } - - $temp = $this->_subtract($x_value, false, $y_value, false); - $temp[self::SIGN] = $this->_compare($x_value, false, $y_value, false) > 0 ? - $x_negative : $y_negative; - - return $temp; - } - - if ($x_size < $y_size) { - $size = $x_size; - $value = $y_value; - } else { - $size = $y_size; - $value = $x_value; - } - - $value[count($value)] = 0; // just in case the carry adds an extra digit - - $carry = 0; - for ($i = 0, $j = 1; $j < $size; $i+=2, $j+=2) { - $sum = $x_value[$j] * self::$baseFull + $x_value[$i] + $y_value[$j] * self::$baseFull + $y_value[$i] + $carry; - $carry = $sum >= self::$maxDigit2; // eg. floor($sum / 2**52); only possible values (in any base) are 0 and 1 - $sum = $carry ? $sum - self::$maxDigit2 : $sum; - - $temp = self::$base === 26 ? intval($sum / 0x4000000) : ($sum >> 31); - - $value[$i] = (int) ($sum - self::$baseFull * $temp); // eg. a faster alternative to fmod($sum, 0x4000000) - $value[$j] = $temp; - } - - if ($j == $size) { // ie. if $y_size is odd - $sum = $x_value[$i] + $y_value[$i] + $carry; - $carry = $sum >= self::$baseFull; - $value[$i] = $carry ? $sum - self::$baseFull : $sum; - ++$i; // ie. let $i = $j since we've just done $value[$i] - } - - if ($carry) { - for (; $value[$i] == self::$maxDigit; ++$i) { - $value[$i] = 0; - } - ++$value[$i]; - } - - return array( - self::VALUE => $this->_trim($value), - self::SIGN => $x_negative - ); - } - - /** - * Subtracts two BigIntegers. - * - * Here's an example: - * - * subtract($b); - * - * echo $c->toString(); // outputs -10 - * ?> - * - * - * @param \phpseclib\Math\BigInteger $y - * @return \phpseclib\Math\BigInteger - * @access public - * @internal Performs base-2**52 subtraction - */ - function subtract($y) - { - switch (MATH_BIGINTEGER_MODE) { - case self::MODE_GMP: - $temp = new static(); - $temp->value = gmp_sub($this->value, $y->value); - - return $this->_normalize($temp); - case self::MODE_BCMATH: - $temp = new static(); - $temp->value = bcsub($this->value, $y->value, 0); - - return $this->_normalize($temp); - } - - $temp = $this->_subtract($this->value, $this->is_negative, $y->value, $y->is_negative); - - $result = new static(); - $result->value = $temp[self::VALUE]; - $result->is_negative = $temp[self::SIGN]; - - return $this->_normalize($result); - } - - /** - * Performs subtraction. - * - * @param array $x_value - * @param bool $x_negative - * @param array $y_value - * @param bool $y_negative - * @return array - * @access private - */ - function _subtract($x_value, $x_negative, $y_value, $y_negative) - { - $x_size = count($x_value); - $y_size = count($y_value); - - if ($x_size == 0) { - return array( - self::VALUE => $y_value, - self::SIGN => !$y_negative - ); - } elseif ($y_size == 0) { - return array( - self::VALUE => $x_value, - self::SIGN => $x_negative - ); - } - - // add, if appropriate (ie. -$x - +$y or +$x - -$y) - if ($x_negative != $y_negative) { - $temp = $this->_add($x_value, false, $y_value, false); - $temp[self::SIGN] = $x_negative; - - return $temp; - } - - $diff = $this->_compare($x_value, $x_negative, $y_value, $y_negative); - - if (!$diff) { - return array( - self::VALUE => array(), - self::SIGN => false - ); - } - - // switch $x and $y around, if appropriate. - if ((!$x_negative && $diff < 0) || ($x_negative && $diff > 0)) { - $temp = $x_value; - $x_value = $y_value; - $y_value = $temp; - - $x_negative = !$x_negative; - - $x_size = count($x_value); - $y_size = count($y_value); - } - - // at this point, $x_value should be at least as big as - if not bigger than - $y_value - - $carry = 0; - for ($i = 0, $j = 1; $j < $y_size; $i+=2, $j+=2) { - $sum = $x_value[$j] * self::$baseFull + $x_value[$i] - $y_value[$j] * self::$baseFull - $y_value[$i] - $carry; - $carry = $sum < 0; // eg. floor($sum / 2**52); only possible values (in any base) are 0 and 1 - $sum = $carry ? $sum + self::$maxDigit2 : $sum; - - $temp = self::$base === 26 ? intval($sum / 0x4000000) : ($sum >> 31); - - $x_value[$i] = (int) ($sum - self::$baseFull * $temp); - $x_value[$j] = $temp; - } - - if ($j == $y_size) { // ie. if $y_size is odd - $sum = $x_value[$i] - $y_value[$i] - $carry; - $carry = $sum < 0; - $x_value[$i] = $carry ? $sum + self::$baseFull : $sum; - ++$i; - } - - if ($carry) { - for (; !$x_value[$i]; ++$i) { - $x_value[$i] = self::$maxDigit; - } - --$x_value[$i]; - } - - return array( - self::VALUE => $this->_trim($x_value), - self::SIGN => $x_negative - ); - } - - /** - * Multiplies two BigIntegers - * - * Here's an example: - * - * multiply($b); - * - * echo $c->toString(); // outputs 200 - * ?> - * - * - * @param \phpseclib\Math\BigInteger $x - * @return \phpseclib\Math\BigInteger - * @access public - */ - function multiply($x) - { - switch (MATH_BIGINTEGER_MODE) { - case self::MODE_GMP: - $temp = new static(); - $temp->value = gmp_mul($this->value, $x->value); - - return $this->_normalize($temp); - case self::MODE_BCMATH: - $temp = new static(); - $temp->value = bcmul($this->value, $x->value, 0); - - return $this->_normalize($temp); - } - - $temp = $this->_multiply($this->value, $this->is_negative, $x->value, $x->is_negative); - - $product = new static(); - $product->value = $temp[self::VALUE]; - $product->is_negative = $temp[self::SIGN]; - - return $this->_normalize($product); - } - - /** - * Performs multiplication. - * - * @param array $x_value - * @param bool $x_negative - * @param array $y_value - * @param bool $y_negative - * @return array - * @access private - */ - function _multiply($x_value, $x_negative, $y_value, $y_negative) - { - //if ( $x_value == $y_value ) { - // return array( - // self::VALUE => $this->_square($x_value), - // self::SIGN => $x_sign != $y_value - // ); - //} - - $x_length = count($x_value); - $y_length = count($y_value); - - if (!$x_length || !$y_length) { // a 0 is being multiplied - return array( - self::VALUE => array(), - self::SIGN => false - ); - } - - return array( - self::VALUE => min($x_length, $y_length) < 2 * self::KARATSUBA_CUTOFF ? - $this->_trim($this->_regularMultiply($x_value, $y_value)) : - $this->_trim($this->_karatsuba($x_value, $y_value)), - self::SIGN => $x_negative != $y_negative - ); - } - - /** - * Performs long multiplication on two BigIntegers - * - * Modeled after 'multiply' in MutableBigInteger.java. - * - * @param array $x_value - * @param array $y_value - * @return array - * @access private - */ - function _regularMultiply($x_value, $y_value) - { - $x_length = count($x_value); - $y_length = count($y_value); - - if (!$x_length || !$y_length) { // a 0 is being multiplied - return array(); - } - - if ($x_length < $y_length) { - $temp = $x_value; - $x_value = $y_value; - $y_value = $temp; - - $x_length = count($x_value); - $y_length = count($y_value); - } - - $product_value = $this->_array_repeat(0, $x_length + $y_length); - - // the following for loop could be removed if the for loop following it - // (the one with nested for loops) initially set $i to 0, but - // doing so would also make the result in one set of unnecessary adds, - // since on the outermost loops first pass, $product->value[$k] is going - // to always be 0 - - $carry = 0; - - for ($j = 0; $j < $x_length; ++$j) { // ie. $i = 0 - $temp = $x_value[$j] * $y_value[0] + $carry; // $product_value[$k] == 0 - $carry = self::$base === 26 ? intval($temp / 0x4000000) : ($temp >> 31); - $product_value[$j] = (int) ($temp - self::$baseFull * $carry); - } - - $product_value[$j] = $carry; - - // the above for loop is what the previous comment was talking about. the - // following for loop is the "one with nested for loops" - for ($i = 1; $i < $y_length; ++$i) { - $carry = 0; - - for ($j = 0, $k = $i; $j < $x_length; ++$j, ++$k) { - $temp = $product_value[$k] + $x_value[$j] * $y_value[$i] + $carry; - $carry = self::$base === 26 ? intval($temp / 0x4000000) : ($temp >> 31); - $product_value[$k] = (int) ($temp - self::$baseFull * $carry); - } - - $product_value[$k] = $carry; - } - - return $product_value; - } - - /** - * Performs Karatsuba multiplication on two BigIntegers - * - * See {@link http://en.wikipedia.org/wiki/Karatsuba_algorithm Karatsuba algorithm} and - * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=120 MPM 5.2.3}. - * - * @param array $x_value - * @param array $y_value - * @return array - * @access private - */ - function _karatsuba($x_value, $y_value) - { - $m = min(count($x_value) >> 1, count($y_value) >> 1); - - if ($m < self::KARATSUBA_CUTOFF) { - return $this->_regularMultiply($x_value, $y_value); - } - - $x1 = array_slice($x_value, $m); - $x0 = array_slice($x_value, 0, $m); - $y1 = array_slice($y_value, $m); - $y0 = array_slice($y_value, 0, $m); - - $z2 = $this->_karatsuba($x1, $y1); - $z0 = $this->_karatsuba($x0, $y0); - - $z1 = $this->_add($x1, false, $x0, false); - $temp = $this->_add($y1, false, $y0, false); - $z1 = $this->_karatsuba($z1[self::VALUE], $temp[self::VALUE]); - $temp = $this->_add($z2, false, $z0, false); - $z1 = $this->_subtract($z1, false, $temp[self::VALUE], false); - - $z2 = array_merge(array_fill(0, 2 * $m, 0), $z2); - $z1[self::VALUE] = array_merge(array_fill(0, $m, 0), $z1[self::VALUE]); - - $xy = $this->_add($z2, false, $z1[self::VALUE], $z1[self::SIGN]); - $xy = $this->_add($xy[self::VALUE], $xy[self::SIGN], $z0, false); - - return $xy[self::VALUE]; - } - - /** - * Performs squaring - * - * @param array $x - * @return array - * @access private - */ - function _square($x = false) - { - return count($x) < 2 * self::KARATSUBA_CUTOFF ? - $this->_trim($this->_baseSquare($x)) : - $this->_trim($this->_karatsubaSquare($x)); - } - - /** - * Performs traditional squaring on two BigIntegers - * - * Squaring can be done faster than multiplying a number by itself can be. See - * {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=7 HAC 14.2.4} / - * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=141 MPM 5.3} for more information. - * - * @param array $value - * @return array - * @access private - */ - function _baseSquare($value) - { - if (empty($value)) { - return array(); - } - $square_value = $this->_array_repeat(0, 2 * count($value)); - - for ($i = 0, $max_index = count($value) - 1; $i <= $max_index; ++$i) { - $i2 = $i << 1; - - $temp = $square_value[$i2] + $value[$i] * $value[$i]; - $carry = self::$base === 26 ? intval($temp / 0x4000000) : ($temp >> 31); - $square_value[$i2] = (int) ($temp - self::$baseFull * $carry); - - // note how we start from $i+1 instead of 0 as we do in multiplication. - for ($j = $i + 1, $k = $i2 + 1; $j <= $max_index; ++$j, ++$k) { - $temp = $square_value[$k] + 2 * $value[$j] * $value[$i] + $carry; - $carry = self::$base === 26 ? intval($temp / 0x4000000) : ($temp >> 31); - $square_value[$k] = (int) ($temp - self::$baseFull * $carry); - } - - // the following line can yield values larger 2**15. at this point, PHP should switch - // over to floats. - $square_value[$i + $max_index + 1] = $carry; - } - - return $square_value; - } - - /** - * Performs Karatsuba "squaring" on two BigIntegers - * - * See {@link http://en.wikipedia.org/wiki/Karatsuba_algorithm Karatsuba algorithm} and - * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=151 MPM 5.3.4}. - * - * @param array $value - * @return array - * @access private - */ - function _karatsubaSquare($value) - { - $m = count($value) >> 1; - - if ($m < self::KARATSUBA_CUTOFF) { - return $this->_baseSquare($value); - } - - $x1 = array_slice($value, $m); - $x0 = array_slice($value, 0, $m); - - $z2 = $this->_karatsubaSquare($x1); - $z0 = $this->_karatsubaSquare($x0); - - $z1 = $this->_add($x1, false, $x0, false); - $z1 = $this->_karatsubaSquare($z1[self::VALUE]); - $temp = $this->_add($z2, false, $z0, false); - $z1 = $this->_subtract($z1, false, $temp[self::VALUE], false); - - $z2 = array_merge(array_fill(0, 2 * $m, 0), $z2); - $z1[self::VALUE] = array_merge(array_fill(0, $m, 0), $z1[self::VALUE]); - - $xx = $this->_add($z2, false, $z1[self::VALUE], $z1[self::SIGN]); - $xx = $this->_add($xx[self::VALUE], $xx[self::SIGN], $z0, false); - - return $xx[self::VALUE]; - } - - /** - * Divides two BigIntegers. - * - * Returns an array whose first element contains the quotient and whose second element contains the - * "common residue". If the remainder would be positive, the "common residue" and the remainder are the - * same. If the remainder would be negative, the "common residue" is equal to the sum of the remainder - * and the divisor (basically, the "common residue" is the first positive modulo). - * - * Here's an example: - * - * divide($b); - * - * echo $quotient->toString(); // outputs 0 - * echo "\r\n"; - * echo $remainder->toString(); // outputs 10 - * ?> - * - * - * @param \phpseclib\Math\BigInteger $y - * @return array - * @access public - * @internal This function is based off of {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=9 HAC 14.20}. - */ - function divide($y) - { - switch (MATH_BIGINTEGER_MODE) { - case self::MODE_GMP: - $quotient = new static(); - $remainder = new static(); - - list($quotient->value, $remainder->value) = gmp_div_qr($this->value, $y->value); - - if (gmp_sign($remainder->value) < 0) { - $remainder->value = gmp_add($remainder->value, gmp_abs($y->value)); - } - - return array($this->_normalize($quotient), $this->_normalize($remainder)); - case self::MODE_BCMATH: - $quotient = new static(); - $remainder = new static(); - - $quotient->value = bcdiv($this->value, $y->value, 0); - $remainder->value = bcmod($this->value, $y->value); - - if ($remainder->value[0] == '-') { - $remainder->value = bcadd($remainder->value, $y->value[0] == '-' ? substr($y->value, 1) : $y->value, 0); - } - - return array($this->_normalize($quotient), $this->_normalize($remainder)); - } - - if (count($y->value) == 1) { - list($q, $r) = $this->_divide_digit($this->value, $y->value[0]); - $quotient = new static(); - $remainder = new static(); - $quotient->value = $q; - $remainder->value = array($r); - $quotient->is_negative = $this->is_negative != $y->is_negative; - return array($this->_normalize($quotient), $this->_normalize($remainder)); - } - - static $zero; - if (!isset($zero)) { - $zero = new static(); - } - - $x = $this->copy(); - $y = $y->copy(); - - $x_sign = $x->is_negative; - $y_sign = $y->is_negative; - - $x->is_negative = $y->is_negative = false; - - $diff = $x->compare($y); - - if (!$diff) { - $temp = new static(); - $temp->value = array(1); - $temp->is_negative = $x_sign != $y_sign; - return array($this->_normalize($temp), $this->_normalize(new static())); - } - - if ($diff < 0) { - // if $x is negative, "add" $y. - if ($x_sign) { - $x = $y->subtract($x); - } - return array($this->_normalize(new static()), $this->_normalize($x)); - } - - // normalize $x and $y as described in HAC 14.23 / 14.24 - $msb = $y->value[count($y->value) - 1]; - for ($shift = 0; !($msb & self::$msb); ++$shift) { - $msb <<= 1; - } - $x->_lshift($shift); - $y->_lshift($shift); - $y_value = &$y->value; - - $x_max = count($x->value) - 1; - $y_max = count($y->value) - 1; - - $quotient = new static(); - $quotient_value = &$quotient->value; - $quotient_value = $this->_array_repeat(0, $x_max - $y_max + 1); - - static $temp, $lhs, $rhs; - if (!isset($temp)) { - $temp = new static(); - $lhs = new static(); - $rhs = new static(); - } - $temp_value = &$temp->value; - $rhs_value = &$rhs->value; - - // $temp = $y << ($x_max - $y_max-1) in base 2**26 - $temp_value = array_merge($this->_array_repeat(0, $x_max - $y_max), $y_value); - - while ($x->compare($temp) >= 0) { - // calculate the "common residue" - ++$quotient_value[$x_max - $y_max]; - $x = $x->subtract($temp); - $x_max = count($x->value) - 1; - } - - for ($i = $x_max; $i >= $y_max + 1; --$i) { - $x_value = &$x->value; - $x_window = array( - isset($x_value[$i]) ? $x_value[$i] : 0, - isset($x_value[$i - 1]) ? $x_value[$i - 1] : 0, - isset($x_value[$i - 2]) ? $x_value[$i - 2] : 0 - ); - $y_window = array( - $y_value[$y_max], - ($y_max > 0) ? $y_value[$y_max - 1] : 0 - ); - - $q_index = $i - $y_max - 1; - if ($x_window[0] == $y_window[0]) { - $quotient_value[$q_index] = self::$maxDigit; - } else { - $quotient_value[$q_index] = $this->_safe_divide( - $x_window[0] * self::$baseFull + $x_window[1], - $y_window[0] - ); - } - - $temp_value = array($y_window[1], $y_window[0]); - - $lhs->value = array($quotient_value[$q_index]); - $lhs = $lhs->multiply($temp); - - $rhs_value = array($x_window[2], $x_window[1], $x_window[0]); - - while ($lhs->compare($rhs) > 0) { - --$quotient_value[$q_index]; - - $lhs->value = array($quotient_value[$q_index]); - $lhs = $lhs->multiply($temp); - } - - $adjust = $this->_array_repeat(0, $q_index); - $temp_value = array($quotient_value[$q_index]); - $temp = $temp->multiply($y); - $temp_value = &$temp->value; - if (count($temp_value)) { - $temp_value = array_merge($adjust, $temp_value); - } - - $x = $x->subtract($temp); - - if ($x->compare($zero) < 0) { - $temp_value = array_merge($adjust, $y_value); - $x = $x->add($temp); - - --$quotient_value[$q_index]; - } - - $x_max = count($x_value) - 1; - } - - // unnormalize the remainder - $x->_rshift($shift); - - $quotient->is_negative = $x_sign != $y_sign; - - // calculate the "common residue", if appropriate - if ($x_sign) { - $y->_rshift($shift); - $x = $y->subtract($x); - } - - return array($this->_normalize($quotient), $this->_normalize($x)); - } - - /** - * Divides a BigInteger by a regular integer - * - * abc / x = a00 / x + b0 / x + c / x - * - * @param array $dividend - * @param array $divisor - * @return array - * @access private - */ - function _divide_digit($dividend, $divisor) - { - $carry = 0; - $result = array(); - - for ($i = count($dividend) - 1; $i >= 0; --$i) { - $temp = self::$baseFull * $carry + $dividend[$i]; - $result[$i] = $this->_safe_divide($temp, $divisor); - $carry = (int) ($temp - $divisor * $result[$i]); - } - - return array($result, $carry); - } - - /** - * Performs modular exponentiation. - * - * Here's an example: - * - * modPow($b, $c); - * - * echo $c->toString(); // outputs 10 - * ?> - * - * - * @param \phpseclib\Math\BigInteger $e - * @param \phpseclib\Math\BigInteger $n - * @return \phpseclib\Math\BigInteger - * @access public - * @internal The most naive approach to modular exponentiation has very unreasonable requirements, and - * and although the approach involving repeated squaring does vastly better, it, too, is impractical - * for our purposes. The reason being that division - by far the most complicated and time-consuming - * of the basic operations (eg. +,-,*,/) - occurs multiple times within it. - * - * Modular reductions resolve this issue. Although an individual modular reduction takes more time - * then an individual division, when performed in succession (with the same modulo), they're a lot faster. - * - * The two most commonly used modular reductions are Barrett and Montgomery reduction. Montgomery reduction, - * although faster, only works when the gcd of the modulo and of the base being used is 1. In RSA, when the - * base is a power of two, the modulo - a product of two primes - is always going to have a gcd of 1 (because - * the product of two odd numbers is odd), but what about when RSA isn't used? - * - * In contrast, Barrett reduction has no such constraint. As such, some bigint implementations perform a - * Barrett reduction after every operation in the modpow function. Others perform Barrett reductions when the - * modulo is even and Montgomery reductions when the modulo is odd. BigInteger.java's modPow method, however, - * uses a trick involving the Chinese Remainder Theorem to factor the even modulo into two numbers - one odd and - * the other, a power of two - and recombine them, later. This is the method that this modPow function uses. - * {@link http://islab.oregonstate.edu/papers/j34monex.pdf Montgomery Reduction with Even Modulus} elaborates. - */ - function modPow($e, $n) - { - $n = $this->bitmask !== false && $this->bitmask->compare($n) < 0 ? $this->bitmask : $n->abs(); - - if ($e->compare(new static()) < 0) { - $e = $e->abs(); - - $temp = $this->modInverse($n); - if ($temp === false) { - return false; - } - - return $this->_normalize($temp->modPow($e, $n)); - } - - if (MATH_BIGINTEGER_MODE == self::MODE_GMP) { - $temp = new static(); - $temp->value = gmp_powm($this->value, $e->value, $n->value); - - return $this->_normalize($temp); - } - - if ($this->compare(new static()) < 0 || $this->compare($n) > 0) { - list(, $temp) = $this->divide($n); - return $temp->modPow($e, $n); - } - - if (defined('MATH_BIGINTEGER_OPENSSL_ENABLED')) { - $components = array( - 'modulus' => $n->toBytes(true), - 'publicExponent' => $e->toBytes(true) - ); - - $components = array( - 'modulus' => pack('Ca*a*', 2, $this->_encodeASN1Length(strlen($components['modulus'])), $components['modulus']), - 'publicExponent' => pack('Ca*a*', 2, $this->_encodeASN1Length(strlen($components['publicExponent'])), $components['publicExponent']) - ); - - $RSAPublicKey = pack( - 'Ca*a*a*', - 48, - $this->_encodeASN1Length(strlen($components['modulus']) + strlen($components['publicExponent'])), - $components['modulus'], - $components['publicExponent'] - ); - - $rsaOID = pack('H*', '300d06092a864886f70d0101010500'); // hex version of MA0GCSqGSIb3DQEBAQUA - $RSAPublicKey = chr(0) . $RSAPublicKey; - $RSAPublicKey = chr(3) . $this->_encodeASN1Length(strlen($RSAPublicKey)) . $RSAPublicKey; - - $encapsulated = pack( - 'Ca*a*', - 48, - $this->_encodeASN1Length(strlen($rsaOID . $RSAPublicKey)), - $rsaOID . $RSAPublicKey - ); - - $RSAPublicKey = "-----BEGIN PUBLIC KEY-----\r\n" . - chunk_split(base64_encode($encapsulated)) . - '-----END PUBLIC KEY-----'; - - $plaintext = str_pad($this->toBytes(), strlen($n->toBytes(true)) - 1, "\0", STR_PAD_LEFT); - - if (openssl_public_encrypt($plaintext, $result, $RSAPublicKey, OPENSSL_NO_PADDING)) { - return new static($result, 256); - } - } - - if (MATH_BIGINTEGER_MODE == self::MODE_BCMATH) { - $temp = new static(); - $temp->value = bcpowmod($this->value, $e->value, $n->value, 0); - - return $this->_normalize($temp); - } - - if (empty($e->value)) { - $temp = new static(); - $temp->value = array(1); - return $this->_normalize($temp); - } - - if ($e->value == array(1)) { - list(, $temp) = $this->divide($n); - return $this->_normalize($temp); - } - - if ($e->value == array(2)) { - $temp = new static(); - $temp->value = $this->_square($this->value); - list(, $temp) = $temp->divide($n); - return $this->_normalize($temp); - } - - return $this->_normalize($this->_slidingWindow($e, $n, self::BARRETT)); - - // the following code, although not callable, can be run independently of the above code - // although the above code performed better in my benchmarks the following could might - // perform better under different circumstances. in lieu of deleting it it's just been - // made uncallable - - // is the modulo odd? - if ($n->value[0] & 1) { - return $this->_normalize($this->_slidingWindow($e, $n, self::MONTGOMERY)); - } - // if it's not, it's even - - // find the lowest set bit (eg. the max pow of 2 that divides $n) - for ($i = 0; $i < count($n->value); ++$i) { - if ($n->value[$i]) { - $temp = decbin($n->value[$i]); - $j = strlen($temp) - strrpos($temp, '1') - 1; - $j+= 26 * $i; - break; - } - } - // at this point, 2^$j * $n/(2^$j) == $n - - $mod1 = $n->copy(); - $mod1->_rshift($j); - $mod2 = new static(); - $mod2->value = array(1); - $mod2->_lshift($j); - - $part1 = ($mod1->value != array(1)) ? $this->_slidingWindow($e, $mod1, self::MONTGOMERY) : new static(); - $part2 = $this->_slidingWindow($e, $mod2, self::POWEROF2); - - $y1 = $mod2->modInverse($mod1); - $y2 = $mod1->modInverse($mod2); - - $result = $part1->multiply($mod2); - $result = $result->multiply($y1); - - $temp = $part2->multiply($mod1); - $temp = $temp->multiply($y2); - - $result = $result->add($temp); - list(, $result) = $result->divide($n); - - return $this->_normalize($result); - } - - /** - * Performs modular exponentiation. - * - * Alias for modPow(). - * - * @param \phpseclib\Math\BigInteger $e - * @param \phpseclib\Math\BigInteger $n - * @return \phpseclib\Math\BigInteger - * @access public - */ - function powMod($e, $n) - { - return $this->modPow($e, $n); - } - - /** - * Sliding Window k-ary Modular Exponentiation - * - * Based on {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=27 HAC 14.85} / - * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=210 MPM 7.7}. In a departure from those algorithims, - * however, this function performs a modular reduction after every multiplication and squaring operation. - * As such, this function has the same preconditions that the reductions being used do. - * - * @param \phpseclib\Math\BigInteger $e - * @param \phpseclib\Math\BigInteger $n - * @param int $mode - * @return \phpseclib\Math\BigInteger - * @access private - */ - function _slidingWindow($e, $n, $mode) - { - static $window_ranges = array(7, 25, 81, 241, 673, 1793); // from BigInteger.java's oddModPow function - //static $window_ranges = array(0, 7, 36, 140, 450, 1303, 3529); // from MPM 7.3.1 - - $e_value = $e->value; - $e_length = count($e_value) - 1; - $e_bits = decbin($e_value[$e_length]); - for ($i = $e_length - 1; $i >= 0; --$i) { - $e_bits.= str_pad(decbin($e_value[$i]), self::$base, '0', STR_PAD_LEFT); - } - - $e_length = strlen($e_bits); - - // calculate the appropriate window size. - // $window_size == 3 if $window_ranges is between 25 and 81, for example. - for ($i = 0, $window_size = 1; $i < count($window_ranges) && $e_length > $window_ranges[$i]; ++$window_size, ++$i) { - } - - $n_value = $n->value; - - // precompute $this^0 through $this^$window_size - $powers = array(); - $powers[1] = $this->_prepareReduce($this->value, $n_value, $mode); - $powers[2] = $this->_squareReduce($powers[1], $n_value, $mode); - - // we do every other number since substr($e_bits, $i, $j+1) (see below) is supposed to end - // in a 1. ie. it's supposed to be odd. - $temp = 1 << ($window_size - 1); - for ($i = 1; $i < $temp; ++$i) { - $i2 = $i << 1; - $powers[$i2 + 1] = $this->_multiplyReduce($powers[$i2 - 1], $powers[2], $n_value, $mode); - } - - $result = array(1); - $result = $this->_prepareReduce($result, $n_value, $mode); - - for ($i = 0; $i < $e_length;) { - if (!$e_bits[$i]) { - $result = $this->_squareReduce($result, $n_value, $mode); - ++$i; - } else { - for ($j = $window_size - 1; $j > 0; --$j) { - if (!empty($e_bits[$i + $j])) { - break; - } - } - - // eg. the length of substr($e_bits, $i, $j + 1) - for ($k = 0; $k <= $j; ++$k) { - $result = $this->_squareReduce($result, $n_value, $mode); - } - - $result = $this->_multiplyReduce($result, $powers[bindec(substr($e_bits, $i, $j + 1))], $n_value, $mode); - - $i += $j + 1; - } - } - - $temp = new static(); - $temp->value = $this->_reduce($result, $n_value, $mode); - - return $temp; - } - - /** - * Modular reduction - * - * For most $modes this will return the remainder. - * - * @see self::_slidingWindow() - * @access private - * @param array $x - * @param array $n - * @param int $mode - * @return array + * @param bool $twos_compliment + * @return string */ - function _reduce($x, $n, $mode) + public function toBits($twos_compliment = false) { - switch ($mode) { - case self::MONTGOMERY: - return $this->_montgomery($x, $n); - case self::BARRETT: - return $this->_barrett($x, $n); - case self::POWEROF2: - $lhs = new static(); - $lhs->value = $x; - $rhs = new static(); - $rhs->value = $n; - return $x->_mod2($n); - case self::CLASSIC: - $lhs = new static(); - $lhs->value = $x; - $rhs = new static(); - $rhs->value = $n; - list(, $temp) = $lhs->divide($rhs); - return $temp->value; - case self::NONE: - return $x; - default: - // an invalid $mode was provided - } + return $this->value->toBits($twos_compliment); } /** - * Modular reduction preperation + * Adds two BigIntegers. * - * @see self::_slidingWindow() - * @access private - * @param array $x - * @param array $n - * @param int $mode - * @return array + * @param BigInteger $y + * @return BigInteger */ - function _prepareReduce($x, $n, $mode) + public function add(BigInteger $y) { - if ($mode == self::MONTGOMERY) { - return $this->_prepMontgomery($x, $n); - } - return $this->_reduce($x, $n, $mode); + return new static($this->value->add($y->value)); } /** - * Modular multiply - * - * @see self::_slidingWindow() - * @access private - * @param array $x - * @param array $y - * @param array $n - * @param int $mode - * @return array + * Subtracts two BigIntegers. + * + * @param BigInteger $y + * @return BigInteger */ - function _multiplyReduce($x, $y, $n, $mode) + public function subtract(BigInteger $y) { - if ($mode == self::MONTGOMERY) { - return $this->_montgomeryMultiply($x, $y, $n); - } - $temp = $this->_multiply($x, false, $y, false); - return $this->_reduce($temp[self::VALUE], $n, $mode); + return new static($this->value->subtract($y->value)); } /** - * Modular square + * Multiplies two BigIntegers * - * @see self::_slidingWindow() - * @access private - * @param array $x - * @param array $n - * @param int $mode - * @return array + * @param BigInteger $x + * @return BigInteger */ - function _squareReduce($x, $n, $mode) + public function multiply(BigInteger $x) { - if ($mode == self::MONTGOMERY) { - return $this->_montgomeryMultiply($x, $x, $n); - } - return $this->_reduce($this->_square($x), $n, $mode); + return new static($this->value->multiply($x->value)); } /** - * Modulos for Powers of Two + * Divides two BigIntegers. + * + * Returns an array whose first element contains the quotient and whose second element contains the + * "common residue". If the remainder would be positive, the "common residue" and the remainder are the + * same. If the remainder would be negative, the "common residue" is equal to the sum of the remainder + * and the divisor (basically, the "common residue" is the first positive modulo). + * + * Here's an example: + * + * divide($b); * - * Calculates $x%$n, where $n = 2**$e, for some $e. Since this is basically the same as doing $x & ($n-1), - * we'll just use this function as a wrapper for doing that. + * echo $quotient->toString(); // outputs 0 + * echo "\r\n"; + * echo $remainder->toString(); // outputs 10 + * ?> + * * - * @see self::_slidingWindow() - * @access private - * @param \phpseclib\Math\BigInteger $n - * @return \phpseclib\Math\BigInteger + * @param BigInteger $y + * @return BigInteger[] */ - function _mod2($n) + public function divide(BigInteger $y) { - $temp = new static(); - $temp->value = array(1); - return $this->bitwise_and($n->subtract($temp)); + list($q, $r) = $this->value->divide($y->value); + return [ + new static($q), + new static($r) + ]; } /** - * Barrett Modular Reduction - * - * See {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=14 HAC 14.3.3} / - * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=165 MPM 6.2.5} for more information. Modified slightly, - * so as not to require negative numbers (initially, this script didn't support negative numbers). - * - * Employs "folding", as described at - * {@link http://www.cosic.esat.kuleuven.be/publications/thesis-149.pdf#page=66 thesis-149.pdf#page=66}. To quote from - * it, "the idea [behind folding] is to find a value x' such that x (mod m) = x' (mod m), with x' being smaller than x." - * - * Unfortunately, the "Barrett Reduction with Folding" algorithm described in thesis-149.pdf is not, as written, all that - * usable on account of (1) its not using reasonable radix points as discussed in - * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=162 MPM 6.2.2} and (2) the fact that, even with reasonable - * radix points, it only works when there are an even number of digits in the denominator. The reason for (2) is that - * (x >> 1) + (x >> 1) != x / 2 + x / 2. If x is even, they're the same, but if x is odd, they're not. See the in-line - * comments for details. - * - * @see self::_slidingWindow() - * @access private - * @param array $n - * @param array $m - * @return array + * Calculates modular inverses. + * + * Say you have (30 mod 17 * x mod 17) mod 17 == 1. x can be found using modular inverses. + * + * @param BigInteger $n + * @return BigInteger */ - function _barrett($n, $m) + public function modInverse(BigInteger $n) { - static $cache = array( - self::VARIABLE => array(), - self::DATA => array() - ); - - $m_length = count($m); - - // if ($this->_compare($n, $this->_square($m)) >= 0) { - if (count($n) > 2 * $m_length) { - $lhs = new static(); - $rhs = new static(); - $lhs->value = $n; - $rhs->value = $m; - list(, $temp) = $lhs->divide($rhs); - return $temp->value; - } - - // if (m.length >> 1) + 2 <= m.length then m is too small and n can't be reduced - if ($m_length < 5) { - return $this->_regularBarrett($n, $m); - } - - // n = 2 * m.length - - if (($key = array_search($m, $cache[self::VARIABLE])) === false) { - $key = count($cache[self::VARIABLE]); - $cache[self::VARIABLE][] = $m; - - $lhs = new static(); - $lhs_value = &$lhs->value; - $lhs_value = $this->_array_repeat(0, $m_length + ($m_length >> 1)); - $lhs_value[] = 1; - $rhs = new static(); - $rhs->value = $m; - - list($u, $m1) = $lhs->divide($rhs); - $u = $u->value; - $m1 = $m1->value; - - $cache[self::DATA][] = array( - 'u' => $u, // m.length >> 1 (technically (m.length >> 1) + 1) - 'm1'=> $m1 // m.length - ); - } else { - extract($cache[self::DATA][$key]); - } - - $cutoff = $m_length + ($m_length >> 1); - $lsd = array_slice($n, 0, $cutoff); // m.length + (m.length >> 1) - $msd = array_slice($n, $cutoff); // m.length >> 1 - $lsd = $this->_trim($lsd); - $temp = $this->_multiply($msd, false, $m1, false); - $n = $this->_add($lsd, false, $temp[self::VALUE], false); // m.length + (m.length >> 1) + 1 - - if ($m_length & 1) { - return $this->_regularBarrett($n[self::VALUE], $m); - } - - // (m.length + (m.length >> 1) + 1) - (m.length - 1) == (m.length >> 1) + 2 - $temp = array_slice($n[self::VALUE], $m_length - 1); - // if even: ((m.length >> 1) + 2) + (m.length >> 1) == m.length + 2 - // if odd: ((m.length >> 1) + 2) + (m.length >> 1) == (m.length - 1) + 2 == m.length + 1 - $temp = $this->_multiply($temp, false, $u, false); - // if even: (m.length + 2) - ((m.length >> 1) + 1) = m.length - (m.length >> 1) + 1 - // if odd: (m.length + 1) - ((m.length >> 1) + 1) = m.length - (m.length >> 1) - $temp = array_slice($temp[self::VALUE], ($m_length >> 1) + 1); - // if even: (m.length - (m.length >> 1) + 1) + m.length = 2 * m.length - (m.length >> 1) + 1 - // if odd: (m.length - (m.length >> 1)) + m.length = 2 * m.length - (m.length >> 1) - $temp = $this->_multiply($temp, false, $m, false); - - // at this point, if m had an odd number of digits, we'd be subtracting a 2 * m.length - (m.length >> 1) digit - // number from a m.length + (m.length >> 1) + 1 digit number. ie. there'd be an extra digit and the while loop - // following this comment would loop a lot (hence our calling _regularBarrett() in that situation). - - $result = $this->_subtract($n[self::VALUE], false, $temp[self::VALUE], false); - - while ($this->_compare($result[self::VALUE], $result[self::SIGN], $m, false) >= 0) { - $result = $this->_subtract($result[self::VALUE], $result[self::SIGN], $m, false); - } - - return $result[self::VALUE]; + return new static($this->value->modInverse($n->value)); } /** - * (Regular) Barrett Modular Reduction + * Calculates modular inverses. * - * For numbers with more than four digits BigInteger::_barrett() is faster. The difference between that and this - * is that this function does not fold the denominator into a smaller form. + * Say you have (30 mod 17 * x mod 17) mod 17 == 1. x can be found using modular inverses. * - * @see self::_slidingWindow() - * @access private - * @param array $x - * @param array $n - * @return array + * @param BigInteger $n + * @return BigInteger[] */ - function _regularBarrett($x, $n) + public function extendedGCD(BigInteger $n) { - static $cache = array( - self::VARIABLE => array(), - self::DATA => array() - ); - - $n_length = count($n); - - if (count($x) > 2 * $n_length) { - $lhs = new static(); - $rhs = new static(); - $lhs->value = $x; - $rhs->value = $n; - list(, $temp) = $lhs->divide($rhs); - return $temp->value; - } - - if (($key = array_search($n, $cache[self::VARIABLE])) === false) { - $key = count($cache[self::VARIABLE]); - $cache[self::VARIABLE][] = $n; - $lhs = new static(); - $lhs_value = &$lhs->value; - $lhs_value = $this->_array_repeat(0, 2 * $n_length); - $lhs_value[] = 1; - $rhs = new static(); - $rhs->value = $n; - list($temp, ) = $lhs->divide($rhs); // m.length - $cache[self::DATA][] = $temp->value; - } - - // 2 * m.length - (m.length - 1) = m.length + 1 - $temp = array_slice($x, $n_length - 1); - // (m.length + 1) + m.length = 2 * m.length + 1 - $temp = $this->_multiply($temp, false, $cache[self::DATA][$key], false); - // (2 * m.length + 1) - (m.length - 1) = m.length + 2 - $temp = array_slice($temp[self::VALUE], $n_length + 1); - - // m.length + 1 - $result = array_slice($x, 0, $n_length + 1); - // m.length + 1 - $temp = $this->_multiplyLower($temp, false, $n, false, $n_length + 1); - // $temp == array_slice($temp->_multiply($temp, false, $n, false)->value, 0, $n_length + 1) - - if ($this->_compare($result, false, $temp[self::VALUE], $temp[self::SIGN]) < 0) { - $corrector_value = $this->_array_repeat(0, $n_length + 1); - $corrector_value[count($corrector_value)] = 1; - $result = $this->_add($result, false, $corrector_value, false); - $result = $result[self::VALUE]; - } - - // at this point, we're subtracting a number with m.length + 1 digits from another number with m.length + 1 digits - $result = $this->_subtract($result, false, $temp[self::VALUE], $temp[self::SIGN]); - while ($this->_compare($result[self::VALUE], $result[self::SIGN], $n, false) > 0) { - $result = $this->_subtract($result[self::VALUE], $result[self::SIGN], $n, false); - } - - return $result[self::VALUE]; + extract($this->value->extendedGCD($n->value)); + /** + * @var BigInteger $gcd + * @var BigInteger $x + * @var BigInteger $y + */ + return [ + 'gcd' => new static($gcd), + 'x' => new static($x), + 'y' => new static($y) + ]; } /** - * Performs long multiplication up to $stop digits + * Calculates the greatest common divisor * - * If you're going to be doing array_slice($product->value, 0, $stop), some cycles can be saved. + * Say you have 693 and 609. The GCD is 21. * - * @see self::_regularBarrett() - * @param array $x_value - * @param bool $x_negative - * @param array $y_value - * @param bool $y_negative - * @param int $stop - * @return array - * @access private + * @param BigInteger $n + * @return BigInteger */ - function _multiplyLower($x_value, $x_negative, $y_value, $y_negative, $stop) + public function gcd(BigInteger $n) { - $x_length = count($x_value); - $y_length = count($y_value); - - if (!$x_length || !$y_length) { // a 0 is being multiplied - return array( - self::VALUE => array(), - self::SIGN => false - ); - } - - if ($x_length < $y_length) { - $temp = $x_value; - $x_value = $y_value; - $y_value = $temp; - - $x_length = count($x_value); - $y_length = count($y_value); - } - - $product_value = $this->_array_repeat(0, $x_length + $y_length); - - // the following for loop could be removed if the for loop following it - // (the one with nested for loops) initially set $i to 0, but - // doing so would also make the result in one set of unnecessary adds, - // since on the outermost loops first pass, $product->value[$k] is going - // to always be 0 - - $carry = 0; - - for ($j = 0; $j < $x_length; ++$j) { // ie. $i = 0, $k = $i - $temp = $x_value[$j] * $y_value[0] + $carry; // $product_value[$k] == 0 - $carry = self::$base === 26 ? intval($temp / 0x4000000) : ($temp >> 31); - $product_value[$j] = (int) ($temp - self::$baseFull * $carry); - } - - if ($j < $stop) { - $product_value[$j] = $carry; - } - - // the above for loop is what the previous comment was talking about. the - // following for loop is the "one with nested for loops" - - for ($i = 1; $i < $y_length; ++$i) { - $carry = 0; - - for ($j = 0, $k = $i; $j < $x_length && $k < $stop; ++$j, ++$k) { - $temp = $product_value[$k] + $x_value[$j] * $y_value[$i] + $carry; - $carry = self::$base === 26 ? intval($temp / 0x4000000) : ($temp >> 31); - $product_value[$k] = (int) ($temp - self::$baseFull * $carry); - } - - if ($k < $stop) { - $product_value[$k] = $carry; - } - } - - return array( - self::VALUE => $this->_trim($product_value), - self::SIGN => $x_negative != $y_negative - ); + return new static($this->value->gcd($n->value)); } /** - * Montgomery Modular Reduction - * - * ($x->_prepMontgomery($n))->_montgomery($n) yields $x % $n. - * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=170 MPM 6.3} provides insights on how this can be - * improved upon (basically, by using the comba method). gcd($n, 2) must be equal to one for this function - * to work correctly. - * - * @see self::_prepMontgomery() - * @see self::_slidingWindow() - * @access private - * @param array $x - * @param array $n - * @return array + * Absolute value. + * + * @return BigInteger */ - function _montgomery($x, $n) + public function abs() { - static $cache = array( - self::VARIABLE => array(), - self::DATA => array() - ); - - if (($key = array_search($n, $cache[self::VARIABLE])) === false) { - $key = count($cache[self::VARIABLE]); - $cache[self::VARIABLE][] = $x; - $cache[self::DATA][] = $this->_modInverse67108864($n); - } - - $k = count($n); - - $result = array(self::VALUE => $x); - - for ($i = 0; $i < $k; ++$i) { - $temp = $result[self::VALUE][$i] * $cache[self::DATA][$key]; - $temp = $temp - self::$baseFull * (self::$base === 26 ? intval($temp / 0x4000000) : ($temp >> 31)); - $temp = $this->_regularMultiply(array($temp), $n); - $temp = array_merge($this->_array_repeat(0, $i), $temp); - $result = $this->_add($result[self::VALUE], false, $temp, false); - } - - $result[self::VALUE] = array_slice($result[self::VALUE], $k); - - if ($this->_compare($result, false, $n, false) >= 0) { - $result = $this->_subtract($result[self::VALUE], false, $n, false); - } - - return $result[self::VALUE]; + return new static($this->value->abs()); } /** - * Montgomery Multiply + * Set Precision * - * Interleaves the montgomery reduction and long multiplication algorithms together as described in - * {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=13 HAC 14.36} + * Some bitwise operations give different results depending on the precision being used. Examples include left + * shift, not, and rotates. * - * @see self::_prepMontgomery() - * @see self::_montgomery() - * @access private - * @param array $x - * @param array $y - * @param array $m - * @return array + * @param int $bits */ - function _montgomeryMultiply($x, $y, $m) + public function setPrecision($bits) { - $temp = $this->_multiply($x, false, $y, false); - return $this->_montgomery($temp[self::VALUE], $m); - - // the following code, although not callable, can be run independently of the above code - // although the above code performed better in my benchmarks the following could might - // perform better under different circumstances. in lieu of deleting it it's just been - // made uncallable - - static $cache = array( - self::VARIABLE => array(), - self::DATA => array() - ); - - if (($key = array_search($m, $cache[self::VARIABLE])) === false) { - $key = count($cache[self::VARIABLE]); - $cache[self::VARIABLE][] = $m; - $cache[self::DATA][] = $this->_modInverse67108864($m); - } - - $n = max(count($x), count($y), count($m)); - $x = array_pad($x, $n, 0); - $y = array_pad($y, $n, 0); - $m = array_pad($m, $n, 0); - $a = array(self::VALUE => $this->_array_repeat(0, $n + 1)); - for ($i = 0; $i < $n; ++$i) { - $temp = $a[self::VALUE][0] + $x[$i] * $y[0]; - $temp = $temp - self::$baseFull * (self::$base === 26 ? intval($temp / 0x4000000) : ($temp >> 31)); - $temp = $temp * $cache[self::DATA][$key]; - $temp = $temp - self::$baseFull * (self::$base === 26 ? intval($temp / 0x4000000) : ($temp >> 31)); - $temp = $this->_add($this->_regularMultiply(array($x[$i]), $y), false, $this->_regularMultiply(array($temp), $m), false); - $a = $this->_add($a[self::VALUE], false, $temp[self::VALUE], false); - $a[self::VALUE] = array_slice($a[self::VALUE], 1); - } - if ($this->_compare($a[self::VALUE], false, $m, false) >= 0) { - $a = $this->_subtract($a[self::VALUE], false, $m, false); - } - return $a[self::VALUE]; + $this->value->setPrecision($bits); } /** - * Prepare a number for use in Montgomery Modular Reductions + * Get Precision * - * @see self::_montgomery() - * @see self::_slidingWindow() - * @access private - * @param array $x - * @param array $n - * @return array + * Returns the precision if it exists, false if it doesn't + * + * @return int|bool */ - function _prepMontgomery($x, $n) + public function getPrecision() { - $lhs = new static(); - $lhs->value = array_merge($this->_array_repeat(0, count($n)), $x); - $rhs = new static(); - $rhs->value = $n; - - list(, $temp) = $lhs->divide($rhs); - return $temp->value; + return $this->value->getPrecision(); } /** - * Modular Inverse of a number mod 2**26 (eg. 67108864) + * Serialize * - * Based off of the bnpInvDigit function implemented and justified in the following URL: - * - * {@link http://www-cs-students.stanford.edu/~tjw/jsbn/jsbn.js} - * - * The following URL provides more info: + * Will be called, automatically, when serialize() is called on a BigInteger object. * - * {@link http://groups.google.com/group/sci.crypt/msg/7a137205c1be7d85} + * __sleep() / __wakeup() have been around since PHP 4.0 * - * As for why we do all the bitmasking... strange things can happen when converting from floats to ints. For - * instance, on some computers, var_dump((int) -4294967297) yields int(-1) and on others, it yields - * int(-2147483648). To avoid problems stemming from this, we use bitmasks to guarantee that ints aren't - * auto-converted to floats. The outermost bitmask is present because without it, there's no guarantee that - * the "residue" returned would be the so-called "common residue". We use fmod, in the last step, because the - * maximum possible $x is 26 bits and the maximum $result is 16 bits. Thus, we have to be able to handle up to - * 40 bits, which only 64-bit floating points will support. + * \Serializable was introduced in PHP 5.1 and deprecated in PHP 8.1: + * https://wiki.php.net/rfc/phase_out_serializable * - * Thanks to Pedro Gimeno Fortea for input! + * __serialize() / __unserialize() were introduced in PHP 7.4: + * https://wiki.php.net/rfc/custom_object_serialization * - * @see self::_montgomery() - * @access private - * @param array $x - * @return int + * @return array */ - function _modInverse67108864($x) // 2**26 == 67,108,864 + public function __sleep() { - $x = -$x[0]; - $result = $x & 0x3; // x**-1 mod 2**2 - $result = ($result * (2 - $x * $result)) & 0xF; // x**-1 mod 2**4 - $result = ($result * (2 - ($x & 0xFF) * $result)) & 0xFF; // x**-1 mod 2**8 - $result = ($result * ((2 - ($x & 0xFFFF) * $result) & 0xFFFF)) & 0xFFFF; // x**-1 mod 2**16 - $result = fmod($result * (2 - fmod($x * $result, self::$baseFull)), self::$baseFull); // x**-1 mod 2**26 - return $result & self::$maxDigit; + $this->hex = $this->toHex(true); + $vars = ['hex']; + if ($this->getPrecision() > 0) { + $vars[] = 'precision'; + } + return $vars; } /** - * Calculates modular inverses. - * - * Say you have (30 mod 17 * x mod 17) mod 17 == 1. x can be found using modular inverses. - * - * Here's an example: - * - * modInverse($b); - * echo $c->toString(); // outputs 4 - * - * echo "\r\n"; - * - * $d = $a->multiply($c); - * list(, $d) = $d->divide($b); - * echo $d; // outputs 1 (as per the definition of modular inverse) - * ?> - * + * Serialize * - * @param \phpseclib\Math\BigInteger $n - * @return \phpseclib\Math\BigInteger|false - * @access public - * @internal See {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=21 HAC 14.64} for more information. + * Will be called, automatically, when unserialize() is called on a BigInteger object. */ - function modInverse($n) + public function __wakeup() { - switch (MATH_BIGINTEGER_MODE) { - case self::MODE_GMP: - $temp = new static(); - $temp->value = gmp_invert($this->value, $n->value); - - return ($temp->value === false) ? false : $this->_normalize($temp); - } - - static $zero, $one; - if (!isset($zero)) { - $zero = new static(); - $one = new static(1); - } - - // $x mod -$n == $x mod $n. - $n = $n->abs(); - - if ($this->compare($zero) < 0) { - $temp = $this->abs(); - $temp = $temp->modInverse($n); - return $this->_normalize($n->subtract($temp)); - } - - extract($this->extendedGCD($n)); - - if (!$gcd->equals($one)) { - return false; + $temp = new static($this->hex, -16); + $this->value = $temp->value; + if ($this->precision > 0) { + // recalculate $this->bitmask + $this->setPrecision($this->precision); } - - $x = $x->compare($zero) < 0 ? $x->add($n) : $x; - - return $this->compare($zero) < 0 ? $this->_normalize($n->subtract($x)) : $this->_normalize($x); } /** - * Calculates the greatest common divisor and Bezout's identity. - * - * Say you have 693 and 609. The GCD is 21. Bezout's identity states that there exist integers x and y such that - * 693*x + 609*y == 21. In point of fact, there are actually an infinite number of x and y combinations and which - * combination is returned is dependent upon which mode is in use. See - * {@link http://en.wikipedia.org/wiki/B%C3%A9zout%27s_identity Bezout's identity - Wikipedia} for more information. - * - * Here's an example: - * - * extendedGCD($b)); - * - * echo $gcd->toString() . "\r\n"; // outputs 21 - * echo $a->toString() * $x->toString() + $b->toString() * $y->toString(); // outputs 21 - * ?> - * - * - * @param \phpseclib\Math\BigInteger $n - * @return \phpseclib\Math\BigInteger - * @access public - * @internal Calculates the GCD using the binary xGCD algorithim described in - * {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=19 HAC 14.61}. As the text above 14.61 notes, - * the more traditional algorithim requires "relatively costly multiple-precision divisions". + * Will be called, automatically, when json_encode() is called on a BigInteger object. */ - function extendedGCD($n) + #[\ReturnTypeWillChange] + public function jsonSerialize() { - switch (MATH_BIGINTEGER_MODE) { - case self::MODE_GMP: - extract(gmp_gcdext($this->value, $n->value)); - - return array( - 'gcd' => $this->_normalize(new static($g)), - 'x' => $this->_normalize(new static($s)), - 'y' => $this->_normalize(new static($t)) - ); - case self::MODE_BCMATH: - // it might be faster to use the binary xGCD algorithim here, as well, but (1) that algorithim works - // best when the base is a power of 2 and (2) i don't think it'd make much difference, anyway. as is, - // the basic extended euclidean algorithim is what we're using. - - $u = $this->value; - $v = $n->value; - - $a = '1'; - $b = '0'; - $c = '0'; - $d = '1'; - - while (bccomp($v, '0', 0) != 0) { - $q = bcdiv($u, $v, 0); - - $temp = $u; - $u = $v; - $v = bcsub($temp, bcmul($v, $q, 0), 0); - - $temp = $a; - $a = $c; - $c = bcsub($temp, bcmul($a, $q, 0), 0); - - $temp = $b; - $b = $d; - $d = bcsub($temp, bcmul($b, $q, 0), 0); - } - - return array( - 'gcd' => $this->_normalize(new static($u)), - 'x' => $this->_normalize(new static($a)), - 'y' => $this->_normalize(new static($b)) - ); - } - - $y = $n->copy(); - $x = $this->copy(); - $g = new static(); - $g->value = array(1); - - while (!(($x->value[0] & 1)|| ($y->value[0] & 1))) { - $x->_rshift(1); - $y->_rshift(1); - $g->_lshift(1); - } - - $u = $x->copy(); - $v = $y->copy(); - - $a = new static(); - $b = new static(); - $c = new static(); - $d = new static(); - - $a->value = $d->value = $g->value = array(1); - $b->value = $c->value = array(); - - while (!empty($u->value)) { - while (!($u->value[0] & 1)) { - $u->_rshift(1); - if ((!empty($a->value) && ($a->value[0] & 1)) || (!empty($b->value) && ($b->value[0] & 1))) { - $a = $a->add($y); - $b = $b->subtract($x); - } - $a->_rshift(1); - $b->_rshift(1); - } - - while (!($v->value[0] & 1)) { - $v->_rshift(1); - if ((!empty($d->value) && ($d->value[0] & 1)) || (!empty($c->value) && ($c->value[0] & 1))) { - $c = $c->add($y); - $d = $d->subtract($x); - } - $c->_rshift(1); - $d->_rshift(1); - } - - if ($u->compare($v) >= 0) { - $u = $u->subtract($v); - $a = $a->subtract($c); - $b = $b->subtract($d); - } else { - $v = $v->subtract($u); - $c = $c->subtract($a); - $d = $d->subtract($b); - } + $result = ['hex' => $this->toHex(true)]; + if ($this->precision > 0) { + $result['precision'] = $this->getPrecision(); } - - return array( - 'gcd' => $this->_normalize($g->multiply($v)), - 'x' => $this->_normalize($c), - 'y' => $this->_normalize($d) - ); + return $result; } /** - * Calculates the greatest common divisor - * - * Say you have 693 and 609. The GCD is 21. - * - * Here's an example: - * - * extendedGCD($b); - * - * echo $gcd->toString() . "\r\n"; // outputs 21 - * ?> - * + * Performs modular exponentiation. * - * @param \phpseclib\Math\BigInteger $n - * @return \phpseclib\Math\BigInteger - * @access public + * @param BigInteger $e + * @param BigInteger $n + * @return BigInteger */ - function gcd($n) + public function powMod(BigInteger $e, BigInteger $n) { - extract($this->extendedGCD($n)); - return $gcd; + return new static($this->value->powMod($e->value, $n->value)); } /** - * Absolute value. + * Performs modular exponentiation. * - * @return \phpseclib\Math\BigInteger - * @access public + * @param BigInteger $e + * @param BigInteger $n + * @return BigInteger */ - function abs() + public function modPow(BigInteger $e, BigInteger $n) { - $temp = new static(); - - switch (MATH_BIGINTEGER_MODE) { - case self::MODE_GMP: - $temp->value = gmp_abs($this->value); - break; - case self::MODE_BCMATH: - $temp->value = (bccomp($this->value, '0', 0) < 0) ? substr($this->value, 1) : $this->value; - break; - default: - $temp->value = $this->value; - } - - return $temp; + return new static($this->value->modPow($e->value, $n->value)); } /** * Compares two numbers. * - * Although one might think !$x->compare($y) means $x != $y, it, in fact, means the opposite. The reason for this is - * demonstrated thusly: + * Although one might think !$x->compare($y) means $x != $y, it, in fact, means the opposite. The reason for this + * is demonstrated thusly: * * $x > $y: $x->compare($y) > 0 * $x < $y: $x->compare($y) < 0 @@ -2687,65 +484,15 @@ function abs() * * Note how the same comparison operator is used. If you want to test for equality, use $x->equals($y). * - * @param \phpseclib\Math\BigInteger $y - * @return int that is < 0 if $this is less than $y; > 0 if $this is greater than $y, and 0 if they are equal. - * @access public - * @see self::equals() - * @internal Could return $this->subtract($x), but that's not as fast as what we do do. - */ - function compare($y) - { - switch (MATH_BIGINTEGER_MODE) { - case self::MODE_GMP: - $r = gmp_cmp($this->value, $y->value); - if ($r < -1) { - $r = -1; - } - if ($r > 1) { - $r = 1; - } - return $r; - case self::MODE_BCMATH: - return bccomp($this->value, $y->value, 0); - } - - return $this->_compare($this->value, $this->is_negative, $y->value, $y->is_negative); - } - - /** - * Compares two numbers. + * {@internal Could return $this->subtract($x), but that's not as fast as what we do do.} * - * @param array $x_value - * @param bool $x_negative - * @param array $y_value - * @param bool $y_negative - * @return int - * @see self::compare() - * @access private + * @param BigInteger $y + * @return int in case < 0 if $this is less than $y; > 0 if $this is greater than $y, and 0 if they are equal. + * @see self::equals() */ - function _compare($x_value, $x_negative, $y_value, $y_negative) + public function compare(BigInteger $y) { - if ($x_negative != $y_negative) { - return (!$x_negative && $y_negative) ? 1 : -1; - } - - $result = $x_negative ? -1 : 1; - - if (count($x_value) != count($y_value)) { - return (count($x_value) > count($y_value)) ? $result : -$result; - } - $size = max(count($x_value), count($y_value)); - - $x_value = array_pad($x_value, $size, 0); - $y_value = array_pad($y_value, $size, 0); - - for ($i = count($x_value) - 1; $i >= 0; --$i) { - if ($x_value[$i] != $y_value[$i]) { - return ($x_value[$i] > $y_value[$i]) ? $result : -$result; - } - } - - return 0; + return $this->value->compare($y->value); } /** @@ -2753,201 +500,55 @@ function _compare($x_value, $x_negative, $y_value, $y_negative) * * If you need to see if one number is greater than or less than another number, use BigInteger::compare() * - * @param \phpseclib\Math\BigInteger $x + * @param BigInteger $x * @return bool - * @access public - * @see self::compare() */ - function equals($x) + public function equals(BigInteger $x) { - switch (MATH_BIGINTEGER_MODE) { - case self::MODE_GMP: - return gmp_cmp($this->value, $x->value) == 0; - default: - return $this->value === $x->value && $this->is_negative == $x->is_negative; - } + return $this->value->equals($x->value); } /** - * Set Precision - * - * Some bitwise operations give different results depending on the precision being used. Examples include left - * shift, not, and rotates. + * Logical Not * - * @param int $bits - * @access public + * @return BigInteger */ - function setPrecision($bits) + public function bitwise_not() { - $this->precision = $bits; - if (MATH_BIGINTEGER_MODE != self::MODE_BCMATH) { - $this->bitmask = new static(chr((1 << ($bits & 0x7)) - 1) . str_repeat(chr(0xFF), $bits >> 3), 256); - } else { - $this->bitmask = new static(bcpow('2', $bits, 0)); - } - - $temp = $this->_normalize($this); - $this->value = $temp->value; + return new static($this->value->bitwise_not()); } /** * Logical And * - * @param \phpseclib\Math\BigInteger $x - * @access public - * @internal Implemented per a request by Lluis Pamies i Juarez - * @return \phpseclib\Math\BigInteger - */ - function bitwise_and($x) - { - switch (MATH_BIGINTEGER_MODE) { - case self::MODE_GMP: - $temp = new static(); - $temp->value = gmp_and($this->value, $x->value); - - return $this->_normalize($temp); - case self::MODE_BCMATH: - $left = $this->toBytes(); - $right = $x->toBytes(); - - $length = max(strlen($left), strlen($right)); - - $left = str_pad($left, $length, chr(0), STR_PAD_LEFT); - $right = str_pad($right, $length, chr(0), STR_PAD_LEFT); - - return $this->_normalize(new static($left & $right, 256)); - } - - $result = $this->copy(); - - $length = min(count($x->value), count($this->value)); - - $result->value = array_slice($result->value, 0, $length); - - for ($i = 0; $i < $length; ++$i) { - $result->value[$i]&= $x->value[$i]; - } - - return $this->_normalize($result); - } - - /** - * Logical Or - * - * @param \phpseclib\Math\BigInteger $x - * @access public - * @internal Implemented per a request by Lluis Pamies i Juarez - * @return \phpseclib\Math\BigInteger + * @param BigInteger $x + * @return BigInteger */ - function bitwise_or($x) - { - switch (MATH_BIGINTEGER_MODE) { - case self::MODE_GMP: - $temp = new static(); - $temp->value = gmp_or($this->value, $x->value); - - return $this->_normalize($temp); - case self::MODE_BCMATH: - $left = $this->toBytes(); - $right = $x->toBytes(); - - $length = max(strlen($left), strlen($right)); - - $left = str_pad($left, $length, chr(0), STR_PAD_LEFT); - $right = str_pad($right, $length, chr(0), STR_PAD_LEFT); - - return $this->_normalize(new static($left | $right, 256)); - } - - $length = max(count($this->value), count($x->value)); - $result = $this->copy(); - $result->value = array_pad($result->value, $length, 0); - $x->value = array_pad($x->value, $length, 0); - - for ($i = 0; $i < $length; ++$i) { - $result->value[$i]|= $x->value[$i]; - } - - return $this->_normalize($result); + public function bitwise_and(BigInteger $x) + { + return new static($this->value->bitwise_and($x->value)); } /** - * Logical Exclusive-Or + * Logical Or * - * @param \phpseclib\Math\BigInteger $x - * @access public - * @internal Implemented per a request by Lluis Pamies i Juarez - * @return \phpseclib\Math\BigInteger + * @param BigInteger $x + * @return BigInteger */ - function bitwise_xor($x) + public function bitwise_or(BigInteger $x) { - switch (MATH_BIGINTEGER_MODE) { - case self::MODE_GMP: - $temp = new static(); - $temp->value = gmp_xor(gmp_abs($this->value), gmp_abs($x->value)); - return $this->_normalize($temp); - case self::MODE_BCMATH: - $left = $this->toBytes(); - $right = $x->toBytes(); - - $length = max(strlen($left), strlen($right)); - - $left = str_pad($left, $length, chr(0), STR_PAD_LEFT); - $right = str_pad($right, $length, chr(0), STR_PAD_LEFT); - - return $this->_normalize(new static($left ^ $right, 256)); - } - - $length = max(count($this->value), count($x->value)); - $result = $this->copy(); - $result->is_negative = false; - $result->value = array_pad($result->value, $length, 0); - $x->value = array_pad($x->value, $length, 0); - - for ($i = 0; $i < $length; ++$i) { - $result->value[$i]^= $x->value[$i]; - } - - return $this->_normalize($result); + return new static($this->value->bitwise_or($x->value)); } /** - * Logical Not + * Logical Exclusive Or * - * @access public - * @internal Implemented per a request by Lluis Pamies i Juarez - * @return \phpseclib\Math\BigInteger + * @param BigInteger $x + * @return BigInteger */ - function bitwise_not() + public function bitwise_xor(BigInteger $x) { - // calculuate "not" without regard to $this->precision - // (will always result in a smaller number. ie. ~1 isn't 1111 1110 - it's 0) - $temp = $this->toBytes(); - if ($temp == '') { - return $this->_normalize(new static()); - } - $pre_msb = decbin(ord($temp[0])); - $temp = ~$temp; - $msb = decbin(ord($temp[0])); - if (strlen($msb) == 8) { - $msb = substr($msb, strpos($msb, '0')); - } - $temp[0] = chr(bindec($msb)); - - // see if we need to add extra leading 1's - $current_bits = strlen($pre_msb) + 8 * strlen($temp) - 8; - $new_bits = $this->precision - $current_bits; - if ($new_bits <= 0) { - return $this->_normalize(new static($temp, 256)); - } - - // generate as many leading 1's as we need to. - $leading_ones = chr((1 << ($new_bits & 0x7)) - 1) . str_repeat(chr(0xFF), $new_bits >> 3); - $this->_base256_lshift($leading_ones, $current_bits); - - $temp = str_pad($temp, strlen($leading_ones), chr(0), STR_PAD_LEFT); - - return $this->_normalize(new static($leading_ones | $temp, 256)); + return new static($this->value->bitwise_xor($x->value)); } /** @@ -2956,36 +557,11 @@ function bitwise_not() * Shifts BigInteger's by $shift bits, effectively dividing by 2**$shift. * * @param int $shift - * @return \phpseclib\Math\BigInteger - * @access public - * @internal The only version that yields any speed increases is the internal version. + * @return BigInteger */ - function bitwise_rightShift($shift) + public function bitwise_rightShift($shift) { - $temp = new static(); - - switch (MATH_BIGINTEGER_MODE) { - case self::MODE_GMP: - static $two; - - if (!isset($two)) { - $two = gmp_init('2'); - } - - $temp->value = gmp_div_q($this->value, gmp_pow($two, $shift)); - - break; - case self::MODE_BCMATH: - $temp->value = bcdiv($this->value, bcpow('2', $shift, 0), 0); - - break; - default: // could just replace _lshift with this, but then all _lshift() calls would need to be rewritten - // and I don't want to do that... - $temp->value = $this->value; - $temp->_rshift($shift); - } - - return $this->_normalize($temp); + return new static($this->value->bitwise_rightShift($shift)); } /** @@ -2994,36 +570,11 @@ function bitwise_rightShift($shift) * Shifts BigInteger's by $shift bits, effectively multiplying by 2**$shift. * * @param int $shift - * @return \phpseclib\Math\BigInteger - * @access public - * @internal The only version that yields any speed increases is the internal version. + * @return BigInteger */ - function bitwise_leftShift($shift) + public function bitwise_leftShift($shift) { - $temp = new static(); - - switch (MATH_BIGINTEGER_MODE) { - case self::MODE_GMP: - static $two; - - if (!isset($two)) { - $two = gmp_init('2'); - } - - $temp->value = gmp_mul($this->value, gmp_pow($two, $shift)); - - break; - case self::MODE_BCMATH: - $temp->value = bcmul($this->value, bcpow('2', $shift, 0), 0); - - break; - default: // could just replace _rshift with this, but then all _lshift() calls would need to be rewritten - // and I don't want to do that... - $temp->value = $this->value; - $temp->_lshift($shift); - } - - return $this->_normalize($temp); + return new static($this->value->bitwise_leftShift($shift)); } /** @@ -3032,43 +583,11 @@ function bitwise_leftShift($shift) * Instead of the top x bits being dropped they're appended to the shifted bit string. * * @param int $shift - * @return \phpseclib\Math\BigInteger - * @access public + * @return BigInteger */ - function bitwise_leftRotate($shift) + public function bitwise_leftRotate($shift) { - $bits = $this->toBytes(); - - if ($this->precision > 0) { - $precision = $this->precision; - if (MATH_BIGINTEGER_MODE == self::MODE_BCMATH) { - $mask = $this->bitmask->subtract(new static(1)); - $mask = $mask->toBytes(); - } else { - $mask = $this->bitmask->toBytes(); - } - } else { - $temp = ord($bits[0]); - for ($i = 0; $temp >> $i; ++$i) { - } - $precision = 8 * strlen($bits) - 8 + $i; - $mask = chr((1 << ($precision & 0x7)) - 1) . str_repeat(chr(0xFF), $precision >> 3); - } - - if ($shift < 0) { - $shift+= $precision; - } - $shift%= $precision; - - if (!$shift) { - return $this->copy(); - } - - $left = $this->bitwise_leftShift($shift); - $left = $left->bitwise_and(new static($mask, 256)); - $right = $this->bitwise_rightShift($precision - $shift); - $result = MATH_BIGINTEGER_MODE != self::MODE_BCMATH ? $left->bitwise_or($right) : $left->add($right); - return $this->_normalize($result); + return new static($this->value->bitwise_leftRotate($shift)); } /** @@ -3077,255 +596,118 @@ function bitwise_leftRotate($shift) * Instead of the bottom x bits being dropped they're prepended to the shifted bit string. * * @param int $shift - * @return \phpseclib\Math\BigInteger - * @access public + * @return BigInteger */ - function bitwise_rightRotate($shift) + public function bitwise_rightRotate($shift) { - return $this->bitwise_leftRotate(-$shift); + return new static($this->value->bitwise_rightRotate($shift)); } /** - * Generates a random BigInteger - * - * Byte length is equal to $length. Uses \phpseclib\Crypt\Random if it's loaded and mt_rand if it's not. + * Returns the smallest and largest n-bit number * - * @param int $size - * @return \phpseclib\Math\BigInteger - * @access private + * @param int $bits + * @return BigInteger[] */ - function _random_number_helper($size) + public static function minMaxBits($bits) { - if (class_exists('\phpseclib\Crypt\Random')) { - $random = Random::string($size); - } else { - $random = ''; - - if ($size & 1) { - $random.= chr(mt_rand(0, 255)); - } + self::initialize_static_variables(); - $blocks = $size >> 1; - for ($i = 0; $i < $blocks; ++$i) { - // mt_rand(-2147483648, 0x7FFFFFFF) always produces -2147483648 on some systems - $random.= pack('n', mt_rand(0, 0xFFFF)); - } - } + $class = self::$mainEngine; + extract($class::minMaxBits($bits)); + /** @var BigInteger $min + * @var BigInteger $max + */ + return [ + 'min' => new static($min), + 'max' => new static($max) + ]; + } - return new static($random, 256); + /** + * Return the size of a BigInteger in bits + * + * @return int + */ + public function getLength() + { + return $this->value->getLength(); } /** - * Generate a random number + * Return the size of a BigInteger in bytes * - * Returns a random number between $min and $max where $min and $max - * can be defined using one of the two methods: + * @return int + */ + public function getLengthInBytes() + { + return $this->value->getLengthInBytes(); + } + + /** + * Generates a random number of a certain size * - * $min->random($max) - * $max->random($min) + * Bit length is equal to $size * - * @param \phpseclib\Math\BigInteger $arg1 - * @param \phpseclib\Math\BigInteger $arg2 - * @return \phpseclib\Math\BigInteger - * @access public - * @internal The API for creating random numbers used to be $a->random($min, $max), where $a was a BigInteger object. - * That method is still supported for BC purposes. + * @param int $size + * @return BigInteger */ - function random($arg1, $arg2 = false) + public static function random($size) { - if ($arg1 === false) { - return false; - } - - if ($arg2 === false) { - $max = $arg1; - $min = $this; - } else { - $min = $arg1; - $max = $arg2; - } + self::initialize_static_variables(); - $compare = $max->compare($min); - - if (!$compare) { - return $this->_normalize($min); - } elseif ($compare < 0) { - // if $min is bigger then $max, swap $min and $max - $temp = $max; - $max = $min; - $min = $temp; - } - - static $one; - if (!isset($one)) { - $one = new static(1); - } + $class = self::$mainEngine; + return new static($class::random($size)); + } - $max = $max->subtract($min->subtract($one)); - $size = strlen(ltrim($max->toBytes(), chr(0))); - - /* - doing $random % $max doesn't work because some numbers will be more likely to occur than others. - eg. if $max is 140 and $random's max is 255 then that'd mean both $random = 5 and $random = 145 - would produce 5 whereas the only value of random that could produce 139 would be 139. ie. - not all numbers would be equally likely. some would be more likely than others. - - creating a whole new random number until you find one that is within the range doesn't work - because, for sufficiently small ranges, the likelihood that you'd get a number within that range - would be pretty small. eg. with $random's max being 255 and if your $max being 1 the probability - would be pretty high that $random would be greater than $max. - - phpseclib works around this using the technique described here: - - http://crypto.stackexchange.com/questions/5708/creating-a-small-number-from-a-cryptographically-secure-random-string - */ - $random_max = new static(chr(1) . str_repeat("\0", $size), 256); - $random = $this->_random_number_helper($size); - - list($max_multiple) = $random_max->divide($max); - $max_multiple = $max_multiple->multiply($max); - - while ($random->compare($max_multiple) >= 0) { - $random = $random->subtract($max_multiple); - $random_max = $random_max->subtract($max_multiple); - $random = $random->bitwise_leftShift(8); - $random = $random->add($this->_random_number_helper(1)); - $random_max = $random_max->bitwise_leftShift(8); - list($max_multiple) = $random_max->divide($max); - $max_multiple = $max_multiple->multiply($max); - } - list(, $random) = $random->divide($max); + /** + * Generates a random prime number of a certain size + * + * Bit length is equal to $size + * + * @param int $size + * @return BigInteger + */ + public static function randomPrime($size) + { + self::initialize_static_variables(); - return $this->_normalize($random->add($min)); + $class = self::$mainEngine; + return new static($class::randomPrime($size)); } /** - * Generate a random prime number. + * Generate a random prime number between a range * * If there's not a prime within the given range, false will be returned. - * If more than $timeout seconds have elapsed, give up and return false. - * - * @param \phpseclib\Math\BigInteger $arg1 - * @param \phpseclib\Math\BigInteger $arg2 - * @param int $timeout - * @return Math_BigInteger|false - * @access public - * @internal See {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap4.pdf#page=15 HAC 4.44}. + * + * @param BigInteger $min + * @param BigInteger $max + * @return false|BigInteger */ - function randomPrime($arg1, $arg2 = false, $timeout = false) + public static function randomRangePrime(BigInteger $min, BigInteger $max) { - if ($arg1 === false) { - return false; - } - - if ($arg2 === false) { - $max = $arg1; - $min = $this; - } else { - $min = $arg1; - $max = $arg2; - } - - $compare = $max->compare($min); - - if (!$compare) { - return $min->isPrime() ? $min : false; - } elseif ($compare < 0) { - // if $min is bigger then $max, swap $min and $max - $temp = $max; - $max = $min; - $min = $temp; - } - - static $one, $two; - if (!isset($one)) { - $one = new static(1); - $two = new static(2); - } - - $start = time(); - - $x = $this->random($min, $max); - - // gmp_nextprime() requires PHP 5 >= 5.2.0 per . - if (MATH_BIGINTEGER_MODE == self::MODE_GMP && extension_loaded('gmp')) { - $p = new static(); - $p->value = gmp_nextprime($x->value); - - if ($p->compare($max) <= 0) { - return $p; - } - - if (!$min->equals($x)) { - $x = $x->subtract($one); - } - - return $x->randomPrime($min, $x); - } - - if ($x->equals($two)) { - return $x; - } - - $x->_make_odd(); - if ($x->compare($max) > 0) { - // if $x > $max then $max is even and if $min == $max then no prime number exists between the specified range - if ($min->equals($max)) { - return false; - } - $x = $min->copy(); - $x->_make_odd(); - } - - $initial_x = $x->copy(); - - while (true) { - if ($timeout !== false && time() - $start > $timeout) { - return false; - } - - if ($x->isPrime()) { - return $x; - } - - $x = $x->add($two); - - if ($x->compare($max) > 0) { - $x = $min->copy(); - if ($x->equals($two)) { - return $x; - } - $x->_make_odd(); - } - - if ($x->equals($initial_x)) { - return false; - } - } + $class = self::$mainEngine; + return new static($class::randomRangePrime($min->value, $max->value)); } /** - * Make the current number odd + * Generate a random number between a range * - * If the current number is odd it'll be unchanged. If it's even, one will be added to it. + * Returns a random number between $min and $max where $min and $max + * can be defined using one of the two methods: * - * @see self::randomPrime() - * @access private + * BigInteger::randomRange($min, $max) + * BigInteger::randomRange($max, $min) + * + * @param BigInteger $min + * @param BigInteger $max + * @return BigInteger */ - function _make_odd() + public static function randomRange(BigInteger $min, BigInteger $max) { - switch (MATH_BIGINTEGER_MODE) { - case self::MODE_GMP: - gmp_setbit($this->value, 0); - break; - case self::MODE_BCMATH: - if ($this->value[strlen($this->value) - 1] % 2 == 0) { - $this->value = bcadd($this->value, '1'); - } - break; - default: - $this->value[0] |= 1; - } + $class = self::$mainEngine; + return new static($class::randomRange($min->value, $max->value)); } /** @@ -3335,453 +717,173 @@ function _make_odd() * $t parameter is distributability. BigInteger::randomPrime() can be distributed across multiple pageloads * on a website instead of just one. * - * @param \phpseclib\Math\BigInteger $t + * @param int|bool $t * @return bool - * @access public - * @internal Uses the - * {@link http://en.wikipedia.org/wiki/Miller%E2%80%93Rabin_primality_test Miller-Rabin primality test}. See - * {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap4.pdf#page=8 HAC 4.24}. */ - function isPrime($t = false) + public function isPrime($t = false) { - $length = strlen($this->toBytes()); - - if (!$t) { - // see HAC 4.49 "Note (controlling the error probability)" - // @codingStandardsIgnoreStart - if ($length >= 163) { $t = 2; } // floor(1300 / 8) - else if ($length >= 106) { $t = 3; } // floor( 850 / 8) - else if ($length >= 81 ) { $t = 4; } // floor( 650 / 8) - else if ($length >= 68 ) { $t = 5; } // floor( 550 / 8) - else if ($length >= 56 ) { $t = 6; } // floor( 450 / 8) - else if ($length >= 50 ) { $t = 7; } // floor( 400 / 8) - else if ($length >= 43 ) { $t = 8; } // floor( 350 / 8) - else if ($length >= 37 ) { $t = 9; } // floor( 300 / 8) - else if ($length >= 31 ) { $t = 12; } // floor( 250 / 8) - else if ($length >= 25 ) { $t = 15; } // floor( 200 / 8) - else if ($length >= 18 ) { $t = 18; } // floor( 150 / 8) - else { $t = 27; } - // @codingStandardsIgnoreEnd - } - - // ie. gmp_testbit($this, 0) - // ie. isEven() or !isOdd() - switch (MATH_BIGINTEGER_MODE) { - case self::MODE_GMP: - return gmp_prob_prime($this->value, $t) != 0; - case self::MODE_BCMATH: - if ($this->value === '2') { - return true; - } - if ($this->value[strlen($this->value) - 1] % 2 == 0) { - return false; - } - break; - default: - if ($this->value == array(2)) { - return true; - } - if (~$this->value[0] & 1) { - return false; - } - } - - static $primes, $zero, $one, $two; - - if (!isset($primes)) { - $primes = array( - 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, - 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, - 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, - 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, - 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, - 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, - 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617, - 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709, 719, 727, - 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, - 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, - 953, 967, 971, 977, 983, 991, 997 - ); - - if (MATH_BIGINTEGER_MODE != self::MODE_INTERNAL) { - for ($i = 0; $i < count($primes); ++$i) { - $primes[$i] = new static($primes[$i]); - } - } - - $zero = new static(); - $one = new static(1); - $two = new static(2); - } - - if ($this->equals($one)) { - return false; - } - - // see HAC 4.4.1 "Random search for probable primes" - if (MATH_BIGINTEGER_MODE != self::MODE_INTERNAL) { - foreach ($primes as $prime) { - list(, $r) = $this->divide($prime); - if ($r->equals($zero)) { - return $this->equals($prime); - } - } - } else { - $value = $this->value; - foreach ($primes as $prime) { - list(, $r) = $this->_divide_digit($value, $prime); - if (!$r) { - return count($value) == 1 && $value[0] == $prime; - } - } - } - - $n = $this->copy(); - $n_1 = $n->subtract($one); - $n_2 = $n->subtract($two); - - $r = $n_1->copy(); - $r_value = $r->value; - // ie. $s = gmp_scan1($n, 0) and $r = gmp_div_q($n, gmp_pow(gmp_init('2'), $s)); - if (MATH_BIGINTEGER_MODE == self::MODE_BCMATH) { - $s = 0; - // if $n was 1, $r would be 0 and this would be an infinite loop, hence our $this->equals($one) check earlier - while ($r->value[strlen($r->value) - 1] % 2 == 0) { - $r->value = bcdiv($r->value, '2', 0); - ++$s; - } - } else { - for ($i = 0, $r_length = count($r_value); $i < $r_length; ++$i) { - $temp = ~$r_value[$i] & 0xFFFFFF; - for ($j = 1; ($temp >> $j) & 1; ++$j) { - } - if ($j != 25) { - break; - } - } - $s = 26 * $i + $j; - $r->_rshift($s); - } - - for ($i = 0; $i < $t; ++$i) { - $a = $this->random($two, $n_2); - $y = $a->modPow($r, $n); - - if (!$y->equals($one) && !$y->equals($n_1)) { - for ($j = 1; $j < $s && !$y->equals($n_1); ++$j) { - $y = $y->modPow($two, $n); - if ($y->equals($one)) { - return false; - } - } - - if (!$y->equals($n_1)) { - return false; - } - } - } - return true; + return $this->value->isPrime($t); } /** - * Logical Left Shift + * Calculates the nth root of a biginteger. * - * Shifts BigInteger's by $shift bits. + * Returns the nth root of a positive biginteger, where n defaults to 2 * - * @param int $shift - * @access private + * @param int $n optional + * @return BigInteger */ - function _lshift($shift) + public function root($n = 2) { - if ($shift == 0) { - return; - } - - $num_digits = (int) ($shift / self::$base); - $shift %= self::$base; - $shift = 1 << $shift; - - $carry = 0; - - for ($i = 0; $i < count($this->value); ++$i) { - $temp = $this->value[$i] * $shift + $carry; - $carry = self::$base === 26 ? intval($temp / 0x4000000) : ($temp >> 31); - $this->value[$i] = (int) ($temp - $carry * self::$baseFull); - } - - if ($carry) { - $this->value[count($this->value)] = $carry; - } - - while ($num_digits--) { - array_unshift($this->value, 0); - } + return new static($this->value->root($n)); } /** - * Logical Right Shift + * Performs exponentiation. * - * Shifts BigInteger's by $shift bits. - * - * @param int $shift - * @access private + * @param BigInteger $n + * @return BigInteger */ - function _rshift($shift) + public function pow(BigInteger $n) { - if ($shift == 0) { - return; - } - - $num_digits = (int) ($shift / self::$base); - $shift %= self::$base; - $carry_shift = self::$base - $shift; - $carry_mask = (1 << $shift) - 1; - - if ($num_digits) { - $this->value = array_slice($this->value, $num_digits); - } - - $carry = 0; - - for ($i = count($this->value) - 1; $i >= 0; --$i) { - $temp = $this->value[$i] >> $shift | $carry; - $carry = ($this->value[$i] & $carry_mask) << $carry_shift; - $this->value[$i] = $temp; - } - - $this->value = $this->_trim($this->value); + return new static($this->value->pow($n->value)); } /** - * Normalize + * Return the minimum BigInteger between an arbitrary number of BigIntegers. * - * Removes leading zeros and truncates (if necessary) to maintain the appropriate precision - * - * @param \phpseclib\Math\BigInteger $result - * @return \phpseclib\Math\BigInteger - * @see self::_trim() - * @access private + * @param BigInteger ...$nums + * @return BigInteger */ - function _normalize($result) + public static function min(BigInteger ...$nums) { - $result->precision = $this->precision; - $result->bitmask = $this->bitmask; - - switch (MATH_BIGINTEGER_MODE) { - case self::MODE_GMP: - if ($this->bitmask !== false) { - $flip = gmp_cmp($result->value, gmp_init(0)) < 0; - if ($flip) { - $result->value = gmp_neg($result->value); - } - $result->value = gmp_and($result->value, $result->bitmask->value); - if ($flip) { - $result->value = gmp_neg($result->value); - } - } - - return $result; - case self::MODE_BCMATH: - if (!empty($result->bitmask->value)) { - $result->value = bcmod($result->value, $result->bitmask->value); - } - - return $result; - } - - $value = &$result->value; - - if (!count($value)) { - $result->is_negative = false; - return $result; - } - - $value = $this->_trim($value); - - if (!empty($result->bitmask->value)) { - $length = min(count($value), count($this->bitmask->value)); - $value = array_slice($value, 0, $length); - - for ($i = 0; $i < $length; ++$i) { - $value[$i] = $value[$i] & $this->bitmask->value[$i]; - } - } - - return $result; + $class = self::$mainEngine; + $nums = array_map(function ($num) { + return $num->value; + }, $nums); + return new static($class::min(...$nums)); } /** - * Trim + * Return the maximum BigInteger between an arbitrary number of BigIntegers. * - * Removes leading zeros + * @param BigInteger ...$nums + * @return BigInteger + */ + public static function max(BigInteger ...$nums) + { + $class = self::$mainEngine; + $nums = array_map(function ($num) { + return $num->value; + }, $nums); + return new static($class::max(...$nums)); + } + + /** + * Tests BigInteger to see if it is between two integers, inclusive * - * @param array $value - * @return \phpseclib\Math\BigInteger - * @access private + * @param BigInteger $min + * @param BigInteger $max + * @return bool */ - function _trim($value) + public function between(BigInteger $min, BigInteger $max) { - for ($i = count($value) - 1; $i >= 0; --$i) { - if ($value[$i]) { - break; - } - unset($value[$i]); - } + return $this->value->between($min->value, $max->value); + } - return $value; + /** + * Clone + */ + public function __clone() + { + $this->value = clone $this->value; } /** - * Array Repeat + * Is Odd? * - * @param array $input - * @param mixed $multiplier - * @return array - * @access private + * @return bool */ - function _array_repeat($input, $multiplier) + public function isOdd() { - return ($multiplier) ? array_fill(0, $multiplier, $input) : array(); + return $this->value->isOdd(); } /** - * Logical Left Shift + * Tests if a bit is set * - * Shifts binary strings $shift bits, essentially multiplying by 2**$shift. - * - * @param string $x (by reference) - * @param int $shift - * @return string - * @access private + * @param int $x + * @return bool */ - function _base256_lshift(&$x, $shift) + public function testBit($x) { - if ($shift == 0) { - return; - } - - $num_bytes = $shift >> 3; // eg. floor($shift/8) - $shift &= 7; // eg. $shift % 8 - - $carry = 0; - for ($i = strlen($x) - 1; $i >= 0; --$i) { - $temp = ord($x[$i]) << $shift | $carry; - $x[$i] = chr($temp); - $carry = $temp >> 8; - } - $carry = ($carry != 0) ? chr($carry) : ''; - $x = $carry . $x . str_repeat(chr(0), $num_bytes); + return $this->value->testBit($x); } /** - * Logical Right Shift + * Is Negative? * - * Shifts binary strings $shift bits, essentially dividing by 2**$shift and returning the remainder. - * - * @param string $x (by referenc) - * @param int $shift - * @return string - * @access private + * @return bool */ - function _base256_rshift(&$x, $shift) + public function isNegative() { - if ($shift == 0) { - $x = ltrim($x, chr(0)); - return ''; - } - - $num_bytes = $shift >> 3; // eg. floor($shift/8) - $shift &= 7; // eg. $shift % 8 - - $remainder = ''; - if ($num_bytes) { - $start = $num_bytes > strlen($x) ? -strlen($x) : -$num_bytes; - $remainder = substr($x, $start); - $x = substr($x, 0, -$num_bytes); - } - - $carry = 0; - $carry_shift = 8 - $shift; - for ($i = 0; $i < strlen($x); ++$i) { - $temp = (ord($x[$i]) >> $shift) | $carry; - $carry = (ord($x[$i]) << $carry_shift) & 0xFF; - $x[$i] = chr($temp); - } - $x = ltrim($x, chr(0)); - - $remainder = chr($carry >> $carry_shift) . $remainder; - - return ltrim($remainder, chr(0)); + return $this->value->isNegative(); } - // one quirk about how the following functions are implemented is that PHP defines N to be an unsigned long - // at 32-bits, while java's longs are 64-bits. - /** - * Converts 32-bit integers to bytes. + * Negate * - * @param int $x - * @return string - * @access private + * Given $k, returns -$k + * + * @return BigInteger */ - function _int2bytes($x) + public function negate() { - return ltrim(pack('N', $x), chr(0)); + return new static($this->value->negate()); } /** - * Converts bytes to 32-bit integers + * Scan for 1 and right shift by that amount * - * @param string $x + * ie. $s = gmp_scan1($n, 0) and $r = gmp_div_q($n, gmp_pow(gmp_init('2'), $s)); + * + * @param BigInteger $r * @return int - * @access private */ - function _bytes2int($x) + public static function scan1divide(BigInteger $r) { - $temp = unpack('Nint', str_pad($x, 4, chr(0), STR_PAD_LEFT)); - return $temp['int']; + $class = self::$mainEngine; + return $class::scan1divide($r->value); } /** - * DER-encode an integer + * Create Recurring Modulo Function * - * The ability to DER-encode integers is needed to create RSA public keys for use with OpenSSL + * Sometimes it may be desirable to do repeated modulos with the same number outside of + * modular exponentiation * - * @see self::modPow() - * @access private - * @param int $length - * @return string + * @return callable */ - function _encodeASN1Length($length) + public function createRecurringModuloFunction() { - if ($length <= 0x7F) { - return chr($length); - } - - $temp = ltrim(pack('N', $length), chr(0)); - return pack('Ca*', 0x80 | strlen($temp), $temp); + $func = $this->value->createRecurringModuloFunction(); + return function (BigInteger $x) use ($func) { + return new static($func($x->value)); + }; } /** - * Single digit division + * Bitwise Split * - * Even if int64 is being used the division operator will return a float64 value - * if the dividend is not evenly divisible by the divisor. Since a float64 doesn't - * have the precision of int64 this is a problem so, when int64 is being used, - * we'll guarantee that the dividend is divisible by first subtracting the remainder. + * Splits BigInteger's into chunks of $split bits * - * @access private - * @param int $x - * @param int $y - * @return int + * @param int $split + * @return BigInteger[] */ - function _safe_divide($x, $y) + public function bitwise_split($split) { - if (self::$base === 26) { - return (int) ($x / $y); - } - - // self::$base === 31 - return ($x - ($x % $y)) / $y; + return array_map(function ($val) { + return new static($val); + }, $this->value->bitwise_split($split)); } } diff --git a/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/BCMath.php b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/BCMath.php new file mode 100644 index 00000000..21e24ce0 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/BCMath.php @@ -0,0 +1,697 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Math\BigInteger\Engines; + +use phpseclib3\Common\Functions\Strings; +use phpseclib3\Exception\BadConfigurationException; + +/** + * BCMath Engine. + * + * @author Jim Wigginton + */ +class BCMath extends Engine +{ + /** + * Can Bitwise operations be done fast? + * + * @see parent::bitwise_leftRotate() + * @see parent::bitwise_rightRotate() + */ + const FAST_BITWISE = false; + + /** + * Engine Directory + * + * @see parent::setModExpEngine + */ + const ENGINE_DIR = 'BCMath'; + + /** + * Test for engine validity + * + * @return bool + * @see parent::__construct() + */ + public static function isValidEngine() + { + return extension_loaded('bcmath'); + } + + /** + * Default constructor + * + * @param mixed $x integer Base-10 number or base-$base number if $base set. + * @param int $base + * @see parent::__construct() + */ + public function __construct($x = 0, $base = 10) + { + if (!isset(static::$isValidEngine[static::class])) { + static::$isValidEngine[static::class] = self::isValidEngine(); + } + if (!static::$isValidEngine[static::class]) { + throw new BadConfigurationException('BCMath is not setup correctly on this system'); + } + + $this->value = '0'; + + parent::__construct($x, $base); + } + + /** + * Initialize a BCMath BigInteger Engine instance + * + * @param int $base + * @see parent::__construct() + */ + protected function initialize($base) + { + switch (abs($base)) { + case 256: + // round $len to the nearest 4 + $len = (strlen($this->value) + 3) & 0xFFFFFFFC; + + $x = str_pad($this->value, $len, chr(0), STR_PAD_LEFT); + + $this->value = '0'; + for ($i = 0; $i < $len; $i += 4) { + $this->value = bcmul($this->value, '4294967296', 0); // 4294967296 == 2**32 + $this->value = bcadd( + $this->value, + 0x1000000 * ord($x[$i]) + ((ord($x[$i + 1]) << 16) | (ord( + $x[$i + 2] + ) << 8) | ord($x[$i + 3])), + 0 + ); + } + + if ($this->is_negative) { + $this->value = '-' . $this->value; + } + break; + case 16: + $x = (strlen($this->value) & 1) ? '0' . $this->value : $this->value; + $temp = new self(Strings::hex2bin($x), 256); + $this->value = $this->is_negative ? '-' . $temp->value : $temp->value; + $this->is_negative = false; + break; + case 10: + // explicitly casting $x to a string is necessary, here, since doing $x[0] on -1 yields different + // results then doing it on '-1' does (modInverse does $x[0]) + $this->value = $this->value === '-' ? '0' : (string)$this->value; + } + } + + /** + * Converts a BigInteger to a base-10 number. + * + * @return string + */ + public function toString() + { + if ($this->value === '0') { + return '0'; + } + + return ltrim($this->value, '0'); + } + + /** + * Converts a BigInteger to a byte string (eg. base-256). + * + * @param bool $twos_compliment + * @return string + */ + public function toBytes($twos_compliment = false) + { + if ($twos_compliment) { + return $this->toBytesHelper(); + } + + $value = ''; + $current = $this->value; + + if ($current[0] == '-') { + $current = substr($current, 1); + } + + while (bccomp($current, '0', 0) > 0) { + $temp = bcmod($current, '16777216'); + $value = chr($temp >> 16) . chr($temp >> 8) . chr($temp) . $value; + $current = bcdiv($current, '16777216', 0); + } + + return $this->precision > 0 ? + substr(str_pad($value, $this->precision >> 3, chr(0), STR_PAD_LEFT), -($this->precision >> 3)) : + ltrim($value, chr(0)); + } + + /** + * Adds two BigIntegers. + * + * @param BCMath $y + * @return BCMath + */ + public function add(BCMath $y) + { + $temp = new self(); + $temp->value = bcadd($this->value, $y->value); + + return $this->normalize($temp); + } + + /** + * Subtracts two BigIntegers. + * + * @param BCMath $y + * @return BCMath + */ + public function subtract(BCMath $y) + { + $temp = new self(); + $temp->value = bcsub($this->value, $y->value); + + return $this->normalize($temp); + } + + /** + * Multiplies two BigIntegers. + * + * @param BCMath $x + * @return BCMath + */ + public function multiply(BCMath $x) + { + $temp = new self(); + $temp->value = bcmul($this->value, $x->value); + + return $this->normalize($temp); + } + + /** + * Divides two BigIntegers. + * + * Returns an array whose first element contains the quotient and whose second element contains the + * "common residue". If the remainder would be positive, the "common residue" and the remainder are the + * same. If the remainder would be negative, the "common residue" is equal to the sum of the remainder + * and the divisor (basically, the "common residue" is the first positive modulo). + * + * @param BCMath $y + * @return array{static, static} + */ + public function divide(BCMath $y) + { + $quotient = new self(); + $remainder = new self(); + + $quotient->value = bcdiv($this->value, $y->value, 0); + $remainder->value = bcmod($this->value, $y->value); + + if ($remainder->value[0] == '-') { + $remainder->value = bcadd($remainder->value, $y->value[0] == '-' ? substr($y->value, 1) : $y->value, 0); + } + + return [$this->normalize($quotient), $this->normalize($remainder)]; + } + + /** + * Calculates modular inverses. + * + * Say you have (30 mod 17 * x mod 17) mod 17 == 1. x can be found using modular inverses. + * + * @param BCMath $n + * @return false|BCMath + */ + public function modInverse(BCMath $n) + { + return $this->modInverseHelper($n); + } + + /** + * Calculates the greatest common divisor and Bezout's identity. + * + * Say you have 693 and 609. The GCD is 21. Bezout's identity states that there exist integers x and y such that + * 693*x + 609*y == 21. In point of fact, there are actually an infinite number of x and y combinations and which + * combination is returned is dependent upon which mode is in use. See + * {@link http://en.wikipedia.org/wiki/B%C3%A9zout%27s_identity Bezout's identity - Wikipedia} for more information. + * + * @param BCMath $n + * @return array{gcd: static, x: static, y: static} + */ + public function extendedGCD(BCMath $n) + { + // it might be faster to use the binary xGCD algorithim here, as well, but (1) that algorithim works + // best when the base is a power of 2 and (2) i don't think it'd make much difference, anyway. as is, + // the basic extended euclidean algorithim is what we're using. + + $u = $this->value; + $v = $n->value; + + $a = '1'; + $b = '0'; + $c = '0'; + $d = '1'; + + while (bccomp($v, '0', 0) != 0) { + $q = bcdiv($u, $v, 0); + + $temp = $u; + $u = $v; + $v = bcsub($temp, bcmul($v, $q, 0), 0); + + $temp = $a; + $a = $c; + $c = bcsub($temp, bcmul($a, $q, 0), 0); + + $temp = $b; + $b = $d; + $d = bcsub($temp, bcmul($b, $q, 0), 0); + } + + return [ + 'gcd' => $this->normalize(new static($u)), + 'x' => $this->normalize(new static($a)), + 'y' => $this->normalize(new static($b)) + ]; + } + + /** + * Calculates the greatest common divisor + * + * Say you have 693 and 609. The GCD is 21. + * + * @param BCMath $n + * @return BCMath + */ + public function gcd(BCMath $n) + { + extract($this->extendedGCD($n)); + /** @var BCMath $gcd */ + return $gcd; + } + + /** + * Absolute value. + * + * @return BCMath + */ + public function abs() + { + $temp = new static(); + $temp->value = strlen($this->value) && $this->value[0] == '-' ? + substr($this->value, 1) : + $this->value; + + return $temp; + } + + /** + * Logical And + * + * @param BCMath $x + * @return BCMath + */ + public function bitwise_and(BCMath $x) + { + return $this->bitwiseAndHelper($x); + } + + /** + * Logical Or + * + * @param BCMath $x + * @return BCMath + */ + public function bitwise_or(BCMath $x) + { + return $this->bitwiseXorHelper($x); + } + + /** + * Logical Exclusive Or + * + * @param BCMath $x + * @return BCMath + */ + public function bitwise_xor(BCMath $x) + { + return $this->bitwiseXorHelper($x); + } + + /** + * Logical Right Shift + * + * Shifts BigInteger's by $shift bits, effectively dividing by 2**$shift. + * + * @param int $shift + * @return BCMath + */ + public function bitwise_rightShift($shift) + { + $temp = new static(); + $temp->value = bcdiv($this->value, bcpow('2', $shift, 0), 0); + + return $this->normalize($temp); + } + + /** + * Logical Left Shift + * + * Shifts BigInteger's by $shift bits, effectively multiplying by 2**$shift. + * + * @param int $shift + * @return BCMath + */ + public function bitwise_leftShift($shift) + { + $temp = new static(); + $temp->value = bcmul($this->value, bcpow('2', $shift, 0), 0); + + return $this->normalize($temp); + } + + /** + * Compares two numbers. + * + * Although one might think !$x->compare($y) means $x != $y, it, in fact, means the opposite. The reason for this + * is demonstrated thusly: + * + * $x > $y: $x->compare($y) > 0 + * $x < $y: $x->compare($y) < 0 + * $x == $y: $x->compare($y) == 0 + * + * Note how the same comparison operator is used. If you want to test for equality, use $x->equals($y). + * + * {@internal Could return $this->subtract($x), but that's not as fast as what we do do.} + * + * @param BCMath $y + * @return int in case < 0 if $this is less than $y; > 0 if $this is greater than $y, and 0 if they are equal. + * @see self::equals() + */ + public function compare(BCMath $y) + { + return bccomp($this->value, $y->value, 0); + } + + /** + * Tests the equality of two numbers. + * + * If you need to see if one number is greater than or less than another number, use BigInteger::compare() + * + * @param BCMath $x + * @return bool + */ + public function equals(BCMath $x) + { + return $this->value == $x->value; + } + + /** + * Performs modular exponentiation. + * + * @param BCMath $e + * @param BCMath $n + * @return BCMath + */ + public function modPow(BCMath $e, BCMath $n) + { + return $this->powModOuter($e, $n); + } + + /** + * Performs modular exponentiation. + * + * Alias for modPow(). + * + * @param BCMath $e + * @param BCMath $n + * @return BCMath + */ + public function powMod(BCMath $e, BCMath $n) + { + return $this->powModOuter($e, $n); + } + + /** + * Performs modular exponentiation. + * + * @param BCMath $e + * @param BCMath $n + * @return BCMath + */ + protected function powModInner(BCMath $e, BCMath $n) + { + try { + $class = static::$modexpEngine[static::class]; + return $class::powModHelper($this, $e, $n, static::class); + } catch (\Exception $err) { + return BCMath\DefaultEngine::powModHelper($this, $e, $n, static::class); + } + } + + /** + * Normalize + * + * Removes leading zeros and truncates (if necessary) to maintain the appropriate precision + * + * @param BCMath $result + * @return BCMath + */ + protected function normalize(BCMath $result) + { + $result->precision = $this->precision; + $result->bitmask = $this->bitmask; + + if ($result->bitmask !== false) { + $result->value = bcmod($result->value, $result->bitmask->value); + } + + return $result; + } + + /** + * Generate a random prime number between a range + * + * If there's not a prime within the given range, false will be returned. + * + * @param BCMath $min + * @param BCMath $max + * @return false|BCMath + */ + public static function randomRangePrime(BCMath $min, BCMath $max) + { + return self::randomRangePrimeOuter($min, $max); + } + + /** + * Generate a random number between a range + * + * Returns a random number between $min and $max where $min and $max + * can be defined using one of the two methods: + * + * BigInteger::randomRange($min, $max) + * BigInteger::randomRange($max, $min) + * + * @param BCMath $min + * @param BCMath $max + * @return BCMath + */ + public static function randomRange(BCMath $min, BCMath $max) + { + return self::randomRangeHelper($min, $max); + } + + /** + * Make the current number odd + * + * If the current number is odd it'll be unchanged. If it's even, one will be added to it. + * + * @see self::randomPrime() + */ + protected function make_odd() + { + if (!$this->isOdd()) { + $this->value = bcadd($this->value, '1'); + } + } + + /** + * Test the number against small primes. + * + * @see self::isPrime() + */ + protected function testSmallPrimes() + { + if ($this->value === '1') { + return false; + } + if ($this->value === '2') { + return true; + } + if ($this->value[strlen($this->value) - 1] % 2 == 0) { + return false; + } + + $value = $this->value; + + foreach (self::PRIMES as $prime) { + $r = bcmod($this->value, $prime); + if ($r == '0') { + return $this->value == $prime; + } + } + + return true; + } + + /** + * Scan for 1 and right shift by that amount + * + * ie. $s = gmp_scan1($n, 0) and $r = gmp_div_q($n, gmp_pow(gmp_init('2'), $s)); + * + * @param BCMath $r + * @return int + * @see self::isPrime() + */ + public static function scan1divide(BCMath $r) + { + $r_value = &$r->value; + $s = 0; + // if $n was 1, $r would be 0 and this would be an infinite loop, hence our $this->equals(static::$one[static::class]) check earlier + while ($r_value[strlen($r_value) - 1] % 2 == 0) { + $r_value = bcdiv($r_value, '2', 0); + ++$s; + } + + return $s; + } + + /** + * Performs exponentiation. + * + * @param BCMath $n + * @return BCMath + */ + public function pow(BCMath $n) + { + $temp = new self(); + $temp->value = bcpow($this->value, $n->value); + + return $this->normalize($temp); + } + + /** + * Return the minimum BigInteger between an arbitrary number of BigIntegers. + * + * @param BCMath ...$nums + * @return BCMath + */ + public static function min(BCMath ...$nums) + { + return self::minHelper($nums); + } + + /** + * Return the maximum BigInteger between an arbitrary number of BigIntegers. + * + * @param BCMath ...$nums + * @return BCMath + */ + public static function max(BCMath ...$nums) + { + return self::maxHelper($nums); + } + + /** + * Tests BigInteger to see if it is between two integers, inclusive + * + * @param BCMath $min + * @param BCMath $max + * @return bool + */ + public function between(BCMath $min, BCMath $max) + { + return $this->compare($min) >= 0 && $this->compare($max) <= 0; + } + + /** + * Set Bitmask + * + * @param int $bits + * @return Engine + * @see self::setPrecision() + */ + protected static function setBitmask($bits) + { + $temp = parent::setBitmask($bits); + return $temp->add(static::$one[static::class]); + } + + /** + * Is Odd? + * + * @return bool + */ + public function isOdd() + { + return $this->value[strlen($this->value) - 1] % 2 == 1; + } + + /** + * Tests if a bit is set + * + * @return bool + */ + public function testBit($x) + { + return bccomp( + bcmod($this->value, bcpow('2', $x + 1, 0)), + bcpow('2', $x, 0), + 0 + ) >= 0; + } + + /** + * Is Negative? + * + * @return bool + */ + public function isNegative() + { + return strlen($this->value) && $this->value[0] == '-'; + } + + /** + * Negate + * + * Given $k, returns -$k + * + * @return BCMath + */ + public function negate() + { + $temp = clone $this; + + if (!strlen($temp->value)) { + return $temp; + } + + $temp->value = $temp->value[0] == '-' ? + substr($this->value, 1) : + '-' . $this->value; + + return $temp; + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/BCMath/Base.php b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/BCMath/Base.php new file mode 100644 index 00000000..fe21e041 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/BCMath/Base.php @@ -0,0 +1,110 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Math\BigInteger\Engines\BCMath; + +use phpseclib3\Math\BigInteger\Engines\BCMath; + +/** + * Sliding Window Exponentiation Engine + * + * @author Jim Wigginton + */ +abstract class Base extends BCMath +{ + /** + * Cache constants + * + * $cache[self::VARIABLE] tells us whether or not the cached data is still valid. + * + */ + const VARIABLE = 0; + /** + * $cache[self::DATA] contains the cached data. + * + */ + const DATA = 1; + + /** + * Test for engine validity + * + * @return bool + */ + public static function isValidEngine() + { + return static::class != __CLASS__; + } + + /** + * Performs modular exponentiation. + * + * @param BCMath $x + * @param BCMath $e + * @param BCMath $n + * @param string $class + * @return BCMath + */ + protected static function powModHelper(BCMath $x, BCMath $e, BCMath $n, $class) + { + if (empty($e->value)) { + $temp = new $class(); + $temp->value = '1'; + return $x->normalize($temp); + } + + return $x->normalize(static::slidingWindow($x, $e, $n, $class)); + } + + /** + * Modular reduction preparation + * + * @param string $x + * @param string $n + * @param string $class + * @see self::slidingWindow() + * @return string + */ + protected static function prepareReduce($x, $n, $class) + { + return static::reduce($x, $n); + } + + /** + * Modular multiply + * + * @param string $x + * @param string $y + * @param string $n + * @param string $class + * @see self::slidingWindow() + * @return string + */ + protected static function multiplyReduce($x, $y, $n, $class) + { + return static::reduce(bcmul($x, $y), $n); + } + + /** + * Modular square + * + * @param string $x + * @param string $n + * @param string $class + * @see self::slidingWindow() + * @return string + */ + protected static function squareReduce($x, $n, $class) + { + return static::reduce(bcmul($x, $x), $n); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/BCMath/BuiltIn.php b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/BCMath/BuiltIn.php new file mode 100644 index 00000000..b7ca8a2c --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/BCMath/BuiltIn.php @@ -0,0 +1,40 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Math\BigInteger\Engines\BCMath; + +use phpseclib3\Math\BigInteger\Engines\BCMath; + +/** + * Built-In BCMath Modular Exponentiation Engine + * + * @author Jim Wigginton + */ +abstract class BuiltIn extends BCMath +{ + /** + * Performs modular exponentiation. + * + * @param BCMath $x + * @param BCMath $e + * @param BCMath $n + * @return BCMath + */ + protected static function powModHelper(BCMath $x, BCMath $e, BCMath $n) + { + $temp = new BCMath(); + $temp->value = bcpowmod($x->value, $e->value, $n->value); + + return $x->normalize($temp); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/BCMath/DefaultEngine.php b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/BCMath/DefaultEngine.php new file mode 100644 index 00000000..b2d9fa95 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/BCMath/DefaultEngine.php @@ -0,0 +1,25 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Math\BigInteger\Engines\BCMath; + +use phpseclib3\Math\BigInteger\Engines\BCMath\Reductions\Barrett; + +/** + * PHP Default Modular Exponentiation Engine + * + * @author Jim Wigginton + */ +abstract class DefaultEngine extends Barrett +{ +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/BCMath/OpenSSL.php b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/BCMath/OpenSSL.php new file mode 100644 index 00000000..aed94942 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/BCMath/OpenSSL.php @@ -0,0 +1,25 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Math\BigInteger\Engines\BCMath; + +use phpseclib3\Math\BigInteger\Engines\OpenSSL as Progenitor; + +/** + * OpenSSL Modular Exponentiation Engine + * + * @author Jim Wigginton + */ +abstract class OpenSSL extends Progenitor +{ +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/BCMath/Reductions/Barrett.php b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/BCMath/Reductions/Barrett.php new file mode 100644 index 00000000..0fb7eaeb --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/BCMath/Reductions/Barrett.php @@ -0,0 +1,187 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Math\BigInteger\Engines\BCMath\Reductions; + +use phpseclib3\Math\BigInteger\Engines\BCMath\Base; + +/** + * PHP Barrett Modular Exponentiation Engine + * + * @author Jim Wigginton + */ +abstract class Barrett extends Base +{ + /** + * Cache constants + * + * $cache[self::VARIABLE] tells us whether or not the cached data is still valid. + * + */ + const VARIABLE = 0; + /** + * $cache[self::DATA] contains the cached data. + * + */ + const DATA = 1; + + /** + * Barrett Modular Reduction + * + * See {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=14 HAC 14.3.3} / + * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=165 MPM 6.2.5} for more information. Modified slightly, + * so as not to require negative numbers (initially, this script didn't support negative numbers). + * + * Employs "folding", as described at + * {@link http://www.cosic.esat.kuleuven.be/publications/thesis-149.pdf#page=66 thesis-149.pdf#page=66}. To quote from + * it, "the idea [behind folding] is to find a value x' such that x (mod m) = x' (mod m), with x' being smaller than x." + * + * Unfortunately, the "Barrett Reduction with Folding" algorithm described in thesis-149.pdf is not, as written, all that + * usable on account of (1) its not using reasonable radix points as discussed in + * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=162 MPM 6.2.2} and (2) the fact that, even with reasonable + * radix points, it only works when there are an even number of digits in the denominator. The reason for (2) is that + * (x >> 1) + (x >> 1) != x / 2 + x / 2. If x is even, they're the same, but if x is odd, they're not. See the in-line + * comments for details. + * + * @param string $n + * @param string $m + * @return string + */ + protected static function reduce($n, $m) + { + static $cache = [ + self::VARIABLE => [], + self::DATA => [] + ]; + + $m_length = strlen($m); + + if (strlen($n) > 2 * $m_length) { + return bcmod($n, $m); + } + + // if (m.length >> 1) + 2 <= m.length then m is too small and n can't be reduced + if ($m_length < 5) { + return self::regularBarrett($n, $m); + } + // n = 2 * m.length + + if (($key = array_search($m, $cache[self::VARIABLE])) === false) { + $key = count($cache[self::VARIABLE]); + $cache[self::VARIABLE][] = $m; + + $lhs = '1' . str_repeat('0', $m_length + ($m_length >> 1)); + $u = bcdiv($lhs, $m, 0); + $m1 = bcsub($lhs, bcmul($u, $m)); + + $cache[self::DATA][] = [ + 'u' => $u, // m.length >> 1 (technically (m.length >> 1) + 1) + 'm1' => $m1 // m.length + ]; + } else { + extract($cache[self::DATA][$key]); + } + + $cutoff = $m_length + ($m_length >> 1); + + $lsd = substr($n, -$cutoff); + $msd = substr($n, 0, -$cutoff); + + $temp = bcmul($msd, $m1); // m.length + (m.length >> 1) + $n = bcadd($lsd, $temp); // m.length + (m.length >> 1) + 1 (so basically we're adding two same length numbers) + //if ($m_length & 1) { + // return self::regularBarrett($n, $m); + //} + + // (m.length + (m.length >> 1) + 1) - (m.length - 1) == (m.length >> 1) + 2 + $temp = substr($n, 0, -$m_length + 1); + // if even: ((m.length >> 1) + 2) + (m.length >> 1) == m.length + 2 + // if odd: ((m.length >> 1) + 2) + (m.length >> 1) == (m.length - 1) + 2 == m.length + 1 + $temp = bcmul($temp, $u); + // if even: (m.length + 2) - ((m.length >> 1) + 1) = m.length - (m.length >> 1) + 1 + // if odd: (m.length + 1) - ((m.length >> 1) + 1) = m.length - (m.length >> 1) + $temp = substr($temp, 0, -($m_length >> 1) - 1); + // if even: (m.length - (m.length >> 1) + 1) + m.length = 2 * m.length - (m.length >> 1) + 1 + // if odd: (m.length - (m.length >> 1)) + m.length = 2 * m.length - (m.length >> 1) + $temp = bcmul($temp, $m); + + // at this point, if m had an odd number of digits, we'd be subtracting a 2 * m.length - (m.length >> 1) digit + // number from a m.length + (m.length >> 1) + 1 digit number. ie. there'd be an extra digit and the while loop + // following this comment would loop a lot (hence our calling _regularBarrett() in that situation). + + $result = bcsub($n, $temp); + + //if (bccomp($result, '0') < 0) { + if ($result[0] == '-') { + $temp = '1' . str_repeat('0', $m_length + 1); + $result = bcadd($result, $temp); + } + + while (bccomp($result, $m) >= 0) { + $result = bcsub($result, $m); + } + + return $result; + } + + /** + * (Regular) Barrett Modular Reduction + * + * For numbers with more than four digits BigInteger::_barrett() is faster. The difference between that and this + * is that this function does not fold the denominator into a smaller form. + * + * @param string $x + * @param string $n + * @return string + */ + private static function regularBarrett($x, $n) + { + static $cache = [ + self::VARIABLE => [], + self::DATA => [] + ]; + + $n_length = strlen($n); + + if (strlen($x) > 2 * $n_length) { + return bcmod($x, $n); + } + + if (($key = array_search($n, $cache[self::VARIABLE])) === false) { + $key = count($cache[self::VARIABLE]); + $cache[self::VARIABLE][] = $n; + $lhs = '1' . str_repeat('0', 2 * $n_length); + $cache[self::DATA][] = bcdiv($lhs, $n, 0); + } + + $temp = substr($x, 0, -$n_length + 1); + $temp = bcmul($temp, $cache[self::DATA][$key]); + $temp = substr($temp, 0, -$n_length - 1); + + $r1 = substr($x, -$n_length - 1); + $r2 = substr(bcmul($temp, $n), -$n_length - 1); + $result = bcsub($r1, $r2); + + //if (bccomp($result, '0') < 0) { + if ($result[0] == '-') { + $q = '1' . str_repeat('0', $n_length + 1); + $result = bcadd($result, $q); + } + + while (bccomp($result, $n) >= 0) { + $result = bcsub($result, $n); + } + + return $result; + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/BCMath/Reductions/EvalBarrett.php b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/BCMath/Reductions/EvalBarrett.php new file mode 100644 index 00000000..e033ba57 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/BCMath/Reductions/EvalBarrett.php @@ -0,0 +1,108 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Math\BigInteger\Engines\BCMath\Reductions; + +use phpseclib3\Math\BigInteger\Engines\BCMath; +use phpseclib3\Math\BigInteger\Engines\BCMath\Base; + +/** + * PHP Barrett Modular Exponentiation Engine + * + * @author Jim Wigginton + */ +abstract class EvalBarrett extends Base +{ + /** + * Custom Reduction Function + * + * @see self::generateCustomReduction + */ + private static $custom_reduction; + + /** + * Barrett Modular Reduction + * + * This calls a dynamically generated loop unrolled function that's specific to a given modulo. + * Array lookups are avoided as are if statements testing for how many bits the host OS supports, etc. + * + * @param string $n + * @param string $m + * @return string + */ + protected static function reduce($n, $m) + { + $inline = self::$custom_reduction; + return $inline($n); + } + + /** + * Generate Custom Reduction + * + * @param BCMath $m + * @param string $class + * @return callable|void + */ + protected static function generateCustomReduction(BCMath $m, $class) + { + $m_length = strlen($m); + + if ($m_length < 5) { + $code = 'return bcmod($x, $n);'; + eval('$func = function ($n) { ' . $code . '};'); + self::$custom_reduction = $func; + return; + } + + $lhs = '1' . str_repeat('0', $m_length + ($m_length >> 1)); + $u = bcdiv($lhs, $m, 0); + $m1 = bcsub($lhs, bcmul($u, $m)); + + $cutoff = $m_length + ($m_length >> 1); + + $m = "'$m'"; + $u = "'$u'"; + $m1 = "'$m1'"; + + $code = ' + $lsd = substr($n, -' . $cutoff . '); + $msd = substr($n, 0, -' . $cutoff . '); + + $temp = bcmul($msd, ' . $m1 . '); + $n = bcadd($lsd, $temp); + + $temp = substr($n, 0, ' . (-$m_length + 1) . '); + $temp = bcmul($temp, ' . $u . '); + $temp = substr($temp, 0, ' . (-($m_length >> 1) - 1) . '); + $temp = bcmul($temp, ' . $m . '); + + $result = bcsub($n, $temp); + + if ($result[0] == \'-\') { + $temp = \'1' . str_repeat('0', $m_length + 1) . '\'; + $result = bcadd($result, $temp); + } + + while (bccomp($result, ' . $m . ') >= 0) { + $result = bcsub($result, ' . $m . '); + } + + return $result;'; + + eval('$func = function ($n) { ' . $code . '};'); + + self::$custom_reduction = $func; + + return $func; + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/Engine.php b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/Engine.php new file mode 100644 index 00000000..05d031c5 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/Engine.php @@ -0,0 +1,1278 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Math\BigInteger\Engines; + +use phpseclib3\Common\Functions\Strings; +use phpseclib3\Crypt\Random; +use phpseclib3\Exception\BadConfigurationException; +use phpseclib3\Math\BigInteger; + +/** + * Base Engine. + * + * @author Jim Wigginton + */ +abstract class Engine implements \JsonSerializable +{ + /* final protected */ const PRIMES = [ + 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, + 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, + 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, + 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, + 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, + 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, + 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617, + 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709, 719, 727, + 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, + 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, + 953, 967, 971, 977, 983, 991, 997, + ]; + + /** + * BigInteger(0) + * + * @var array, static> + */ + protected static $zero = []; + + /** + * BigInteger(1) + * + * @var array, static> + */ + protected static $one = []; + + /** + * BigInteger(2) + * + * @var array, static> + */ + protected static $two = []; + + /** + * Modular Exponentiation Engine + * + * @var array, class-string> + */ + protected static $modexpEngine; + + /** + * Engine Validity Flag + * + * @var array, bool> + */ + protected static $isValidEngine; + + /** + * Holds the BigInteger's value + * + * @var \GMP|string|array|int + */ + protected $value; + + /** + * Holds the BigInteger's sign + * + * @var bool + */ + protected $is_negative; + + /** + * Precision + * + * @see static::setPrecision() + * @var int + */ + protected $precision = -1; + + /** + * Precision Bitmask + * + * @see static::setPrecision() + * @var static|false + */ + protected $bitmask = false; + + /** + * Recurring Modulo Function + * + * @var callable + */ + protected $reduce; + + /** + * Mode independent value used for serialization. + * + * @see self::__sleep() + * @see self::__wakeup() + * @var string + */ + protected $hex; + + /** + * Default constructor + * + * @param int|numeric-string $x integer Base-10 number or base-$base number if $base set. + * @param int $base + */ + public function __construct($x = 0, $base = 10) + { + if (!array_key_exists(static::class, static::$zero)) { + static::$zero[static::class] = null; // Placeholder to prevent infinite loop. + static::$zero[static::class] = new static(0); + static::$one[static::class] = new static(1); + static::$two[static::class] = new static(2); + } + + // '0' counts as empty() but when the base is 256 '0' is equal to ord('0') or 48 + // '0' is the only value like this per http://php.net/empty + if (empty($x) && (abs($base) != 256 || $x !== '0')) { + return; + } + + switch ($base) { + case -256: + case 256: + if ($base == -256 && (ord($x[0]) & 0x80)) { + $this->value = ~$x; + $this->is_negative = true; + } else { + $this->value = $x; + $this->is_negative = false; + } + + $this->initialize($base); + + if ($this->is_negative) { + $temp = $this->add(new static('-1')); + $this->value = $temp->value; + } + break; + case -16: + case 16: + if ($base > 0 && $x[0] == '-') { + $this->is_negative = true; + $x = substr($x, 1); + } + + $x = preg_replace('#^(?:0x)?([A-Fa-f0-9]*).*#', '$1', $x); + + $is_negative = false; + if ($base < 0 && hexdec($x[0]) >= 8) { + $this->is_negative = $is_negative = true; + $x = Strings::bin2hex(~Strings::hex2bin($x)); + } + + $this->value = $x; + $this->initialize($base); + + if ($is_negative) { + $temp = $this->add(new static('-1')); + $this->value = $temp->value; + } + break; + case -10: + case 10: + // (?value = preg_replace('#(?value) || $this->value == '-') { + $this->value = '0'; + } + $this->initialize($base); + break; + case -2: + case 2: + if ($base > 0 && $x[0] == '-') { + $this->is_negative = true; + $x = substr($x, 1); + } + + $x = preg_replace('#^([01]*).*#', '$1', $x); + + $temp = new static(Strings::bits2bin($x), 128 * $base); // ie. either -16 or +16 + $this->value = $temp->value; + if ($temp->is_negative) { + $this->is_negative = true; + } + + break; + default: + // base not supported, so we'll let $this == 0 + } + } + + /** + * Sets engine type. + * + * Throws an exception if the type is invalid + * + * @param class-string $engine + */ + public static function setModExpEngine($engine) + { + $fqengine = '\\phpseclib3\\Math\\BigInteger\\Engines\\' . static::ENGINE_DIR . '\\' . $engine; + if (!class_exists($fqengine) || !method_exists($fqengine, 'isValidEngine')) { + throw new \InvalidArgumentException("$engine is not a valid engine"); + } + if (!$fqengine::isValidEngine()) { + throw new BadConfigurationException("$engine is not setup correctly on this system"); + } + static::$modexpEngine[static::class] = $fqengine; + } + + /** + * Converts a BigInteger to a byte string (eg. base-256). + * + * Negative numbers are saved as positive numbers, unless $twos_compliment is set to true, at which point, they're + * saved as two's compliment. + * @return string + */ + protected function toBytesHelper() + { + $comparison = $this->compare(new static()); + if ($comparison == 0) { + return $this->precision > 0 ? str_repeat(chr(0), ($this->precision + 1) >> 3) : ''; + } + + $temp = $comparison < 0 ? $this->add(new static(1)) : $this; + $bytes = $temp->toBytes(); + + if (!strlen($bytes)) { // eg. if the number we're trying to convert is -1 + $bytes = chr(0); + } + + if (ord($bytes[0]) & 0x80) { + $bytes = chr(0) . $bytes; + } + + return $comparison < 0 ? ~$bytes : $bytes; + } + + /** + * Converts a BigInteger to a hex string (eg. base-16). + * + * @param bool $twos_compliment + * @return string + */ + public function toHex($twos_compliment = false) + { + return Strings::bin2hex($this->toBytes($twos_compliment)); + } + + /** + * Converts a BigInteger to a bit string (eg. base-2). + * + * Negative numbers are saved as positive numbers, unless $twos_compliment is set to true, at which point, they're + * saved as two's compliment. + * + * @param bool $twos_compliment + * @return string + */ + public function toBits($twos_compliment = false) + { + $hex = $this->toBytes($twos_compliment); + $bits = Strings::bin2bits($hex); + + $result = $this->precision > 0 ? substr($bits, -$this->precision) : ltrim($bits, '0'); + + if ($twos_compliment && $this->compare(new static()) > 0 && $this->precision <= 0) { + return '0' . $result; + } + + return $result; + } + + /** + * Calculates modular inverses. + * + * Say you have (30 mod 17 * x mod 17) mod 17 == 1. x can be found using modular inverses. + * + * {@internal See {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=21 HAC 14.64} for more information.} + * + * @param Engine $n + * @return static|false + */ + protected function modInverseHelper(Engine $n) + { + // $x mod -$n == $x mod $n. + $n = $n->abs(); + + if ($this->compare(static::$zero[static::class]) < 0) { + $temp = $this->abs(); + $temp = $temp->modInverse($n); + return $this->normalize($n->subtract($temp)); + } + + extract($this->extendedGCD($n)); + /** + * @var Engine $gcd + * @var Engine $x + */ + + if (!$gcd->equals(static::$one[static::class])) { + return false; + } + + $x = $x->compare(static::$zero[static::class]) < 0 ? $x->add($n) : $x; + + return $this->compare(static::$zero[static::class]) < 0 ? $this->normalize($n->subtract($x)) : $this->normalize($x); + } + + /** + * Serialize + * + * Will be called, automatically, when serialize() is called on a BigInteger object. + * + * @return array + */ + public function __sleep() + { + $this->hex = $this->toHex(true); + $vars = ['hex']; + if ($this->precision > 0) { + $vars[] = 'precision'; + } + return $vars; + } + + /** + * Serialize + * + * Will be called, automatically, when unserialize() is called on a BigInteger object. + * + * @return void + */ + public function __wakeup() + { + $temp = new static($this->hex, -16); + $this->value = $temp->value; + $this->is_negative = $temp->is_negative; + if ($this->precision > 0) { + // recalculate $this->bitmask + $this->setPrecision($this->precision); + } + } + + /** + * JSON Serialize + * + * Will be called, automatically, when json_encode() is called on a BigInteger object. + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + $result = ['hex' => $this->toHex(true)]; + if ($this->precision > 0) { + $result['precision'] = $this->precision; + } + return $result; + } + + /** + * Converts a BigInteger to a base-10 number. + * + * @return string + */ + public function __toString() + { + return $this->toString(); + } + + /** + * __debugInfo() magic method + * + * Will be called, automatically, when print_r() or var_dump() are called + * + * @return array + */ + public function __debugInfo() + { + $result = [ + 'value' => '0x' . $this->toHex(true), + 'engine' => basename(static::class) + ]; + return $this->precision > 0 ? $result + ['precision' => $this->precision] : $result; + } + + /** + * Set Precision + * + * Some bitwise operations give different results depending on the precision being used. Examples include left + * shift, not, and rotates. + * + * @param int $bits + */ + public function setPrecision($bits) + { + if ($bits < 1) { + $this->precision = -1; + $this->bitmask = false; + + return; + } + $this->precision = $bits; + $this->bitmask = static::setBitmask($bits); + + $temp = $this->normalize($this); + $this->value = $temp->value; + } + + /** + * Get Precision + * + * Returns the precision if it exists, -1 if it doesn't + * + * @return int + */ + public function getPrecision() + { + return $this->precision; + } + + /** + * Set Bitmask + * @return static + * @param int $bits + * @see self::setPrecision() + */ + protected static function setBitmask($bits) + { + return new static(chr((1 << ($bits & 0x7)) - 1) . str_repeat(chr(0xFF), $bits >> 3), 256); + } + + /** + * Logical Not + * + * @return Engine|string + */ + public function bitwise_not() + { + // calculuate "not" without regard to $this->precision + // (will always result in a smaller number. ie. ~1 isn't 1111 1110 - it's 0) + $temp = $this->toBytes(); + if ($temp == '') { + return $this->normalize(static::$zero[static::class]); + } + $pre_msb = decbin(ord($temp[0])); + $temp = ~$temp; + $msb = decbin(ord($temp[0])); + if (strlen($msb) == 8) { + $msb = substr($msb, strpos($msb, '0')); + } + $temp[0] = chr(bindec($msb)); + + // see if we need to add extra leading 1's + $current_bits = strlen($pre_msb) + 8 * strlen($temp) - 8; + $new_bits = $this->precision - $current_bits; + if ($new_bits <= 0) { + return $this->normalize(new static($temp, 256)); + } + + // generate as many leading 1's as we need to. + $leading_ones = chr((1 << ($new_bits & 0x7)) - 1) . str_repeat(chr(0xFF), $new_bits >> 3); + + self::base256_lshift($leading_ones, $current_bits); + + $temp = str_pad($temp, strlen($leading_ones), chr(0), STR_PAD_LEFT); + + return $this->normalize(new static($leading_ones | $temp, 256)); + } + + /** + * Logical Left Shift + * + * Shifts binary strings $shift bits, essentially multiplying by 2**$shift. + * + * @param string $x + * @param int $shift + * @return void + */ + protected static function base256_lshift(&$x, $shift) + { + if ($shift == 0) { + return; + } + + $num_bytes = $shift >> 3; // eg. floor($shift/8) + $shift &= 7; // eg. $shift % 8 + + $carry = 0; + for ($i = strlen($x) - 1; $i >= 0; --$i) { + $temp = ord($x[$i]) << $shift | $carry; + $x[$i] = chr($temp); + $carry = $temp >> 8; + } + $carry = ($carry != 0) ? chr($carry) : ''; + $x = $carry . $x . str_repeat(chr(0), $num_bytes); + } + + /** + * Logical Left Rotate + * + * Instead of the top x bits being dropped they're appended to the shifted bit string. + * + * @param int $shift + * @return Engine + */ + public function bitwise_leftRotate($shift) + { + $bits = $this->toBytes(); + + if ($this->precision > 0) { + $precision = $this->precision; + if (static::FAST_BITWISE) { + $mask = $this->bitmask->toBytes(); + } else { + $mask = $this->bitmask->subtract(new static(1)); + $mask = $mask->toBytes(); + } + } else { + $temp = ord($bits[0]); + for ($i = 0; $temp >> $i; ++$i) { + } + $precision = 8 * strlen($bits) - 8 + $i; + $mask = chr((1 << ($precision & 0x7)) - 1) . str_repeat(chr(0xFF), $precision >> 3); + } + + if ($shift < 0) { + $shift += $precision; + } + $shift %= $precision; + + if (!$shift) { + return clone $this; + } + + $left = $this->bitwise_leftShift($shift); + $left = $left->bitwise_and(new static($mask, 256)); + $right = $this->bitwise_rightShift($precision - $shift); + $result = static::FAST_BITWISE ? $left->bitwise_or($right) : $left->add($right); + return $this->normalize($result); + } + + /** + * Logical Right Rotate + * + * Instead of the bottom x bits being dropped they're prepended to the shifted bit string. + * + * @param int $shift + * @return Engine + */ + public function bitwise_rightRotate($shift) + { + return $this->bitwise_leftRotate(-$shift); + } + + /** + * Returns the smallest and largest n-bit number + * + * @param int $bits + * @return array{min: static, max: static} + */ + public static function minMaxBits($bits) + { + $bytes = $bits >> 3; + $min = str_repeat(chr(0), $bytes); + $max = str_repeat(chr(0xFF), $bytes); + $msb = $bits & 7; + if ($msb) { + $min = chr(1 << ($msb - 1)) . $min; + $max = chr((1 << $msb) - 1) . $max; + } else { + $min[0] = chr(0x80); + } + return [ + 'min' => new static($min, 256), + 'max' => new static($max, 256) + ]; + } + + /** + * Return the size of a BigInteger in bits + * + * @return int + */ + public function getLength() + { + return strlen($this->toBits()); + } + + /** + * Return the size of a BigInteger in bytes + * + * @return int + */ + public function getLengthInBytes() + { + return strlen($this->toBytes()); + } + + /** + * Performs some pre-processing for powMod + * + * @param Engine $e + * @param Engine $n + * @return static|false + */ + protected function powModOuter(Engine $e, Engine $n) + { + $n = $this->bitmask !== false && $this->bitmask->compare($n) < 0 ? $this->bitmask : $n->abs(); + + if ($e->compare(new static()) < 0) { + $e = $e->abs(); + + $temp = $this->modInverse($n); + if ($temp === false) { + return false; + } + + return $this->normalize($temp->powModInner($e, $n)); + } + + return $this->powModInner($e, $n); + } + + /** + * Sliding Window k-ary Modular Exponentiation + * + * Based on {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=27 HAC 14.85} / + * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=210 MPM 7.7}. In a departure from those algorithims, + * however, this function performs a modular reduction after every multiplication and squaring operation. + * As such, this function has the same preconditions that the reductions being used do. + * + * @template T of Engine + * @param Engine $x + * @param Engine $e + * @param Engine $n + * @param class-string $class + * @return T + */ + protected static function slidingWindow(Engine $x, Engine $e, Engine $n, $class) + { + static $window_ranges = [7, 25, 81, 241, 673, 1793]; // from BigInteger.java's oddModPow function + //static $window_ranges = [0, 7, 36, 140, 450, 1303, 3529]; // from MPM 7.3.1 + + $e_bits = $e->toBits(); + $e_length = strlen($e_bits); + + // calculate the appropriate window size. + // $window_size == 3 if $window_ranges is between 25 and 81, for example. + for ($i = 0, $window_size = 1; $i < count($window_ranges) && $e_length > $window_ranges[$i]; ++$window_size, ++$i) { + } + + $n_value = $n->value; + + if (method_exists(static::class, 'generateCustomReduction')) { + static::generateCustomReduction($n, $class); + } + + // precompute $this^0 through $this^$window_size + $powers = []; + $powers[1] = static::prepareReduce($x->value, $n_value, $class); + $powers[2] = static::squareReduce($powers[1], $n_value, $class); + + // we do every other number since substr($e_bits, $i, $j+1) (see below) is supposed to end + // in a 1. ie. it's supposed to be odd. + $temp = 1 << ($window_size - 1); + for ($i = 1; $i < $temp; ++$i) { + $i2 = $i << 1; + $powers[$i2 + 1] = static::multiplyReduce($powers[$i2 - 1], $powers[2], $n_value, $class); + } + + $result = new $class(1); + $result = static::prepareReduce($result->value, $n_value, $class); + + for ($i = 0; $i < $e_length;) { + if (!$e_bits[$i]) { + $result = static::squareReduce($result, $n_value, $class); + ++$i; + } else { + for ($j = $window_size - 1; $j > 0; --$j) { + if (!empty($e_bits[$i + $j])) { + break; + } + } + + // eg. the length of substr($e_bits, $i, $j + 1) + for ($k = 0; $k <= $j; ++$k) { + $result = static::squareReduce($result, $n_value, $class); + } + + $result = static::multiplyReduce($result, $powers[bindec(substr($e_bits, $i, $j + 1))], $n_value, $class); + + $i += $j + 1; + } + } + + $temp = new $class(); + $temp->value = static::reduce($result, $n_value, $class); + + return $temp; + } + + /** + * Generates a random number of a certain size + * + * Bit length is equal to $size + * + * @param int $size + * @return Engine + */ + public static function random($size) + { + extract(static::minMaxBits($size)); + /** + * @var BigInteger $min + * @var BigInteger $max + */ + return static::randomRange($min, $max); + } + + /** + * Generates a random prime number of a certain size + * + * Bit length is equal to $size + * + * @param int $size + * @return Engine + */ + public static function randomPrime($size) + { + extract(static::minMaxBits($size)); + /** + * @var static $min + * @var static $max + */ + return static::randomRangePrime($min, $max); + } + + /** + * Performs some pre-processing for randomRangePrime + * + * @param Engine $min + * @param Engine $max + * @return static|false + */ + protected static function randomRangePrimeOuter(Engine $min, Engine $max) + { + $compare = $max->compare($min); + + if (!$compare) { + return $min->isPrime() ? $min : false; + } elseif ($compare < 0) { + // if $min is bigger then $max, swap $min and $max + $temp = $max; + $max = $min; + $min = $temp; + } + + $x = static::randomRange($min, $max); + + return static::randomRangePrimeInner($x, $min, $max); + } + + /** + * Generate a random number between a range + * + * Returns a random number between $min and $max where $min and $max + * can be defined using one of the two methods: + * + * BigInteger::randomRange($min, $max) + * BigInteger::randomRange($max, $min) + * + * @param Engine $min + * @param Engine $max + * @return Engine + */ + protected static function randomRangeHelper(Engine $min, Engine $max) + { + $compare = $max->compare($min); + + if (!$compare) { + return $min; + } elseif ($compare < 0) { + // if $min is bigger then $max, swap $min and $max + $temp = $max; + $max = $min; + $min = $temp; + } + + if (!isset(static::$one[static::class])) { + static::$one[static::class] = new static(1); + } + + $max = $max->subtract($min->subtract(static::$one[static::class])); + + $size = strlen(ltrim($max->toBytes(), chr(0))); + + /* + doing $random % $max doesn't work because some numbers will be more likely to occur than others. + eg. if $max is 140 and $random's max is 255 then that'd mean both $random = 5 and $random = 145 + would produce 5 whereas the only value of random that could produce 139 would be 139. ie. + not all numbers would be equally likely. some would be more likely than others. + + creating a whole new random number until you find one that is within the range doesn't work + because, for sufficiently small ranges, the likelihood that you'd get a number within that range + would be pretty small. eg. with $random's max being 255 and if your $max being 1 the probability + would be pretty high that $random would be greater than $max. + + phpseclib works around this using the technique described here: + + http://crypto.stackexchange.com/questions/5708/creating-a-small-number-from-a-cryptographically-secure-random-string + */ + $random_max = new static(chr(1) . str_repeat("\0", $size), 256); + $random = new static(Random::string($size), 256); + + list($max_multiple) = $random_max->divide($max); + $max_multiple = $max_multiple->multiply($max); + + while ($random->compare($max_multiple) >= 0) { + $random = $random->subtract($max_multiple); + $random_max = $random_max->subtract($max_multiple); + $random = $random->bitwise_leftShift(8); + $random = $random->add(new static(Random::string(1), 256)); + $random_max = $random_max->bitwise_leftShift(8); + list($max_multiple) = $random_max->divide($max); + $max_multiple = $max_multiple->multiply($max); + } + list(, $random) = $random->divide($max); + + return $random->add($min); + } + + /** + * Performs some post-processing for randomRangePrime + * + * @param Engine $x + * @param Engine $min + * @param Engine $max + * @return static|false + */ + protected static function randomRangePrimeInner(Engine $x, Engine $min, Engine $max) + { + if (!isset(static::$two[static::class])) { + static::$two[static::class] = new static('2'); + } + + $x->make_odd(); + if ($x->compare($max) > 0) { + // if $x > $max then $max is even and if $min == $max then no prime number exists between the specified range + if ($min->equals($max)) { + return false; + } + $x = clone $min; + $x->make_odd(); + } + + $initial_x = clone $x; + + while (true) { + if ($x->isPrime()) { + return $x; + } + + $x = $x->add(static::$two[static::class]); + + if ($x->compare($max) > 0) { + $x = clone $min; + if ($x->equals(static::$two[static::class])) { + return $x; + } + $x->make_odd(); + } + + if ($x->equals($initial_x)) { + return false; + } + } + } + + /** + * Sets the $t parameter for primality testing + * + * @return int + */ + protected function setupIsPrime() + { + $length = $this->getLengthInBytes(); + + // see HAC 4.49 "Note (controlling the error probability)" + // @codingStandardsIgnoreStart + if ($length >= 163) { $t = 2; } // floor(1300 / 8) + else if ($length >= 106) { $t = 3; } // floor( 850 / 8) + else if ($length >= 81 ) { $t = 4; } // floor( 650 / 8) + else if ($length >= 68 ) { $t = 5; } // floor( 550 / 8) + else if ($length >= 56 ) { $t = 6; } // floor( 450 / 8) + else if ($length >= 50 ) { $t = 7; } // floor( 400 / 8) + else if ($length >= 43 ) { $t = 8; } // floor( 350 / 8) + else if ($length >= 37 ) { $t = 9; } // floor( 300 / 8) + else if ($length >= 31 ) { $t = 12; } // floor( 250 / 8) + else if ($length >= 25 ) { $t = 15; } // floor( 200 / 8) + else if ($length >= 18 ) { $t = 18; } // floor( 150 / 8) + else { $t = 27; } + // @codingStandardsIgnoreEnd + + return $t; + } + + /** + * Tests Primality + * + * Uses the {@link http://en.wikipedia.org/wiki/Miller%E2%80%93Rabin_primality_test Miller-Rabin primality test}. + * See {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap4.pdf#page=8 HAC 4.24} for more info. + * + * @param int $t + * @return bool + */ + protected function testPrimality($t) + { + if (!$this->testSmallPrimes()) { + return false; + } + + $n = clone $this; + $n_1 = $n->subtract(static::$one[static::class]); + $n_2 = $n->subtract(static::$two[static::class]); + + $r = clone $n_1; + $s = static::scan1divide($r); + + for ($i = 0; $i < $t; ++$i) { + $a = static::randomRange(static::$two[static::class], $n_2); + $y = $a->modPow($r, $n); + + if (!$y->equals(static::$one[static::class]) && !$y->equals($n_1)) { + for ($j = 1; $j < $s && !$y->equals($n_1); ++$j) { + $y = $y->modPow(static::$two[static::class], $n); + if ($y->equals(static::$one[static::class])) { + return false; + } + } + + if (!$y->equals($n_1)) { + return false; + } + } + } + + return true; + } + + /** + * Checks a numer to see if it's prime + * + * Assuming the $t parameter is not set, this function has an error rate of 2**-80. The main motivation for the + * $t parameter is distributability. BigInteger::randomPrime() can be distributed across multiple pageloads + * on a website instead of just one. + * + * @param int|bool $t + * @return bool + */ + public function isPrime($t = false) + { + if (!$t) { + $t = $this->setupIsPrime(); + } + return $this->testPrimality($t); + } + + /** + * Performs a few preliminary checks on root + * + * @param int $n + * @return Engine + */ + protected function rootHelper($n) + { + if ($n < 1) { + return clone static::$zero[static::class]; + } // we want positive exponents + if ($this->compare(static::$one[static::class]) < 0) { + return clone static::$zero[static::class]; + } // we want positive numbers + if ($this->compare(static::$two[static::class]) < 0) { + return clone static::$one[static::class]; + } // n-th root of 1 or 2 is 1 + + return $this->rootInner($n); + } + + /** + * Calculates the nth root of a biginteger. + * + * Returns the nth root of a positive biginteger, where n defaults to 2 + * + * {@internal This function is based off of {@link http://mathforum.org/library/drmath/view/52605.html this page} and {@link http://stackoverflow.com/questions/11242920/calculating-nth-root-with-bcmath-in-php this stackoverflow question}.} + * + * @param int $n + * @return Engine + */ + protected function rootInner($n) + { + $n = new static($n); + + // g is our guess number + $g = static::$two[static::class]; + // while (g^n < num) g=g*2 + while ($g->pow($n)->compare($this) < 0) { + $g = $g->multiply(static::$two[static::class]); + } + // if (g^n==num) num is a power of 2, we're lucky, end of job + // == 0 bccomp(bcpow($g, $n), $n->value)==0 + if ($g->pow($n)->equals($this) > 0) { + $root = $g; + return $this->normalize($root); + } + + // if we're here num wasn't a power of 2 :( + $og = $g; // og means original guess and here is our upper bound + $g = $g->divide(static::$two[static::class])[0]; // g is set to be our lower bound + $step = $og->subtract($g)->divide(static::$two[static::class])[0]; // step is the half of upper bound - lower bound + $g = $g->add($step); // we start at lower bound + step , basically in the middle of our interval + + // while step>1 + + while ($step->compare(static::$one[static::class]) == 1) { + $guess = $g->pow($n); + $step = $step->divide(static::$two[static::class])[0]; + $comp = $guess->compare($this); // compare our guess with real number + switch ($comp) { + case -1: // if guess is lower we add the new step + $g = $g->add($step); + break; + case 1: // if guess is higher we sub the new step + $g = $g->subtract($step); + break; + case 0: // if guess is exactly the num we're done, we return the value + $root = $g; + break 2; + } + } + + if ($comp == 1) { + $g = $g->subtract($step); + } + + // whatever happened, g is the closest guess we can make so return it + $root = $g; + + return $this->normalize($root); + } + + /** + * Calculates the nth root of a biginteger. + * + * @param int $n + * @return Engine + */ + public function root($n = 2) + { + return $this->rootHelper($n); + } + + /** + * Return the minimum BigInteger between an arbitrary number of BigIntegers. + * + * @param array $nums + * @return Engine + */ + protected static function minHelper(array $nums) + { + if (count($nums) == 1) { + return $nums[0]; + } + $min = $nums[0]; + for ($i = 1; $i < count($nums); $i++) { + $min = $min->compare($nums[$i]) > 0 ? $nums[$i] : $min; + } + return $min; + } + + /** + * Return the minimum BigInteger between an arbitrary number of BigIntegers. + * + * @param array $nums + * @return Engine + */ + protected static function maxHelper(array $nums) + { + if (count($nums) == 1) { + return $nums[0]; + } + $max = $nums[0]; + for ($i = 1; $i < count($nums); $i++) { + $max = $max->compare($nums[$i]) < 0 ? $nums[$i] : $max; + } + return $max; + } + + /** + * Create Recurring Modulo Function + * + * Sometimes it may be desirable to do repeated modulos with the same number outside of + * modular exponentiation + * + * @return callable + */ + public function createRecurringModuloFunction() + { + $class = static::class; + + $fqengine = !method_exists(static::$modexpEngine[static::class], 'reduce') ? + '\\phpseclib3\\Math\\BigInteger\\Engines\\' . static::ENGINE_DIR . '\\DefaultEngine' : + static::$modexpEngine[static::class]; + if (method_exists($fqengine, 'generateCustomReduction')) { + $func = $fqengine::generateCustomReduction($this, static::class); + return eval('return function(' . static::class . ' $x) use ($func, $class) { + $r = new $class(); + $r->value = $func($x->value); + return $r; + };'); + } + $n = $this->value; + return eval('return function(' . static::class . ' $x) use ($n, $fqengine, $class) { + $r = new $class(); + $r->value = $fqengine::reduce($x->value, $n, $class); + return $r; + };'); + } + + /** + * Calculates the greatest common divisor and Bezout's identity. + * + * @param Engine $n + * @return array{gcd: Engine, x: Engine, y: Engine} + */ + protected function extendedGCDHelper(Engine $n) + { + $u = clone $this; + $v = clone $n; + + $one = new static(1); + $zero = new static(); + + $a = clone $one; + $b = clone $zero; + $c = clone $zero; + $d = clone $one; + + while (!$v->equals($zero)) { + list($q) = $u->divide($v); + + $temp = $u; + $u = $v; + $v = $temp->subtract($v->multiply($q)); + + $temp = $a; + $a = $c; + $c = $temp->subtract($a->multiply($q)); + + $temp = $b; + $b = $d; + $d = $temp->subtract($b->multiply($q)); + } + + return [ + 'gcd' => $u, + 'x' => $a, + 'y' => $b + ]; + } + + /** + * Bitwise Split + * + * Splits BigInteger's into chunks of $split bits + * + * @param int $split + * @return Engine[] + */ + public function bitwise_split($split) + { + if ($split < 1) { + throw new \RuntimeException('Offset must be greater than 1'); + } + + $mask = static::$one[static::class]->bitwise_leftShift($split)->subtract(static::$one[static::class]); + + $num = clone $this; + + $vals = []; + while (!$num->equals(static::$zero[static::class])) { + $vals[] = $num->bitwise_and($mask); + $num = $num->bitwise_rightShift($split); + } + + return array_reverse($vals); + } + + /** + * Logical And + * + * @param Engine $x + * @return Engine + */ + protected function bitwiseAndHelper(Engine $x) + { + $left = $this->toBytes(true); + $right = $x->toBytes(true); + + $length = max(strlen($left), strlen($right)); + + $left = str_pad($left, $length, chr(0), STR_PAD_LEFT); + $right = str_pad($right, $length, chr(0), STR_PAD_LEFT); + + return $this->normalize(new static($left & $right, -256)); + } + + /** + * Logical Or + * + * @param Engine $x + * @return Engine + */ + protected function bitwiseOrHelper(Engine $x) + { + $left = $this->toBytes(true); + $right = $x->toBytes(true); + + $length = max(strlen($left), strlen($right)); + + $left = str_pad($left, $length, chr(0), STR_PAD_LEFT); + $right = str_pad($right, $length, chr(0), STR_PAD_LEFT); + + return $this->normalize(new static($left | $right, -256)); + } + + /** + * Logical Exclusive Or + * + * @param Engine $x + * @return Engine + */ + protected function bitwiseXorHelper(Engine $x) + { + $left = $this->toBytes(true); + $right = $x->toBytes(true); + + $length = max(strlen($left), strlen($right)); + + + $left = str_pad($left, $length, chr(0), STR_PAD_LEFT); + $right = str_pad($right, $length, chr(0), STR_PAD_LEFT); + return $this->normalize(new static($left ^ $right, -256)); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/GMP.php b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/GMP.php new file mode 100644 index 00000000..f6163629 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/GMP.php @@ -0,0 +1,694 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Math\BigInteger\Engines; + +use phpseclib3\Exception\BadConfigurationException; + +/** + * GMP Engine. + * + * @author Jim Wigginton + */ +class GMP extends Engine +{ + /** + * Can Bitwise operations be done fast? + * + * @see parent::bitwise_leftRotate() + * @see parent::bitwise_rightRotate() + */ + const FAST_BITWISE = true; + + /** + * Engine Directory + * + * @see parent::setModExpEngine + */ + const ENGINE_DIR = 'GMP'; + + /** + * Test for engine validity + * + * @return bool + * @see parent::__construct() + */ + public static function isValidEngine() + { + return extension_loaded('gmp'); + } + + /** + * Default constructor + * + * @param mixed $x integer Base-10 number or base-$base number if $base set. + * @param int $base + * @see parent::__construct() + */ + public function __construct($x = 0, $base = 10) + { + if (!isset(static::$isValidEngine[static::class])) { + static::$isValidEngine[static::class] = self::isValidEngine(); + } + if (!static::$isValidEngine[static::class]) { + throw new BadConfigurationException('GMP is not setup correctly on this system'); + } + + if ($x instanceof \GMP) { + $this->value = $x; + return; + } + + $this->value = gmp_init(0); + + parent::__construct($x, $base); + } + + /** + * Initialize a GMP BigInteger Engine instance + * + * @param int $base + * @see parent::__construct() + */ + protected function initialize($base) + { + switch (abs($base)) { + case 256: + $this->value = gmp_import($this->value); + if ($this->is_negative) { + $this->value = -$this->value; + } + break; + case 16: + $temp = $this->is_negative ? '-0x' . $this->value : '0x' . $this->value; + $this->value = gmp_init($temp); + break; + case 10: + $this->value = gmp_init(isset($this->value) ? $this->value : '0'); + } + } + + /** + * Converts a BigInteger to a base-10 number. + * + * @return string + */ + public function toString() + { + return (string)$this->value; + } + + /** + * Converts a BigInteger to a bit string (eg. base-2). + * + * Negative numbers are saved as positive numbers, unless $twos_compliment is set to true, at which point, they're + * saved as two's compliment. + * + * @param bool $twos_compliment + * @return string + */ + public function toBits($twos_compliment = false) + { + $hex = $this->toHex($twos_compliment); + + $bits = gmp_strval(gmp_init($hex, 16), 2); + + if ($this->precision > 0) { + $bits = substr($bits, -$this->precision); + } + + if ($twos_compliment && $this->compare(new static()) > 0 && $this->precision <= 0) { + return '0' . $bits; + } + + return $bits; + } + + /** + * Converts a BigInteger to a byte string (eg. base-256). + * + * @param bool $twos_compliment + * @return string + */ + public function toBytes($twos_compliment = false) + { + if ($twos_compliment) { + return $this->toBytesHelper(); + } + + if (gmp_cmp($this->value, gmp_init(0)) == 0) { + return $this->precision > 0 ? str_repeat(chr(0), ($this->precision + 1) >> 3) : ''; + } + + $temp = gmp_export($this->value); + + return $this->precision > 0 ? + substr(str_pad($temp, $this->precision >> 3, chr(0), STR_PAD_LEFT), -($this->precision >> 3)) : + ltrim($temp, chr(0)); + } + + /** + * Adds two BigIntegers. + * + * @param GMP $y + * @return GMP + */ + public function add(GMP $y) + { + $temp = new self(); + $temp->value = $this->value + $y->value; + + return $this->normalize($temp); + } + + /** + * Subtracts two BigIntegers. + * + * @param GMP $y + * @return GMP + */ + public function subtract(GMP $y) + { + $temp = new self(); + $temp->value = $this->value - $y->value; + + return $this->normalize($temp); + } + + /** + * Multiplies two BigIntegers. + * + * @param GMP $x + * @return GMP + */ + public function multiply(GMP $x) + { + $temp = new self(); + $temp->value = $this->value * $x->value; + + return $this->normalize($temp); + } + + /** + * Divides two BigIntegers. + * + * Returns an array whose first element contains the quotient and whose second element contains the + * "common residue". If the remainder would be positive, the "common residue" and the remainder are the + * same. If the remainder would be negative, the "common residue" is equal to the sum of the remainder + * and the divisor (basically, the "common residue" is the first positive modulo). + * + * @param GMP $y + * @return array{GMP, GMP} + */ + public function divide(GMP $y) + { + $quotient = new self(); + $remainder = new self(); + + list($quotient->value, $remainder->value) = gmp_div_qr($this->value, $y->value); + + if (gmp_sign($remainder->value) < 0) { + $remainder->value = $remainder->value + gmp_abs($y->value); + } + + return [$this->normalize($quotient), $this->normalize($remainder)]; + } + + /** + * Compares two numbers. + * + * Although one might think !$x->compare($y) means $x != $y, it, in fact, means the opposite. The reason for this + * is demonstrated thusly: + * + * $x > $y: $x->compare($y) > 0 + * $x < $y: $x->compare($y) < 0 + * $x == $y: $x->compare($y) == 0 + * + * Note how the same comparison operator is used. If you want to test for equality, use $x->equals($y). + * + * {@internal Could return $this->subtract($x), but that's not as fast as what we do do.} + * + * @param GMP $y + * @return int in case < 0 if $this is less than $y; > 0 if $this is greater than $y, and 0 if they are equal. + * @see self::equals() + */ + public function compare(GMP $y) + { + $r = gmp_cmp($this->value, $y->value); + if ($r < -1) { + $r = -1; + } + if ($r > 1) { + $r = 1; + } + return $r; + } + + /** + * Tests the equality of two numbers. + * + * If you need to see if one number is greater than or less than another number, use BigInteger::compare() + * + * @param GMP $x + * @return bool + */ + public function equals(GMP $x) + { + return $this->value == $x->value; + } + + /** + * Calculates modular inverses. + * + * Say you have (30 mod 17 * x mod 17) mod 17 == 1. x can be found using modular inverses. + * + * @param GMP $n + * @return false|GMP + */ + public function modInverse(GMP $n) + { + $temp = new self(); + $temp->value = gmp_invert($this->value, $n->value); + + return $temp->value === false ? false : $this->normalize($temp); + } + + /** + * Calculates the greatest common divisor and Bezout's identity. + * + * Say you have 693 and 609. The GCD is 21. Bezout's identity states that there exist integers x and y such that + * 693*x + 609*y == 21. In point of fact, there are actually an infinite number of x and y combinations and which + * combination is returned is dependent upon which mode is in use. See + * {@link http://en.wikipedia.org/wiki/B%C3%A9zout%27s_identity Bezout's identity - Wikipedia} for more information. + * + * @param GMP $n + * @return GMP[] + */ + public function extendedGCD(GMP $n) + { + extract(gmp_gcdext($this->value, $n->value)); + + return [ + 'gcd' => $this->normalize(new self($g)), + 'x' => $this->normalize(new self($s)), + 'y' => $this->normalize(new self($t)) + ]; + } + + /** + * Calculates the greatest common divisor + * + * Say you have 693 and 609. The GCD is 21. + * + * @param GMP $n + * @return GMP + */ + public function gcd(GMP $n) + { + $r = gmp_gcd($this->value, $n->value); + return $this->normalize(new self($r)); + } + + /** + * Absolute value. + * + * @return GMP + */ + public function abs() + { + $temp = new self(); + $temp->value = gmp_abs($this->value); + + return $temp; + } + + /** + * Logical And + * + * @param GMP $x + * @return GMP + */ + public function bitwise_and(GMP $x) + { + $temp = new self(); + $temp->value = $this->value & $x->value; + + return $this->normalize($temp); + } + + /** + * Logical Or + * + * @param GMP $x + * @return GMP + */ + public function bitwise_or(GMP $x) + { + $temp = new self(); + $temp->value = $this->value | $x->value; + + return $this->normalize($temp); + } + + /** + * Logical Exclusive Or + * + * @param GMP $x + * @return GMP + */ + public function bitwise_xor(GMP $x) + { + $temp = new self(); + $temp->value = $this->value ^ $x->value; + + return $this->normalize($temp); + } + + /** + * Logical Right Shift + * + * Shifts BigInteger's by $shift bits, effectively dividing by 2**$shift. + * + * @param int $shift + * @return GMP + */ + public function bitwise_rightShift($shift) + { + // 0xFFFFFFFF >> 2 == -1 (on 32-bit systems) + // gmp_init('0xFFFFFFFF') >> 2 == gmp_init('0x3FFFFFFF') + + $temp = new self(); + $temp->value = $this->value >> $shift; + + return $this->normalize($temp); + } + + /** + * Logical Left Shift + * + * Shifts BigInteger's by $shift bits, effectively multiplying by 2**$shift. + * + * @param int $shift + * @return GMP + */ + public function bitwise_leftShift($shift) + { + $temp = new self(); + $temp->value = $this->value << $shift; + + return $this->normalize($temp); + } + + /** + * Performs modular exponentiation. + * + * @param GMP $e + * @param GMP $n + * @return GMP + */ + public function modPow(GMP $e, GMP $n) + { + return $this->powModOuter($e, $n); + } + + /** + * Performs modular exponentiation. + * + * Alias for modPow(). + * + * @param GMP $e + * @param GMP $n + * @return GMP + */ + public function powMod(GMP $e, GMP $n) + { + return $this->powModOuter($e, $n); + } + + /** + * Performs modular exponentiation. + * + * @param GMP $e + * @param GMP $n + * @return GMP + */ + protected function powModInner(GMP $e, GMP $n) + { + $class = static::$modexpEngine[static::class]; + return $class::powModHelper($this, $e, $n); + } + + /** + * Normalize + * + * Removes leading zeros and truncates (if necessary) to maintain the appropriate precision + * + * @param GMP $result + * @return GMP + */ + protected function normalize(GMP $result) + { + $result->precision = $this->precision; + $result->bitmask = $this->bitmask; + + if ($result->bitmask !== false) { + $flip = $result->value < 0; + if ($flip) { + $result->value = -$result->value; + } + $result->value = $result->value & $result->bitmask->value; + if ($flip) { + $result->value = -$result->value; + } + } + + return $result; + } + + /** + * Performs some post-processing for randomRangePrime + * + * @param Engine $x + * @param Engine $min + * @param Engine $max + * @return GMP + */ + protected static function randomRangePrimeInner(Engine $x, Engine $min, Engine $max) + { + $p = gmp_nextprime($x->value); + + if ($p <= $max->value) { + return new self($p); + } + + if ($min->value != $x->value) { + $x = new self($x->value - 1); + } + + return self::randomRangePrime($min, $x); + } + + /** + * Generate a random prime number between a range + * + * If there's not a prime within the given range, false will be returned. + * + * @param GMP $min + * @param GMP $max + * @return false|GMP + */ + public static function randomRangePrime(GMP $min, GMP $max) + { + return self::randomRangePrimeOuter($min, $max); + } + + /** + * Generate a random number between a range + * + * Returns a random number between $min and $max where $min and $max + * can be defined using one of the two methods: + * + * BigInteger::randomRange($min, $max) + * BigInteger::randomRange($max, $min) + * + * @param GMP $min + * @param GMP $max + * @return GMP + */ + public static function randomRange(GMP $min, GMP $max) + { + return self::randomRangeHelper($min, $max); + } + + /** + * Make the current number odd + * + * If the current number is odd it'll be unchanged. If it's even, one will be added to it. + * + * @see self::randomPrime() + */ + protected function make_odd() + { + gmp_setbit($this->value, 0); + } + + /** + * Tests Primality + * + * @param int $t + * @return bool + */ + protected function testPrimality($t) + { + return gmp_prob_prime($this->value, $t) != 0; + } + + /** + * Calculates the nth root of a biginteger. + * + * Returns the nth root of a positive biginteger, where n defaults to 2 + * + * @param int $n + * @return GMP + */ + protected function rootInner($n) + { + $root = new self(); + $root->value = gmp_root($this->value, $n); + return $this->normalize($root); + } + + /** + * Performs exponentiation. + * + * @param GMP $n + * @return GMP + */ + public function pow(GMP $n) + { + $temp = new self(); + $temp->value = $this->value ** $n->value; + + return $this->normalize($temp); + } + + /** + * Return the minimum BigInteger between an arbitrary number of BigIntegers. + * + * @param GMP ...$nums + * @return GMP + */ + public static function min(GMP ...$nums) + { + return self::minHelper($nums); + } + + /** + * Return the maximum BigInteger between an arbitrary number of BigIntegers. + * + * @param GMP ...$nums + * @return GMP + */ + public static function max(GMP ...$nums) + { + return self::maxHelper($nums); + } + + /** + * Tests BigInteger to see if it is between two integers, inclusive + * + * @param GMP $min + * @param GMP $max + * @return bool + */ + public function between(GMP $min, GMP $max) + { + return $this->compare($min) >= 0 && $this->compare($max) <= 0; + } + + /** + * Create Recurring Modulo Function + * + * Sometimes it may be desirable to do repeated modulos with the same number outside of + * modular exponentiation + * + * @return callable + */ + public function createRecurringModuloFunction() + { + $temp = $this->value; + return function (GMP $x) use ($temp) { + return new GMP($x->value % $temp); + }; + } + + /** + * Scan for 1 and right shift by that amount + * + * ie. $s = gmp_scan1($n, 0) and $r = gmp_div_q($n, gmp_pow(gmp_init('2'), $s)); + * + * @param GMP $r + * @return int + */ + public static function scan1divide(GMP $r) + { + $s = gmp_scan1($r->value, 0); + $r->value >>= $s; + return $s; + } + + /** + * Is Odd? + * + * @return bool + */ + public function isOdd() + { + return gmp_testbit($this->value, 0); + } + + /** + * Tests if a bit is set + * + * @return bool + */ + public function testBit($x) + { + return gmp_testbit($this->value, $x); + } + + /** + * Is Negative? + * + * @return bool + */ + public function isNegative() + { + return gmp_sign($this->value) == -1; + } + + /** + * Negate + * + * Given $k, returns -$k + * + * @return GMP + */ + public function negate() + { + $temp = clone $this; + $temp->value = -$this->value; + + return $temp; + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/GMP/DefaultEngine.php b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/GMP/DefaultEngine.php new file mode 100644 index 00000000..bc219fbe --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/GMP/DefaultEngine.php @@ -0,0 +1,40 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Math\BigInteger\Engines\GMP; + +use phpseclib3\Math\BigInteger\Engines\GMP; + +/** + * GMP Modular Exponentiation Engine + * + * @author Jim Wigginton + */ +abstract class DefaultEngine extends GMP +{ + /** + * Performs modular exponentiation. + * + * @param GMP $x + * @param GMP $e + * @param GMP $n + * @return GMP + */ + protected static function powModHelper(GMP $x, GMP $e, GMP $n) + { + $temp = new GMP(); + $temp->value = gmp_powm($x->value, $e->value, $n->value); + + return $x->normalize($temp); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/OpenSSL.php b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/OpenSSL.php new file mode 100644 index 00000000..e33a9f19 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/OpenSSL.php @@ -0,0 +1,68 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Math\BigInteger\Engines; + +use phpseclib3\Crypt\RSA\Formats\Keys\PKCS8; +use phpseclib3\Math\BigInteger; + +/** + * OpenSSL Modular Exponentiation Engine + * + * @author Jim Wigginton + */ +abstract class OpenSSL +{ + /** + * Test for engine validity + * + * @return bool + */ + public static function isValidEngine() + { + return extension_loaded('openssl') && static::class != __CLASS__; + } + + /** + * Performs modular exponentiation. + * + * @param Engine $x + * @param Engine $e + * @param Engine $n + * @return Engine + */ + public static function powModHelper(Engine $x, Engine $e, Engine $n) + { + if ($n->getLengthInBytes() < 31 || $n->getLengthInBytes() > 16384) { + throw new \OutOfRangeException('Only modulo between 31 and 16384 bits are accepted'); + } + + $key = PKCS8::savePublicKey( + new BigInteger($n), + new BigInteger($e) + ); + + $plaintext = str_pad($x->toBytes(), $n->getLengthInBytes(), "\0", STR_PAD_LEFT); + + // this is easily prone to failure. if the modulo is a multiple of 2 or 3 or whatever it + // won't work and you'll get a "failure: error:0906D06C:PEM routines:PEM_read_bio:no start line" + // error. i suppose, for even numbers, we could do what PHP\Montgomery.php does, but then what + // about odd numbers divisible by 3, by 5, etc? + if (!openssl_public_encrypt($plaintext, $result, $key, OPENSSL_NO_PADDING)) { + throw new \UnexpectedValueException(openssl_error_string()); + } + + $class = get_class($x); + return new $class($result, 256); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP.php b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP.php new file mode 100644 index 00000000..ab9bdc99 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP.php @@ -0,0 +1,1329 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Math\BigInteger\Engines; + +use phpseclib3\Common\Functions\Strings; +use phpseclib3\Exception\BadConfigurationException; + +/** + * Pure-PHP Engine. + * + * @author Jim Wigginton + */ +abstract class PHP extends Engine +{ + /**#@+ + * Array constants + * + * Rather than create a thousands and thousands of new BigInteger objects in repeated function calls to add() and + * multiply() or whatever, we'll just work directly on arrays, taking them in as parameters and returning them. + * + */ + /** + * $result[self::VALUE] contains the value. + */ + const VALUE = 0; + /** + * $result[self::SIGN] contains the sign. + */ + const SIGN = 1; + /**#@-*/ + + /** + * Karatsuba Cutoff + * + * At what point do we switch between Karatsuba multiplication and schoolbook long multiplication? + * + */ + const KARATSUBA_CUTOFF = 25; + + /** + * Can Bitwise operations be done fast? + * + * @see parent::bitwise_leftRotate() + * @see parent::bitwise_rightRotate() + */ + const FAST_BITWISE = true; + + /** + * Engine Directory + * + * @see parent::setModExpEngine + */ + const ENGINE_DIR = 'PHP'; + + /** + * Default constructor + * + * @param mixed $x integer Base-10 number or base-$base number if $base set. + * @param int $base + * @return PHP + * @see parent::__construct() + */ + public function __construct($x = 0, $base = 10) + { + if (!isset(static::$isValidEngine[static::class])) { + static::$isValidEngine[static::class] = static::isValidEngine(); + } + if (!static::$isValidEngine[static::class]) { + throw new BadConfigurationException(static::class . ' is not setup correctly on this system'); + } + + $this->value = []; + parent::__construct($x, $base); + } + + /** + * Initialize a PHP BigInteger Engine instance + * + * @param int $base + * @see parent::__construct() + */ + protected function initialize($base) + { + switch (abs($base)) { + case 16: + $x = (strlen($this->value) & 1) ? '0' . $this->value : $this->value; + $temp = new static(Strings::hex2bin($x), 256); + $this->value = $temp->value; + break; + case 10: + $temp = new static(); + + $multiplier = new static(); + $multiplier->value = [static::MAX10]; + + $x = $this->value; + + if ($x[0] == '-') { + $this->is_negative = true; + $x = substr($x, 1); + } + + $x = str_pad( + $x, + strlen($x) + ((static::MAX10LEN - 1) * strlen($x)) % static::MAX10LEN, + 0, + STR_PAD_LEFT + ); + while (strlen($x)) { + $temp = $temp->multiply($multiplier); + $temp = $temp->add(new static($this->int2bytes(substr($x, 0, static::MAX10LEN)), 256)); + $x = substr($x, static::MAX10LEN); + } + + $this->value = $temp->value; + } + } + + /** + * Pads strings so that unpack may be used on them + * + * @param string $str + * @return string + */ + protected function pad($str) + { + $length = strlen($str); + + $pad = 4 - (strlen($str) % 4); + + return str_pad($str, $length + $pad, "\0", STR_PAD_LEFT); + } + + /** + * Converts a BigInteger to a base-10 number. + * + * @return string + */ + public function toString() + { + if (!count($this->value)) { + return '0'; + } + + $temp = clone $this; + $temp->bitmask = false; + $temp->is_negative = false; + + $divisor = new static(); + $divisor->value = [static::MAX10]; + $result = ''; + while (count($temp->value)) { + list($temp, $mod) = $temp->divide($divisor); + $result = str_pad( + isset($mod->value[0]) ? $mod->value[0] : '', + static::MAX10LEN, + '0', + STR_PAD_LEFT + ) . $result; + } + $result = ltrim($result, '0'); + if (empty($result)) { + $result = '0'; + } + + if ($this->is_negative) { + $result = '-' . $result; + } + + return $result; + } + + /** + * Converts a BigInteger to a byte string (eg. base-256). + * + * @param bool $twos_compliment + * @return string + */ + public function toBytes($twos_compliment = false) + { + if ($twos_compliment) { + return $this->toBytesHelper(); + } + + if (!count($this->value)) { + return $this->precision > 0 ? str_repeat(chr(0), ($this->precision + 1) >> 3) : ''; + } + + $result = $this->bitwise_small_split(8); + $result = implode('', array_map('chr', $result)); + + return $this->precision > 0 ? + str_pad( + substr($result, -(($this->precision + 7) >> 3)), + ($this->precision + 7) >> 3, + chr(0), + STR_PAD_LEFT + ) : + $result; + } + + /** + * Performs addition. + * + * @param array $x_value + * @param bool $x_negative + * @param array $y_value + * @param bool $y_negative + * @return array + */ + protected static function addHelper(array $x_value, $x_negative, array $y_value, $y_negative) + { + $x_size = count($x_value); + $y_size = count($y_value); + + if ($x_size == 0) { + return [ + self::VALUE => $y_value, + self::SIGN => $y_negative + ]; + } elseif ($y_size == 0) { + return [ + self::VALUE => $x_value, + self::SIGN => $x_negative + ]; + } + + // subtract, if appropriate + if ($x_negative != $y_negative) { + if ($x_value == $y_value) { + return [ + self::VALUE => [], + self::SIGN => false + ]; + } + + $temp = self::subtractHelper($x_value, false, $y_value, false); + $temp[self::SIGN] = self::compareHelper($x_value, false, $y_value, false) > 0 ? + $x_negative : $y_negative; + + return $temp; + } + + if ($x_size < $y_size) { + $size = $x_size; + $value = $y_value; + } else { + $size = $y_size; + $value = $x_value; + } + + $value[count($value)] = 0; // just in case the carry adds an extra digit + + $carry = 0; + for ($i = 0, $j = 1; $j < $size; $i += 2, $j += 2) { + //$sum = $x_value[$j] * static::BASE_FULL + $x_value[$i] + $y_value[$j] * static::BASE_FULL + $y_value[$i] + $carry; + $sum = ($x_value[$j] + $y_value[$j]) * static::BASE_FULL + $x_value[$i] + $y_value[$i] + $carry; + $carry = $sum >= static::MAX_DIGIT2; // eg. floor($sum / 2**52); only possible values (in any base) are 0 and 1 + $sum = $carry ? $sum - static::MAX_DIGIT2 : $sum; + + $temp = static::BASE === 26 ? intval($sum / 0x4000000) : ($sum >> 31); + + $value[$i] = (int)($sum - static::BASE_FULL * $temp); // eg. a faster alternative to fmod($sum, 0x4000000) + $value[$j] = $temp; + } + + if ($j == $size) { // ie. if $y_size is odd + $sum = $x_value[$i] + $y_value[$i] + $carry; + $carry = $sum >= static::BASE_FULL; + $value[$i] = $carry ? $sum - static::BASE_FULL : $sum; + ++$i; // ie. let $i = $j since we've just done $value[$i] + } + + if ($carry) { + for (; $value[$i] == static::MAX_DIGIT; ++$i) { + $value[$i] = 0; + } + ++$value[$i]; + } + + return [ + self::VALUE => self::trim($value), + self::SIGN => $x_negative + ]; + } + + /** + * Performs subtraction. + * + * @param array $x_value + * @param bool $x_negative + * @param array $y_value + * @param bool $y_negative + * @return array + */ + public static function subtractHelper(array $x_value, $x_negative, array $y_value, $y_negative) + { + $x_size = count($x_value); + $y_size = count($y_value); + + if ($x_size == 0) { + return [ + self::VALUE => $y_value, + self::SIGN => !$y_negative + ]; + } elseif ($y_size == 0) { + return [ + self::VALUE => $x_value, + self::SIGN => $x_negative + ]; + } + + // add, if appropriate (ie. -$x - +$y or +$x - -$y) + if ($x_negative != $y_negative) { + $temp = self::addHelper($x_value, false, $y_value, false); + $temp[self::SIGN] = $x_negative; + + return $temp; + } + + $diff = self::compareHelper($x_value, $x_negative, $y_value, $y_negative); + + if (!$diff) { + return [ + self::VALUE => [], + self::SIGN => false + ]; + } + + // switch $x and $y around, if appropriate. + if ((!$x_negative && $diff < 0) || ($x_negative && $diff > 0)) { + $temp = $x_value; + $x_value = $y_value; + $y_value = $temp; + + $x_negative = !$x_negative; + + $x_size = count($x_value); + $y_size = count($y_value); + } + + // at this point, $x_value should be at least as big as - if not bigger than - $y_value + + $carry = 0; + for ($i = 0, $j = 1; $j < $y_size; $i += 2, $j += 2) { + $sum = ($x_value[$j] - $y_value[$j]) * static::BASE_FULL + $x_value[$i] - $y_value[$i] - $carry; + + $carry = $sum < 0; // eg. floor($sum / 2**52); only possible values (in any base) are 0 and 1 + $sum = $carry ? $sum + static::MAX_DIGIT2 : $sum; + + $temp = static::BASE === 26 ? intval($sum / 0x4000000) : ($sum >> 31); + + $x_value[$i] = (int)($sum - static::BASE_FULL * $temp); + $x_value[$j] = $temp; + } + + if ($j == $y_size) { // ie. if $y_size is odd + $sum = $x_value[$i] - $y_value[$i] - $carry; + $carry = $sum < 0; + $x_value[$i] = $carry ? $sum + static::BASE_FULL : $sum; + ++$i; + } + + if ($carry) { + for (; !$x_value[$i]; ++$i) { + $x_value[$i] = static::MAX_DIGIT; + } + --$x_value[$i]; + } + + return [ + self::VALUE => self::trim($x_value), + self::SIGN => $x_negative + ]; + } + + /** + * Performs multiplication. + * + * @param array $x_value + * @param bool $x_negative + * @param array $y_value + * @param bool $y_negative + * @return array + */ + protected static function multiplyHelper(array $x_value, $x_negative, array $y_value, $y_negative) + { + //if ( $x_value == $y_value ) { + // return [ + // self::VALUE => self::square($x_value), + // self::SIGN => $x_sign != $y_value + // ]; + //} + + $x_length = count($x_value); + $y_length = count($y_value); + + if (!$x_length || !$y_length) { // a 0 is being multiplied + return [ + self::VALUE => [], + self::SIGN => false + ]; + } + + return [ + self::VALUE => min($x_length, $y_length) < 2 * self::KARATSUBA_CUTOFF ? + self::trim(self::regularMultiply($x_value, $y_value)) : + self::trim(self::karatsuba($x_value, $y_value)), + self::SIGN => $x_negative != $y_negative + ]; + } + + /** + * Performs Karatsuba multiplication on two BigIntegers + * + * See {@link http://en.wikipedia.org/wiki/Karatsuba_algorithm Karatsuba algorithm} and + * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=120 MPM 5.2.3}. + * + * @param array $x_value + * @param array $y_value + * @return array + */ + private static function karatsuba(array $x_value, array $y_value) + { + $m = min(count($x_value) >> 1, count($y_value) >> 1); + + if ($m < self::KARATSUBA_CUTOFF) { + return self::regularMultiply($x_value, $y_value); + } + + $x1 = array_slice($x_value, $m); + $x0 = array_slice($x_value, 0, $m); + $y1 = array_slice($y_value, $m); + $y0 = array_slice($y_value, 0, $m); + + $z2 = self::karatsuba($x1, $y1); + $z0 = self::karatsuba($x0, $y0); + + $z1 = self::addHelper($x1, false, $x0, false); + $temp = self::addHelper($y1, false, $y0, false); + $z1 = self::karatsuba($z1[self::VALUE], $temp[self::VALUE]); + $temp = self::addHelper($z2, false, $z0, false); + $z1 = self::subtractHelper($z1, false, $temp[self::VALUE], false); + + $z2 = array_merge(array_fill(0, 2 * $m, 0), $z2); + $z1[self::VALUE] = array_merge(array_fill(0, $m, 0), $z1[self::VALUE]); + + $xy = self::addHelper($z2, false, $z1[self::VALUE], $z1[self::SIGN]); + $xy = self::addHelper($xy[self::VALUE], $xy[self::SIGN], $z0, false); + + return $xy[self::VALUE]; + } + + /** + * Performs long multiplication on two BigIntegers + * + * Modeled after 'multiply' in MutableBigInteger.java. + * + * @param array $x_value + * @param array $y_value + * @return array + */ + protected static function regularMultiply(array $x_value, array $y_value) + { + $x_length = count($x_value); + $y_length = count($y_value); + + if (!$x_length || !$y_length) { // a 0 is being multiplied + return []; + } + + $product_value = self::array_repeat(0, $x_length + $y_length); + + // the following for loop could be removed if the for loop following it + // (the one with nested for loops) initially set $i to 0, but + // doing so would also make the result in one set of unnecessary adds, + // since on the outermost loops first pass, $product->value[$k] is going + // to always be 0 + + $carry = 0; + for ($j = 0; $j < $x_length; ++$j) { // ie. $i = 0 + $temp = $x_value[$j] * $y_value[0] + $carry; // $product_value[$k] == 0 + $carry = static::BASE === 26 ? intval($temp / 0x4000000) : ($temp >> 31); + $product_value[$j] = (int)($temp - static::BASE_FULL * $carry); + } + + $product_value[$j] = $carry; + + // the above for loop is what the previous comment was talking about. the + // following for loop is the "one with nested for loops" + for ($i = 1; $i < $y_length; ++$i) { + $carry = 0; + + for ($j = 0, $k = $i; $j < $x_length; ++$j, ++$k) { + $temp = $product_value[$k] + $x_value[$j] * $y_value[$i] + $carry; + $carry = static::BASE === 26 ? intval($temp / 0x4000000) : ($temp >> 31); + $product_value[$k] = (int)($temp - static::BASE_FULL * $carry); + } + + $product_value[$k] = $carry; + } + + return $product_value; + } + + /** + * Divides two BigIntegers. + * + * Returns an array whose first element contains the quotient and whose second element contains the + * "common residue". If the remainder would be positive, the "common residue" and the remainder are the + * same. If the remainder would be negative, the "common residue" is equal to the sum of the remainder + * and the divisor (basically, the "common residue" is the first positive modulo). + * + * @return array{static, static} + * @internal This function is based off of + * {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=9 HAC 14.20}. + */ + protected function divideHelper(PHP $y) + { + if (count($y->value) == 1) { + list($q, $r) = $this->divide_digit($this->value, $y->value[0]); + $quotient = new static(); + $remainder = new static(); + $quotient->value = $q; + $remainder->value = [$r]; + $quotient->is_negative = $this->is_negative != $y->is_negative; + return [$this->normalize($quotient), $this->normalize($remainder)]; + } + + $x = clone $this; + $y = clone $y; + + $x_sign = $x->is_negative; + $y_sign = $y->is_negative; + + $x->is_negative = $y->is_negative = false; + + $diff = $x->compare($y); + + if (!$diff) { + $temp = new static(); + $temp->value = [1]; + $temp->is_negative = $x_sign != $y_sign; + return [$this->normalize($temp), $this->normalize(static::$zero[static::class])]; + } + + if ($diff < 0) { + // if $x is negative, "add" $y. + if ($x_sign) { + $x = $y->subtract($x); + } + return [$this->normalize(static::$zero[static::class]), $this->normalize($x)]; + } + + // normalize $x and $y as described in HAC 14.23 / 14.24 + $msb = $y->value[count($y->value) - 1]; + for ($shift = 0; !($msb & static::MSB); ++$shift) { + $msb <<= 1; + } + $x->lshift($shift); + $y->lshift($shift); + $y_value = &$y->value; + + $x_max = count($x->value) - 1; + $y_max = count($y->value) - 1; + + $quotient = new static(); + $quotient_value = &$quotient->value; + $quotient_value = self::array_repeat(0, $x_max - $y_max + 1); + + static $temp, $lhs, $rhs; + if (!isset($temp)) { + $temp = new static(); + $lhs = new static(); + $rhs = new static(); + } + if (static::class != get_class($temp)) { + $temp = new static(); + $lhs = new static(); + $rhs = new static(); + } + $temp_value = &$temp->value; + $rhs_value = &$rhs->value; + + // $temp = $y << ($x_max - $y_max-1) in base 2**26 + $temp_value = array_merge(self::array_repeat(0, $x_max - $y_max), $y_value); + + while ($x->compare($temp) >= 0) { + // calculate the "common residue" + ++$quotient_value[$x_max - $y_max]; + $x = $x->subtract($temp); + $x_max = count($x->value) - 1; + } + + for ($i = $x_max; $i >= $y_max + 1; --$i) { + $x_value = &$x->value; + $x_window = [ + isset($x_value[$i]) ? $x_value[$i] : 0, + isset($x_value[$i - 1]) ? $x_value[$i - 1] : 0, + isset($x_value[$i - 2]) ? $x_value[$i - 2] : 0 + ]; + $y_window = [ + $y_value[$y_max], + ($y_max > 0) ? $y_value[$y_max - 1] : 0 + ]; + + $q_index = $i - $y_max - 1; + if ($x_window[0] == $y_window[0]) { + $quotient_value[$q_index] = static::MAX_DIGIT; + } else { + $quotient_value[$q_index] = self::safe_divide( + $x_window[0] * static::BASE_FULL + $x_window[1], + $y_window[0] + ); + } + + $temp_value = [$y_window[1], $y_window[0]]; + + $lhs->value = [$quotient_value[$q_index]]; + $lhs = $lhs->multiply($temp); + + $rhs_value = [$x_window[2], $x_window[1], $x_window[0]]; + + while ($lhs->compare($rhs) > 0) { + --$quotient_value[$q_index]; + + $lhs->value = [$quotient_value[$q_index]]; + $lhs = $lhs->multiply($temp); + } + + $adjust = self::array_repeat(0, $q_index); + $temp_value = [$quotient_value[$q_index]]; + $temp = $temp->multiply($y); + $temp_value = &$temp->value; + if (count($temp_value)) { + $temp_value = array_merge($adjust, $temp_value); + } + + $x = $x->subtract($temp); + + if ($x->compare(static::$zero[static::class]) < 0) { + $temp_value = array_merge($adjust, $y_value); + $x = $x->add($temp); + + --$quotient_value[$q_index]; + } + + $x_max = count($x_value) - 1; + } + + // unnormalize the remainder + $x->rshift($shift); + + $quotient->is_negative = $x_sign != $y_sign; + + // calculate the "common residue", if appropriate + if ($x_sign) { + $y->rshift($shift); + $x = $y->subtract($x); + } + + return [$this->normalize($quotient), $this->normalize($x)]; + } + + /** + * Divides a BigInteger by a regular integer + * + * abc / x = a00 / x + b0 / x + c / x + * + * @param array $dividend + * @param int $divisor + * @return array + */ + private static function divide_digit(array $dividend, $divisor) + { + $carry = 0; + $result = []; + + for ($i = count($dividend) - 1; $i >= 0; --$i) { + $temp = static::BASE_FULL * $carry + $dividend[$i]; + $result[$i] = self::safe_divide($temp, $divisor); + $carry = (int)($temp - $divisor * $result[$i]); + } + + return [$result, $carry]; + } + + /** + * Single digit division + * + * Even if int64 is being used the division operator will return a float64 value + * if the dividend is not evenly divisible by the divisor. Since a float64 doesn't + * have the precision of int64 this is a problem so, when int64 is being used, + * we'll guarantee that the dividend is divisible by first subtracting the remainder. + * + * @param int $x + * @param int $y + * @return int + */ + private static function safe_divide($x, $y) + { + if (static::BASE === 26) { + return (int)($x / $y); + } + + // static::BASE === 31 + /** @var int */ + return ($x - ($x % $y)) / $y; + } + + /** + * Convert an array / boolean to a PHP BigInteger object + * + * @param array $arr + * @return static + */ + protected function convertToObj(array $arr) + { + $result = new static(); + $result->value = $arr[self::VALUE]; + $result->is_negative = $arr[self::SIGN]; + + return $this->normalize($result); + } + + /** + * Normalize + * + * Removes leading zeros and truncates (if necessary) to maintain the appropriate precision + * + * @param PHP $result + * @return static + */ + protected function normalize(PHP $result) + { + $result->precision = $this->precision; + $result->bitmask = $this->bitmask; + + $value = &$result->value; + + if (!count($value)) { + $result->is_negative = false; + return $result; + } + + $value = static::trim($value); + + if (!empty($result->bitmask->value)) { + $length = min(count($value), count($result->bitmask->value)); + $value = array_slice($value, 0, $length); + + for ($i = 0; $i < $length; ++$i) { + $value[$i] = $value[$i] & $result->bitmask->value[$i]; + } + + $value = static::trim($value); + } + + return $result; + } + + /** + * Compares two numbers. + * + * @param array $x_value + * @param bool $x_negative + * @param array $y_value + * @param bool $y_negative + * @return int + * @see static::compare() + */ + protected static function compareHelper(array $x_value, $x_negative, array $y_value, $y_negative) + { + if ($x_negative != $y_negative) { + return (!$x_negative && $y_negative) ? 1 : -1; + } + + $result = $x_negative ? -1 : 1; + + if (count($x_value) != count($y_value)) { + return (count($x_value) > count($y_value)) ? $result : -$result; + } + $size = max(count($x_value), count($y_value)); + + $x_value = array_pad($x_value, $size, 0); + $y_value = array_pad($y_value, $size, 0); + + for ($i = count($x_value) - 1; $i >= 0; --$i) { + if ($x_value[$i] != $y_value[$i]) { + return ($x_value[$i] > $y_value[$i]) ? $result : -$result; + } + } + + return 0; + } + + /** + * Absolute value. + * + * @return PHP + */ + public function abs() + { + $temp = new static(); + $temp->value = $this->value; + + return $temp; + } + + /** + * Trim + * + * Removes leading zeros + * + * @param list $value + * @return list + */ + protected static function trim(array $value) + { + for ($i = count($value) - 1; $i >= 0; --$i) { + if ($value[$i]) { + break; + } + unset($value[$i]); + } + + return $value; + } + + /** + * Logical Right Shift + * + * Shifts BigInteger's by $shift bits, effectively dividing by 2**$shift. + * + * @param int $shift + * @return PHP + */ + public function bitwise_rightShift($shift) + { + $temp = new static(); + + // could just replace lshift with this, but then all lshift() calls would need to be rewritten + // and I don't want to do that... + $temp->value = $this->value; + $temp->rshift($shift); + + return $this->normalize($temp); + } + + /** + * Logical Left Shift + * + * Shifts BigInteger's by $shift bits, effectively multiplying by 2**$shift. + * + * @param int $shift + * @return PHP + */ + public function bitwise_leftShift($shift) + { + $temp = new static(); + // could just replace _rshift with this, but then all _lshift() calls would need to be rewritten + // and I don't want to do that... + $temp->value = $this->value; + $temp->lshift($shift); + + return $this->normalize($temp); + } + + /** + * Converts 32-bit integers to bytes. + * + * @param int $x + * @return string + */ + private static function int2bytes($x) + { + return ltrim(pack('N', $x), chr(0)); + } + + /** + * Array Repeat + * + * @param int $input + * @param int $multiplier + * @return array + */ + protected static function array_repeat($input, $multiplier) + { + return $multiplier ? array_fill(0, $multiplier, $input) : []; + } + + /** + * Logical Left Shift + * + * Shifts BigInteger's by $shift bits. + * + * @param int $shift + */ + protected function lshift($shift) + { + if ($shift == 0) { + return; + } + + $num_digits = (int)($shift / static::BASE); + $shift %= static::BASE; + $shift = 1 << $shift; + + $carry = 0; + + for ($i = 0; $i < count($this->value); ++$i) { + $temp = $this->value[$i] * $shift + $carry; + $carry = static::BASE === 26 ? intval($temp / 0x4000000) : ($temp >> 31); + $this->value[$i] = (int)($temp - $carry * static::BASE_FULL); + } + + if ($carry) { + $this->value[count($this->value)] = $carry; + } + + while ($num_digits--) { + array_unshift($this->value, 0); + } + } + + /** + * Logical Right Shift + * + * Shifts BigInteger's by $shift bits. + * + * @param int $shift + */ + protected function rshift($shift) + { + if ($shift == 0) { + return; + } + + $num_digits = (int)($shift / static::BASE); + $shift %= static::BASE; + $carry_shift = static::BASE - $shift; + $carry_mask = (1 << $shift) - 1; + + if ($num_digits) { + $this->value = array_slice($this->value, $num_digits); + } + + $carry = 0; + + for ($i = count($this->value) - 1; $i >= 0; --$i) { + $temp = $this->value[$i] >> $shift | $carry; + $carry = ($this->value[$i] & $carry_mask) << $carry_shift; + $this->value[$i] = $temp; + } + + $this->value = static::trim($this->value); + } + + /** + * Performs modular exponentiation. + * + * @param PHP $e + * @param PHP $n + * @return PHP + */ + protected function powModInner(PHP $e, PHP $n) + { + try { + $class = static::$modexpEngine[static::class]; + return $class::powModHelper($this, $e, $n, static::class); + } catch (\Exception $err) { + return PHP\DefaultEngine::powModHelper($this, $e, $n, static::class); + } + } + + /** + * Performs squaring + * + * @param list $x + * @return list + */ + protected static function square(array $x) + { + return count($x) < 2 * self::KARATSUBA_CUTOFF ? + self::trim(self::baseSquare($x)) : + self::trim(self::karatsubaSquare($x)); + } + + /** + * Performs traditional squaring on two BigIntegers + * + * Squaring can be done faster than multiplying a number by itself can be. See + * {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=7 HAC 14.2.4} / + * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=141 MPM 5.3} for more information. + * + * @param array $value + * @return array + */ + protected static function baseSquare(array $value) + { + if (empty($value)) { + return []; + } + $square_value = self::array_repeat(0, 2 * count($value)); + + for ($i = 0, $max_index = count($value) - 1; $i <= $max_index; ++$i) { + $i2 = $i << 1; + + $temp = $square_value[$i2] + $value[$i] * $value[$i]; + $carry = static::BASE === 26 ? intval($temp / 0x4000000) : ($temp >> 31); + $square_value[$i2] = (int)($temp - static::BASE_FULL * $carry); + + // note how we start from $i+1 instead of 0 as we do in multiplication. + for ($j = $i + 1, $k = $i2 + 1; $j <= $max_index; ++$j, ++$k) { + $temp = $square_value[$k] + 2 * $value[$j] * $value[$i] + $carry; + $carry = static::BASE === 26 ? intval($temp / 0x4000000) : ($temp >> 31); + $square_value[$k] = (int)($temp - static::BASE_FULL * $carry); + } + + // the following line can yield values larger 2**15. at this point, PHP should switch + // over to floats. + $square_value[$i + $max_index + 1] = $carry; + } + + return $square_value; + } + + /** + * Performs Karatsuba "squaring" on two BigIntegers + * + * See {@link http://en.wikipedia.org/wiki/Karatsuba_algorithm Karatsuba algorithm} and + * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=151 MPM 5.3.4}. + * + * @param array $value + * @return array + */ + protected static function karatsubaSquare(array $value) + { + $m = count($value) >> 1; + + if ($m < self::KARATSUBA_CUTOFF) { + return self::baseSquare($value); + } + + $x1 = array_slice($value, $m); + $x0 = array_slice($value, 0, $m); + + $z2 = self::karatsubaSquare($x1); + $z0 = self::karatsubaSquare($x0); + + $z1 = self::addHelper($x1, false, $x0, false); + $z1 = self::karatsubaSquare($z1[self::VALUE]); + $temp = self::addHelper($z2, false, $z0, false); + $z1 = self::subtractHelper($z1, false, $temp[self::VALUE], false); + + $z2 = array_merge(array_fill(0, 2 * $m, 0), $z2); + $z1[self::VALUE] = array_merge(array_fill(0, $m, 0), $z1[self::VALUE]); + + $xx = self::addHelper($z2, false, $z1[self::VALUE], $z1[self::SIGN]); + $xx = self::addHelper($xx[self::VALUE], $xx[self::SIGN], $z0, false); + + return $xx[self::VALUE]; + } + + /** + * Make the current number odd + * + * If the current number is odd it'll be unchanged. If it's even, one will be added to it. + * + * @see self::randomPrime() + */ + protected function make_odd() + { + $this->value[0] |= 1; + } + + /** + * Test the number against small primes. + * + * @see self::isPrime() + */ + protected function testSmallPrimes() + { + if ($this->value == [1]) { + return false; + } + if ($this->value == [2]) { + return true; + } + if (~$this->value[0] & 1) { + return false; + } + + $value = $this->value; + foreach (static::PRIMES as $prime) { + list(, $r) = self::divide_digit($value, $prime); + if (!$r) { + return count($value) == 1 && $value[0] == $prime; + } + } + + return true; + } + + /** + * Scan for 1 and right shift by that amount + * + * ie. $s = gmp_scan1($n, 0) and $r = gmp_div_q($n, gmp_pow(gmp_init('2'), $s)); + * + * @param PHP $r + * @return int + * @see self::isPrime() + */ + public static function scan1divide(PHP $r) + { + $r_value = &$r->value; + for ($i = 0, $r_length = count($r_value); $i < $r_length; ++$i) { + $temp = ~$r_value[$i] & static::MAX_DIGIT; + for ($j = 1; ($temp >> $j) & 1; ++$j) { + } + if ($j <= static::BASE) { + break; + } + } + $s = static::BASE * $i + $j; + $r->rshift($s); + return $s; + } + + /** + * Performs exponentiation. + * + * @param PHP $n + * @return PHP + */ + protected function powHelper(PHP $n) + { + if ($n->compare(static::$zero[static::class]) == 0) { + return new static(1); + } // n^0 = 1 + + $temp = clone $this; + while (!$n->equals(static::$one[static::class])) { + $temp = $temp->multiply($this); + $n = $n->subtract(static::$one[static::class]); + } + + return $temp; + } + + /** + * Is Odd? + * + * @return bool + */ + public function isOdd() + { + return (bool)($this->value[0] & 1); + } + + /** + * Tests if a bit is set + * + * @return bool + */ + public function testBit($x) + { + $digit = (int) floor($x / static::BASE); + $bit = $x % static::BASE; + + if (!isset($this->value[$digit])) { + return false; + } + + return (bool)($this->value[$digit] & (1 << $bit)); + } + + /** + * Is Negative? + * + * @return bool + */ + public function isNegative() + { + return $this->is_negative; + } + + /** + * Negate + * + * Given $k, returns -$k + * + * @return static + */ + public function negate() + { + $temp = clone $this; + $temp->is_negative = !$temp->is_negative; + + return $temp; + } + + /** + * Bitwise Split + * + * Splits BigInteger's into chunks of $split bits + * + * @param int $split + * @return list + */ + public function bitwise_split($split) + { + if ($split < 1) { + throw new \RuntimeException('Offset must be greater than 1'); + } + + $width = (int)($split / static::BASE); + if (!$width) { + $arr = $this->bitwise_small_split($split); + return array_map(function ($digit) { + $temp = new static(); + $temp->value = $digit != 0 ? [$digit] : []; + return $temp; + }, $arr); + } + + $vals = []; + $val = $this->value; + + $i = $overflow = 0; + $len = count($val); + while ($i < $len) { + $digit = []; + if (!$overflow) { + $digit = array_slice($val, $i, $width); + $i += $width; + $overflow = $split % static::BASE; + if ($overflow) { + $mask = (1 << $overflow) - 1; + $temp = isset($val[$i]) ? $val[$i] : 0; + $digit[] = $temp & $mask; + } + } else { + $remaining = static::BASE - $overflow; + $tempsplit = $split - $remaining; + $tempwidth = (int)($tempsplit / static::BASE + 1); + $digit = array_slice($val, $i, $tempwidth); + $i += $tempwidth; + $tempoverflow = $tempsplit % static::BASE; + if ($tempoverflow) { + $tempmask = (1 << $tempoverflow) - 1; + $temp = isset($val[$i]) ? $val[$i] : 0; + $digit[] = $temp & $tempmask; + } + $newbits = 0; + for ($j = count($digit) - 1; $j >= 0; $j--) { + $temp = $digit[$j] & $mask; + $digit[$j] = ($digit[$j] >> $overflow) | ($newbits << $remaining); + $newbits = $temp; + } + $overflow = $tempoverflow; + $mask = $tempmask; + } + $temp = new static(); + $temp->value = static::trim($digit); + $vals[] = $temp; + } + + return array_reverse($vals); + } + + /** + * Bitwise Split where $split < static::BASE + * + * @param int $split + * @return list + */ + private function bitwise_small_split($split) + { + $vals = []; + $val = $this->value; + + $mask = (1 << $split) - 1; + + $i = $overflow = 0; + $len = count($val); + $val[] = 0; + $remaining = static::BASE; + while ($i != $len) { + $digit = $val[$i] & $mask; + $val[$i] >>= $split; + if (!$overflow) { + $remaining -= $split; + $overflow = $split <= $remaining ? 0 : $split - $remaining; + + if (!$remaining) { + $i++; + $remaining = static::BASE; + $overflow = 0; + } + } elseif (++$i != $len) { + $tempmask = (1 << $overflow) - 1; + $digit |= ($val[$i] & $tempmask) << $remaining; + $val[$i] >>= $overflow; + $remaining = static::BASE - $overflow; + $overflow = $split <= $remaining ? 0 : $split - $remaining; + } + + $vals[] = $digit; + } + + while ($vals[count($vals) - 1] == 0) { + unset($vals[count($vals) - 1]); + } + + return array_reverse($vals); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Base.php b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Base.php new file mode 100644 index 00000000..40f64bd1 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Base.php @@ -0,0 +1,143 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Math\BigInteger\Engines\PHP; + +use phpseclib3\Math\BigInteger\Engines\PHP; + +/** + * PHP Modular Exponentiation Engine + * + * @author Jim Wigginton + */ +abstract class Base extends PHP +{ + /** + * Cache constants + * + * $cache[self::VARIABLE] tells us whether or not the cached data is still valid. + * + */ + const VARIABLE = 0; + /** + * $cache[self::DATA] contains the cached data. + * + */ + const DATA = 1; + + /** + * Test for engine validity + * + * @return bool + */ + public static function isValidEngine() + { + return static::class != __CLASS__; + } + + /** + * Performs modular exponentiation. + * + * The most naive approach to modular exponentiation has very unreasonable requirements, and + * and although the approach involving repeated squaring does vastly better, it, too, is impractical + * for our purposes. The reason being that division - by far the most complicated and time-consuming + * of the basic operations (eg. +,-,*,/) - occurs multiple times within it. + * + * Modular reductions resolve this issue. Although an individual modular reduction takes more time + * then an individual division, when performed in succession (with the same modulo), they're a lot faster. + * + * The two most commonly used modular reductions are Barrett and Montgomery reduction. Montgomery reduction, + * although faster, only works when the gcd of the modulo and of the base being used is 1. In RSA, when the + * base is a power of two, the modulo - a product of two primes - is always going to have a gcd of 1 (because + * the product of two odd numbers is odd), but what about when RSA isn't used? + * + * In contrast, Barrett reduction has no such constraint. As such, some bigint implementations perform a + * Barrett reduction after every operation in the modpow function. Others perform Barrett reductions when the + * modulo is even and Montgomery reductions when the modulo is odd. BigInteger.java's modPow method, however, + * uses a trick involving the Chinese Remainder Theorem to factor the even modulo into two numbers - one odd and + * the other, a power of two - and recombine them, later. This is the method that this modPow function uses. + * {@link http://islab.oregonstate.edu/papers/j34monex.pdf Montgomery Reduction with Even Modulus} elaborates. + * + * @param PHP $x + * @param PHP $e + * @param PHP $n + * @param string $class + * @return PHP + */ + protected static function powModHelper(PHP $x, PHP $e, PHP $n, $class) + { + if (empty($e->value)) { + $temp = new $class(); + $temp->value = [1]; + return $x->normalize($temp); + } + + if ($e->value == [1]) { + list(, $temp) = $x->divide($n); + return $x->normalize($temp); + } + + if ($e->value == [2]) { + $temp = new $class(); + $temp->value = $class::square($x->value); + list(, $temp) = $temp->divide($n); + return $x->normalize($temp); + } + + return $x->normalize(static::slidingWindow($x, $e, $n, $class)); + } + + /** + * Modular reduction preparation + * + * @param array $x + * @param array $n + * @param string $class + * @see self::slidingWindow() + * @return array + */ + protected static function prepareReduce(array $x, array $n, $class) + { + return static::reduce($x, $n, $class); + } + + /** + * Modular multiply + * + * @param array $x + * @param array $y + * @param array $n + * @param string $class + * @see self::slidingWindow() + * @return array + */ + protected static function multiplyReduce(array $x, array $y, array $n, $class) + { + $temp = $class::multiplyHelper($x, false, $y, false); + return static::reduce($temp[self::VALUE], $n, $class); + } + + /** + * Modular square + * + * @param array $x + * @param array $n + * @param string $class + * @see self::slidingWindow() + * @return array + */ + protected static function squareReduce(array $x, array $n, $class) + { + return static::reduce($class::square($x), $n, $class); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/DefaultEngine.php b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/DefaultEngine.php new file mode 100644 index 00000000..6d33532e --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/DefaultEngine.php @@ -0,0 +1,25 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Math\BigInteger\Engines\PHP; + +use phpseclib3\Math\BigInteger\Engines\PHP\Reductions\EvalBarrett; + +/** + * PHP Default Modular Exponentiation Engine + * + * @author Jim Wigginton + */ +abstract class DefaultEngine extends EvalBarrett +{ +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Montgomery.php b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Montgomery.php new file mode 100644 index 00000000..09f825f9 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Montgomery.php @@ -0,0 +1,89 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Math\BigInteger\Engines\PHP; + +use phpseclib3\Math\BigInteger\Engines\Engine; +use phpseclib3\Math\BigInteger\Engines\PHP; +use phpseclib3\Math\BigInteger\Engines\PHP\Reductions\PowerOfTwo; + +/** + * PHP Montgomery Modular Exponentiation Engine + * + * @author Jim Wigginton + */ +abstract class Montgomery extends Base +{ + /** + * Test for engine validity + * + * @return bool + */ + public static function isValidEngine() + { + return static::class != __CLASS__; + } + + /** + * Performs modular exponentiation. + * + * @template T of Engine + * @param Engine $x + * @param Engine $e + * @param Engine $n + * @param class-string $class + * @return T + */ + protected static function slidingWindow(Engine $x, Engine $e, Engine $n, $class) + { + // is the modulo odd? + if ($n->value[0] & 1) { + return parent::slidingWindow($x, $e, $n, $class); + } + // if it's not, it's even + + // find the lowest set bit (eg. the max pow of 2 that divides $n) + for ($i = 0; $i < count($n->value); ++$i) { + if ($n->value[$i]) { + $temp = decbin($n->value[$i]); + $j = strlen($temp) - strrpos($temp, '1') - 1; + $j += $class::BASE * $i; + break; + } + } + // at this point, 2^$j * $n/(2^$j) == $n + + $mod1 = clone $n; + $mod1->rshift($j); + $mod2 = new $class(); + $mod2->value = [1]; + $mod2->lshift($j); + + $part1 = $mod1->value != [1] ? parent::slidingWindow($x, $e, $mod1, $class) : new $class(); + $part2 = PowerOfTwo::slidingWindow($x, $e, $mod2, $class); + + $y1 = $mod2->modInverse($mod1); + $y2 = $mod1->modInverse($mod2); + + $result = $part1->multiply($mod2); + $result = $result->multiply($y1); + + $temp = $part2->multiply($mod1); + $temp = $temp->multiply($y2); + + $result = $result->add($temp); + list(, $result) = $result->divide($n); + + return $result; + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/OpenSSL.php b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/OpenSSL.php new file mode 100644 index 00000000..eddd25e2 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/OpenSSL.php @@ -0,0 +1,25 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Math\BigInteger\Engines\PHP; + +use phpseclib3\Math\BigInteger\Engines\OpenSSL as Progenitor; + +/** + * OpenSSL Modular Exponentiation Engine + * + * @author Jim Wigginton + */ +abstract class OpenSSL extends Progenitor +{ +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Reductions/Barrett.php b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Reductions/Barrett.php new file mode 100644 index 00000000..3518d76f --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Reductions/Barrett.php @@ -0,0 +1,281 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Math\BigInteger\Engines\PHP\Reductions; + +use phpseclib3\Math\BigInteger\Engines\PHP; +use phpseclib3\Math\BigInteger\Engines\PHP\Base; + +/** + * PHP Barrett Modular Exponentiation Engine + * + * @author Jim Wigginton + */ +abstract class Barrett extends Base +{ + /** + * Barrett Modular Reduction + * + * See {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=14 HAC 14.3.3} / + * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=165 MPM 6.2.5} for more information. Modified slightly, + * so as not to require negative numbers (initially, this script didn't support negative numbers). + * + * Employs "folding", as described at + * {@link http://www.cosic.esat.kuleuven.be/publications/thesis-149.pdf#page=66 thesis-149.pdf#page=66}. To quote from + * it, "the idea [behind folding] is to find a value x' such that x (mod m) = x' (mod m), with x' being smaller than x." + * + * Unfortunately, the "Barrett Reduction with Folding" algorithm described in thesis-149.pdf is not, as written, all that + * usable on account of (1) its not using reasonable radix points as discussed in + * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=162 MPM 6.2.2} and (2) the fact that, even with reasonable + * radix points, it only works when there are an even number of digits in the denominator. The reason for (2) is that + * (x >> 1) + (x >> 1) != x / 2 + x / 2. If x is even, they're the same, but if x is odd, they're not. See the in-line + * comments for details. + * + * @param array $n + * @param array $m + * @param class-string $class + * @return array + */ + protected static function reduce(array $n, array $m, $class) + { + static $cache = [ + self::VARIABLE => [], + self::DATA => [] + ]; + + $m_length = count($m); + + // if (self::compareHelper($n, $static::square($m)) >= 0) { + if (count($n) > 2 * $m_length) { + $lhs = new $class(); + $rhs = new $class(); + $lhs->value = $n; + $rhs->value = $m; + list(, $temp) = $lhs->divide($rhs); + return $temp->value; + } + + // if (m.length >> 1) + 2 <= m.length then m is too small and n can't be reduced + if ($m_length < 5) { + return self::regularBarrett($n, $m, $class); + } + // n = 2 * m.length + + if (($key = array_search($m, $cache[self::VARIABLE])) === false) { + $key = count($cache[self::VARIABLE]); + $cache[self::VARIABLE][] = $m; + + $lhs = new $class(); + $lhs_value = &$lhs->value; + $lhs_value = self::array_repeat(0, $m_length + ($m_length >> 1)); + $lhs_value[] = 1; + $rhs = new $class(); + $rhs->value = $m; + + list($u, $m1) = $lhs->divide($rhs); + $u = $u->value; + $m1 = $m1->value; + + $cache[self::DATA][] = [ + 'u' => $u, // m.length >> 1 (technically (m.length >> 1) + 1) + 'm1' => $m1 // m.length + ]; + } else { + extract($cache[self::DATA][$key]); + } + + $cutoff = $m_length + ($m_length >> 1); + $lsd = array_slice($n, 0, $cutoff); // m.length + (m.length >> 1) + $msd = array_slice($n, $cutoff); // m.length >> 1 + + $lsd = self::trim($lsd); + $temp = $class::multiplyHelper($msd, false, $m1, false); // m.length + (m.length >> 1) + $n = $class::addHelper($lsd, false, $temp[self::VALUE], false); // m.length + (m.length >> 1) + 1 (so basically we're adding two same length numbers) + //if ($m_length & 1) { + // return self::regularBarrett($n[self::VALUE], $m, $class); + //} + + // (m.length + (m.length >> 1) + 1) - (m.length - 1) == (m.length >> 1) + 2 + $temp = array_slice($n[self::VALUE], $m_length - 1); + // if even: ((m.length >> 1) + 2) + (m.length >> 1) == m.length + 2 + // if odd: ((m.length >> 1) + 2) + (m.length >> 1) == (m.length - 1) + 2 == m.length + 1 + $temp = $class::multiplyHelper($temp, false, $u, false); + // if even: (m.length + 2) - ((m.length >> 1) + 1) = m.length - (m.length >> 1) + 1 + // if odd: (m.length + 1) - ((m.length >> 1) + 1) = m.length - (m.length >> 1) + $temp = array_slice($temp[self::VALUE], ($m_length >> 1) + 1); + // if even: (m.length - (m.length >> 1) + 1) + m.length = 2 * m.length - (m.length >> 1) + 1 + // if odd: (m.length - (m.length >> 1)) + m.length = 2 * m.length - (m.length >> 1) + $temp = $class::multiplyHelper($temp, false, $m, false); + + // at this point, if m had an odd number of digits, we'd be subtracting a 2 * m.length - (m.length >> 1) digit + // number from a m.length + (m.length >> 1) + 1 digit number. ie. there'd be an extra digit and the while loop + // following this comment would loop a lot (hence our calling _regularBarrett() in that situation). + + $result = $class::subtractHelper($n[self::VALUE], false, $temp[self::VALUE], false); + + while (self::compareHelper($result[self::VALUE], $result[self::SIGN], $m, false) >= 0) { + $result = $class::subtractHelper($result[self::VALUE], $result[self::SIGN], $m, false); + } + + return $result[self::VALUE]; + } + + /** + * (Regular) Barrett Modular Reduction + * + * For numbers with more than four digits BigInteger::_barrett() is faster. The difference between that and this + * is that this function does not fold the denominator into a smaller form. + * + * @param array $x + * @param array $n + * @param string $class + * @return array + */ + private static function regularBarrett(array $x, array $n, $class) + { + static $cache = [ + self::VARIABLE => [], + self::DATA => [] + ]; + + $n_length = count($n); + + if (count($x) > 2 * $n_length) { + $lhs = new $class(); + $rhs = new $class(); + $lhs->value = $x; + $rhs->value = $n; + list(, $temp) = $lhs->divide($rhs); + return $temp->value; + } + + if (($key = array_search($n, $cache[self::VARIABLE])) === false) { + $key = count($cache[self::VARIABLE]); + $cache[self::VARIABLE][] = $n; + $lhs = new $class(); + $lhs_value = &$lhs->value; + $lhs_value = self::array_repeat(0, 2 * $n_length); + $lhs_value[] = 1; + $rhs = new $class(); + $rhs->value = $n; + list($temp, ) = $lhs->divide($rhs); // m.length + $cache[self::DATA][] = $temp->value; + } + + // 2 * m.length - (m.length - 1) = m.length + 1 + $temp = array_slice($x, $n_length - 1); + // (m.length + 1) + m.length = 2 * m.length + 1 + $temp = $class::multiplyHelper($temp, false, $cache[self::DATA][$key], false); + // (2 * m.length + 1) - (m.length - 1) = m.length + 2 + $temp = array_slice($temp[self::VALUE], $n_length + 1); + + // m.length + 1 + $result = array_slice($x, 0, $n_length + 1); + // m.length + 1 + $temp = self::multiplyLower($temp, false, $n, false, $n_length + 1, $class); + // $temp == array_slice($class::regularMultiply($temp, false, $n, false)->value, 0, $n_length + 1) + + if (self::compareHelper($result, false, $temp[self::VALUE], $temp[self::SIGN]) < 0) { + $corrector_value = self::array_repeat(0, $n_length + 1); + $corrector_value[count($corrector_value)] = 1; + $result = $class::addHelper($result, false, $corrector_value, false); + $result = $result[self::VALUE]; + } + + // at this point, we're subtracting a number with m.length + 1 digits from another number with m.length + 1 digits + $result = $class::subtractHelper($result, false, $temp[self::VALUE], $temp[self::SIGN]); + while (self::compareHelper($result[self::VALUE], $result[self::SIGN], $n, false) > 0) { + $result = $class::subtractHelper($result[self::VALUE], $result[self::SIGN], $n, false); + } + + return $result[self::VALUE]; + } + + /** + * Performs long multiplication up to $stop digits + * + * If you're going to be doing array_slice($product->value, 0, $stop), some cycles can be saved. + * + * @see self::regularBarrett() + * @param array $x_value + * @param bool $x_negative + * @param array $y_value + * @param bool $y_negative + * @param int $stop + * @param string $class + * @return array + */ + private static function multiplyLower(array $x_value, $x_negative, array $y_value, $y_negative, $stop, $class) + { + $x_length = count($x_value); + $y_length = count($y_value); + + if (!$x_length || !$y_length) { // a 0 is being multiplied + return [ + self::VALUE => [], + self::SIGN => false + ]; + } + + if ($x_length < $y_length) { + $temp = $x_value; + $x_value = $y_value; + $y_value = $temp; + + $x_length = count($x_value); + $y_length = count($y_value); + } + + $product_value = self::array_repeat(0, $x_length + $y_length); + + // the following for loop could be removed if the for loop following it + // (the one with nested for loops) initially set $i to 0, but + // doing so would also make the result in one set of unnecessary adds, + // since on the outermost loops first pass, $product->value[$k] is going + // to always be 0 + + $carry = 0; + + for ($j = 0; $j < $x_length; ++$j) { // ie. $i = 0, $k = $i + $temp = $x_value[$j] * $y_value[0] + $carry; // $product_value[$k] == 0 + $carry = $class::BASE === 26 ? intval($temp / 0x4000000) : ($temp >> 31); + $product_value[$j] = (int) ($temp - $class::BASE_FULL * $carry); + } + + if ($j < $stop) { + $product_value[$j] = $carry; + } + + // the above for loop is what the previous comment was talking about. the + // following for loop is the "one with nested for loops" + + for ($i = 1; $i < $y_length; ++$i) { + $carry = 0; + + for ($j = 0, $k = $i; $j < $x_length && $k < $stop; ++$j, ++$k) { + $temp = $product_value[$k] + $x_value[$j] * $y_value[$i] + $carry; + $carry = $class::BASE === 26 ? intval($temp / 0x4000000) : ($temp >> 31); + $product_value[$k] = (int) ($temp - $class::BASE_FULL * $carry); + } + + if ($k < $stop) { + $product_value[$k] = $carry; + } + } + + return [ + self::VALUE => self::trim($product_value), + self::SIGN => $x_negative != $y_negative + ]; + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Reductions/Classic.php b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Reductions/Classic.php new file mode 100644 index 00000000..54f3b863 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Reductions/Classic.php @@ -0,0 +1,42 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Math\BigInteger\Engines\PHP\Reductions; + +use phpseclib3\Math\BigInteger\Engines\PHP\Base; + +/** + * PHP Classic Modular Exponentiation Engine + * + * @author Jim Wigginton + */ +abstract class Classic extends Base +{ + /** + * Regular Division + * + * @param array $x + * @param array $n + * @param string $class + * @return array + */ + protected static function reduce(array $x, array $n, $class) + { + $lhs = new $class(); + $lhs->value = $x; + $rhs = new $class(); + $rhs->value = $n; + list(, $temp) = $lhs->divide($rhs); + return $temp->value; + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Reductions/EvalBarrett.php b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Reductions/EvalBarrett.php new file mode 100644 index 00000000..2f2c54b5 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Reductions/EvalBarrett.php @@ -0,0 +1,484 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Math\BigInteger\Engines\PHP\Reductions; + +use phpseclib3\Math\BigInteger\Engines\PHP; +use phpseclib3\Math\BigInteger\Engines\PHP\Base; + +/** + * PHP Dynamic Barrett Modular Exponentiation Engine + * + * @author Jim Wigginton + */ +abstract class EvalBarrett extends Base +{ + /** + * Custom Reduction Function + * + * @see self::generateCustomReduction + */ + private static $custom_reduction; + + /** + * Barrett Modular Reduction + * + * This calls a dynamically generated loop unrolled function that's specific to a given modulo. + * Array lookups are avoided as are if statements testing for how many bits the host OS supports, etc. + * + * @param array $n + * @param array $m + * @param string $class + * @return array + */ + protected static function reduce(array $n, array $m, $class) + { + $inline = self::$custom_reduction; + return $inline($n); + } + + /** + * Generate Custom Reduction + * + * @param PHP $m + * @param string $class + * @return callable + */ + protected static function generateCustomReduction(PHP $m, $class) + { + $m_length = count($m->value); + + if ($m_length < 5) { + $code = ' + $lhs = new ' . $class . '(); + $lhs->value = $x; + $rhs = new ' . $class . '(); + $rhs->value = [' . + implode(',', array_map(self::class . '::float2string', $m->value)) . ']; + list(, $temp) = $lhs->divide($rhs); + return $temp->value; + '; + eval('$func = function ($x) { ' . $code . '};'); + self::$custom_reduction = $func; + //self::$custom_reduction = \Closure::bind($func, $m, $class); + return $func; + } + + $lhs = new $class(); + $lhs_value = &$lhs->value; + + $lhs_value = self::array_repeat(0, $m_length + ($m_length >> 1)); + $lhs_value[] = 1; + $rhs = new $class(); + + list($u, $m1) = $lhs->divide($m); + + if ($class::BASE != 26) { + $u = $u->value; + } else { + $lhs_value = self::array_repeat(0, 2 * $m_length); + $lhs_value[] = 1; + $rhs = new $class(); + + list($u) = $lhs->divide($m); + $u = $u->value; + } + + $m = $m->value; + $m1 = $m1->value; + + $cutoff = count($m) + (count($m) >> 1); + + $code = ' + if (count($n) > ' . (2 * count($m)) . ') { + $lhs = new ' . $class . '(); + $rhs = new ' . $class . '(); + $lhs->value = $n; + $rhs->value = [' . + implode(',', array_map('self::float2string', $m)) . ']; + list(, $temp) = $lhs->divide($rhs); + return $temp->value; + } + + $lsd = array_slice($n, 0, ' . $cutoff . '); + $msd = array_slice($n, ' . $cutoff . ');'; + + $code .= self::generateInlineTrim('msd'); + $code .= self::generateInlineMultiply('msd', $m1, 'temp', $class); + $code .= self::generateInlineAdd('lsd', 'temp', 'n', $class); + + $code .= '$temp = array_slice($n, ' . (count($m) - 1) . ');'; + $code .= self::generateInlineMultiply('temp', $u, 'temp2', $class); + $code .= self::generateInlineTrim('temp2'); + + $code .= $class::BASE == 26 ? + '$temp = array_slice($temp2, ' . (count($m) + 1) . ');' : + '$temp = array_slice($temp2, ' . ((count($m) >> 1) + 1) . ');'; + $code .= self::generateInlineMultiply('temp', $m, 'temp2', $class); + $code .= self::generateInlineTrim('temp2'); + + /* + if ($class::BASE == 26) { + $code.= '$n = array_slice($n, 0, ' . (count($m) + 1) . '); + $temp2 = array_slice($temp2, 0, ' . (count($m) + 1) . ');'; + } + */ + + $code .= self::generateInlineSubtract2('n', 'temp2', 'temp', $class); + + $subcode = self::generateInlineSubtract1('temp', $m, 'temp2', $class); + $subcode .= '$temp = $temp2;'; + + $code .= self::generateInlineCompare($m, 'temp', $subcode); + + $code .= 'return $temp;'; + + eval('$func = function ($n) { ' . $code . '};'); + + self::$custom_reduction = $func; + + return $func; + + //self::$custom_reduction = \Closure::bind($func, $m, $class); + } + + /** + * Inline Trim + * + * Removes leading zeros + * + * @param string $name + * @return string + */ + private static function generateInlineTrim($name) + { + return ' + for ($i = count($' . $name . ') - 1; $i >= 0; --$i) { + if ($' . $name . '[$i]) { + break; + } + unset($' . $name . '[$i]); + }'; + } + + /** + * Inline Multiply (unknown, known) + * + * @param string $input + * @param array $arr + * @param string $output + * @param string $class + * @return string + */ + private static function generateInlineMultiply($input, array $arr, $output, $class) + { + if (!count($arr)) { + return 'return [];'; + } + + $regular = ' + $length = count($' . $input . '); + if (!$length) { + $' . $output . ' = []; + }else{ + $' . $output . ' = array_fill(0, $length + ' . count($arr) . ', 0); + $carry = 0;'; + + for ($i = 0; $i < count($arr); $i++) { + $regular .= ' + $subtemp = $' . $input . '[0] * ' . $arr[$i]; + $regular .= $i ? ' + $carry;' : ';'; + + $regular .= '$carry = '; + $regular .= $class::BASE === 26 ? + 'intval($subtemp / 0x4000000);' : + '$subtemp >> 31;'; + $regular .= + '$' . $output . '[' . $i . '] = '; + if ($class::BASE === 26) { + $regular .= '(int) ('; + } + $regular .= '$subtemp - ' . $class::BASE_FULL . ' * $carry'; + $regular .= $class::BASE === 26 ? ');' : ';'; + } + + $regular .= '$' . $output . '[' . count($arr) . '] = $carry;'; + + $regular .= ' + for ($i = 1; $i < $length; ++$i) {'; + + for ($j = 0; $j < count($arr); $j++) { + $regular .= $j ? '$k++;' : '$k = $i;'; + $regular .= ' + $subtemp = $' . $output . '[$k] + $' . $input . '[$i] * ' . $arr[$j]; + $regular .= $j ? ' + $carry;' : ';'; + + $regular .= '$carry = '; + $regular .= $class::BASE === 26 ? + 'intval($subtemp / 0x4000000);' : + '$subtemp >> 31;'; + $regular .= + '$' . $output . '[$k] = '; + if ($class::BASE === 26) { + $regular .= '(int) ('; + } + $regular .= '$subtemp - ' . $class::BASE_FULL . ' * $carry'; + $regular .= $class::BASE === 26 ? ');' : ';'; + } + + $regular .= '$' . $output . '[++$k] = $carry; $carry = 0;'; + + $regular .= '}}'; + + //if (count($arr) < 2 * self::KARATSUBA_CUTOFF) { + //} + + return $regular; + } + + /** + * Inline Addition + * + * @param string $x + * @param string $y + * @param string $result + * @param string $class + * @return string + */ + private static function generateInlineAdd($x, $y, $result, $class) + { + $code = ' + $length = max(count($' . $x . '), count($' . $y . ')); + $' . $result . ' = array_pad($' . $x . ', $length + 1, 0); + $_' . $y . ' = array_pad($' . $y . ', $length, 0); + $carry = 0; + for ($i = 0, $j = 1; $j < $length; $i+=2, $j+=2) { + $sum = ($' . $result . '[$j] + $_' . $y . '[$j]) * ' . $class::BASE_FULL . ' + + $' . $result . '[$i] + $_' . $y . '[$i] + + $carry; + $carry = $sum >= ' . self::float2string($class::MAX_DIGIT2) . '; + $sum = $carry ? $sum - ' . self::float2string($class::MAX_DIGIT2) . ' : $sum;'; + + $code .= $class::BASE === 26 ? + '$upper = intval($sum / 0x4000000); $' . $result . '[$i] = (int) ($sum - ' . $class::BASE_FULL . ' * $upper);' : + '$upper = $sum >> 31; $' . $result . '[$i] = $sum - ' . $class::BASE_FULL . ' * $upper;'; + $code .= ' + $' . $result . '[$j] = $upper; + } + if ($j == $length) { + $sum = $' . $result . '[$i] + $_' . $y . '[$i] + $carry; + $carry = $sum >= ' . self::float2string($class::BASE_FULL) . '; + $' . $result . '[$i] = $carry ? $sum - ' . self::float2string($class::BASE_FULL) . ' : $sum; + ++$i; + } + if ($carry) { + for (; $' . $result . '[$i] == ' . $class::MAX_DIGIT . '; ++$i) { + $' . $result . '[$i] = 0; + } + ++$' . $result . '[$i]; + }'; + $code .= self::generateInlineTrim($result); + + return $code; + } + + /** + * Inline Subtraction 2 + * + * For when $known is more digits than $unknown. This is the harder use case to optimize for. + * + * @param string $known + * @param string $unknown + * @param string $result + * @param string $class + * @return string + */ + private static function generateInlineSubtract2($known, $unknown, $result, $class) + { + $code = ' + $' . $result . ' = $' . $known . '; + $carry = 0; + $size = count($' . $unknown . '); + for ($i = 0, $j = 1; $j < $size; $i+= 2, $j+= 2) { + $sum = ($' . $known . '[$j] - $' . $unknown . '[$j]) * ' . $class::BASE_FULL . ' + $' . $known . '[$i] + - $' . $unknown . '[$i] + - $carry; + $carry = $sum < 0; + if ($carry) { + $sum+= ' . self::float2string($class::MAX_DIGIT2) . '; + } + $subtemp = '; + $code .= $class::BASE === 26 ? + 'intval($sum / 0x4000000);' : + '$sum >> 31;'; + $code .= '$' . $result . '[$i] = '; + if ($class::BASE === 26) { + $code .= '(int) ('; + } + $code .= '$sum - ' . $class::BASE_FULL . ' * $subtemp'; + if ($class::BASE === 26) { + $code .= ')'; + } + $code .= '; + $' . $result . '[$j] = $subtemp; + } + if ($j == $size) { + $sum = $' . $known . '[$i] - $' . $unknown . '[$i] - $carry; + $carry = $sum < 0; + $' . $result . '[$i] = $carry ? $sum + ' . $class::BASE_FULL . ' : $sum; + ++$i; + } + + if ($carry) { + for (; !$' . $result . '[$i]; ++$i) { + $' . $result . '[$i] = ' . $class::MAX_DIGIT . '; + } + --$' . $result . '[$i]; + }'; + + $code .= self::generateInlineTrim($result); + + return $code; + } + + /** + * Inline Subtraction 1 + * + * For when $unknown is more digits than $known. This is the easier use case to optimize for. + * + * @param string $unknown + * @param array $known + * @param string $result + * @param string $class + * @return string + */ + private static function generateInlineSubtract1($unknown, array $known, $result, $class) + { + $code = '$' . $result . ' = $' . $unknown . ';'; + for ($i = 0, $j = 1; $j < count($known); $i += 2, $j += 2) { + $code .= '$sum = $' . $unknown . '[' . $j . '] * ' . $class::BASE_FULL . ' + $' . $unknown . '[' . $i . '] - '; + $code .= self::float2string($known[$j] * $class::BASE_FULL + $known[$i]); + if ($i != 0) { + $code .= ' - $carry'; + } + + $code .= '; + if ($carry = $sum < 0) { + $sum+= ' . self::float2string($class::MAX_DIGIT2) . '; + } + $subtemp = '; + $code .= $class::BASE === 26 ? + 'intval($sum / 0x4000000);' : + '$sum >> 31;'; + $code .= ' + $' . $result . '[' . $i . '] = '; + if ($class::BASE === 26) { + $code .= ' (int) ('; + } + $code .= '$sum - ' . $class::BASE_FULL . ' * $subtemp'; + if ($class::BASE === 26) { + $code .= ')'; + } + $code .= '; + $' . $result . '[' . $j . '] = $subtemp;'; + } + + $code .= '$i = ' . $i . ';'; + + if ($j == count($known)) { + $code .= ' + $sum = $' . $unknown . '[' . $i . '] - ' . $known[$i] . ' - $carry; + $carry = $sum < 0; + $' . $result . '[' . $i . '] = $carry ? $sum + ' . $class::BASE_FULL . ' : $sum; + ++$i;'; + } + + $code .= ' + if ($carry) { + for (; !$' . $result . '[$i]; ++$i) { + $' . $result . '[$i] = ' . $class::MAX_DIGIT . '; + } + --$' . $result . '[$i]; + }'; + $code .= self::generateInlineTrim($result); + + return $code; + } + + /** + * Inline Comparison + * + * If $unknown >= $known then loop + * + * @param array $known + * @param string $unknown + * @param string $subcode + * @return string + */ + private static function generateInlineCompare(array $known, $unknown, $subcode) + { + $uniqid = uniqid(); + $code = 'loop_' . $uniqid . ': + $clength = count($' . $unknown . '); + switch (true) { + case $clength < ' . count($known) . ': + goto end_' . $uniqid . '; + case $clength > ' . count($known) . ':'; + for ($i = count($known) - 1; $i >= 0; $i--) { + $code .= ' + case $' . $unknown . '[' . $i . '] > ' . $known[$i] . ': + goto subcode_' . $uniqid . '; + case $' . $unknown . '[' . $i . '] < ' . $known[$i] . ': + goto end_' . $uniqid . ';'; + } + $code .= ' + default: + // do subcode + } + + subcode_' . $uniqid . ':' . $subcode . ' + goto loop_' . $uniqid . '; + + end_' . $uniqid . ':'; + + return $code; + } + + /** + * Convert a float to a string + * + * If you do echo floatval(pow(2, 52)) you'll get 4.6116860184274E+18. It /can/ be displayed without a loss of + * precision but displayed in this way there will be precision loss, hence the need for this method. + * + * @param int|float $num + * @return string + */ + private static function float2string($num) + { + if (!is_float($num)) { + return (string) $num; + } + + if ($num < 0) { + return '-' . self::float2string(abs($num)); + } + + $temp = ''; + while ($num) { + $temp = fmod($num, 10) . $temp; + $num = floor($num / 10); + } + + return $temp; + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Reductions/Montgomery.php b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Reductions/Montgomery.php new file mode 100644 index 00000000..a34035e7 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Reductions/Montgomery.php @@ -0,0 +1,126 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Math\BigInteger\Engines\PHP\Reductions; + +use phpseclib3\Math\BigInteger\Engines\PHP\Montgomery as Progenitor; + +/** + * PHP Montgomery Modular Exponentiation Engine + * + * @author Jim Wigginton + */ +abstract class Montgomery extends Progenitor +{ + /** + * Prepare a number for use in Montgomery Modular Reductions + * + * @param array $x + * @param array $n + * @param string $class + * @return array + */ + protected static function prepareReduce(array $x, array $n, $class) + { + $lhs = new $class(); + $lhs->value = array_merge(self::array_repeat(0, count($n)), $x); + $rhs = new $class(); + $rhs->value = $n; + + list(, $temp) = $lhs->divide($rhs); + return $temp->value; + } + + /** + * Montgomery Multiply + * + * Interleaves the montgomery reduction and long multiplication algorithms together as described in + * {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=13 HAC 14.36} + * + * @param array $x + * @param array $n + * @param string $class + * @return array + */ + protected static function reduce(array $x, array $n, $class) + { + static $cache = [ + self::VARIABLE => [], + self::DATA => [] + ]; + + if (($key = array_search($n, $cache[self::VARIABLE])) === false) { + $key = count($cache[self::VARIABLE]); + $cache[self::VARIABLE][] = $x; + $cache[self::DATA][] = self::modInverse67108864($n, $class); + } + + $k = count($n); + + $result = [self::VALUE => $x]; + + for ($i = 0; $i < $k; ++$i) { + $temp = $result[self::VALUE][$i] * $cache[self::DATA][$key]; + $temp = $temp - $class::BASE_FULL * ($class::BASE === 26 ? intval($temp / 0x4000000) : ($temp >> 31)); + $temp = $class::regularMultiply([$temp], $n); + $temp = array_merge(self::array_repeat(0, $i), $temp); + $result = $class::addHelper($result[self::VALUE], false, $temp, false); + } + + $result[self::VALUE] = array_slice($result[self::VALUE], $k); + + if (self::compareHelper($result, false, $n, false) >= 0) { + $result = $class::subtractHelper($result[self::VALUE], false, $n, false); + } + + return $result[self::VALUE]; + } + + /** + * Modular Inverse of a number mod 2**26 (eg. 67108864) + * + * Based off of the bnpInvDigit function implemented and justified in the following URL: + * + * {@link http://www-cs-students.stanford.edu/~tjw/jsbn/jsbn.js} + * + * The following URL provides more info: + * + * {@link http://groups.google.com/group/sci.crypt/msg/7a137205c1be7d85} + * + * As for why we do all the bitmasking... strange things can happen when converting from floats to ints. For + * instance, on some computers, var_dump((int) -4294967297) yields int(-1) and on others, it yields + * int(-2147483648). To avoid problems stemming from this, we use bitmasks to guarantee that ints aren't + * auto-converted to floats. The outermost bitmask is present because without it, there's no guarantee that + * the "residue" returned would be the so-called "common residue". We use fmod, in the last step, because the + * maximum possible $x is 26 bits and the maximum $result is 16 bits. Thus, we have to be able to handle up to + * 40 bits, which only 64-bit floating points will support. + * + * Thanks to Pedro Gimeno Fortea for input! + * + * @param array $x + * @param string $class + * @return int + */ + protected static function modInverse67108864(array $x, $class) // 2**26 == 67,108,864 + { + $x = -$x[0]; + $result = $x & 0x3; // x**-1 mod 2**2 + $result = ($result * (2 - $x * $result)) & 0xF; // x**-1 mod 2**4 + $result = ($result * (2 - ($x & 0xFF) * $result)) & 0xFF; // x**-1 mod 2**8 + $result = ($result * ((2 - ($x & 0xFFFF) * $result) & 0xFFFF)) & 0xFFFF; // x**-1 mod 2**16 + $result = $class::BASE == 26 ? + fmod($result * (2 - fmod($x * $result, $class::BASE_FULL)), $class::BASE_FULL) : // x**-1 mod 2**26 + ($result * (2 - ($x * $result) % $class::BASE_FULL)) % $class::BASE_FULL; + return $result & $class::MAX_DIGIT; + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Reductions/MontgomeryMult.php b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Reductions/MontgomeryMult.php new file mode 100644 index 00000000..4fed3c3f --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Reductions/MontgomeryMult.php @@ -0,0 +1,76 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Math\BigInteger\Engines\PHP\Reductions; + +use phpseclib3\Math\BigInteger\Engines\PHP; + +/** + * PHP Montgomery Modular Exponentiation Engine with interleaved multiplication + * + * @author Jim Wigginton + */ +abstract class MontgomeryMult extends Montgomery +{ + /** + * Montgomery Multiply + * + * Interleaves the montgomery reduction and long multiplication algorithms together as described in + * {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=13 HAC 14.36} + * + * @see self::_prepMontgomery() + * @see self::_montgomery() + * @param array $x + * @param array $y + * @param array $m + * @param class-string $class + * @return array + */ + public static function multiplyReduce(array $x, array $y, array $m, $class) + { + // the following code, although not callable, can be run independently of the above code + // although the above code performed better in my benchmarks the following could might + // perform better under different circumstances. in lieu of deleting it it's just been + // made uncallable + + static $cache = [ + self::VARIABLE => [], + self::DATA => [] + ]; + + if (($key = array_search($m, $cache[self::VARIABLE])) === false) { + $key = count($cache[self::VARIABLE]); + $cache[self::VARIABLE][] = $m; + $cache[self::DATA][] = self::modInverse67108864($m, $class); + } + + $n = max(count($x), count($y), count($m)); + $x = array_pad($x, $n, 0); + $y = array_pad($y, $n, 0); + $m = array_pad($m, $n, 0); + $a = [self::VALUE => self::array_repeat(0, $n + 1)]; + for ($i = 0; $i < $n; ++$i) { + $temp = $a[self::VALUE][0] + $x[$i] * $y[0]; + $temp = $temp - $class::BASE_FULL * ($class::BASE === 26 ? intval($temp / 0x4000000) : ($temp >> 31)); + $temp = $temp * $cache[self::DATA][$key]; + $temp = $temp - $class::BASE_FULL * ($class::BASE === 26 ? intval($temp / 0x4000000) : ($temp >> 31)); + $temp = $class::addHelper($class::regularMultiply([$x[$i]], $y), false, $class::regularMultiply([$temp], $m), false); + $a = $class::addHelper($a[self::VALUE], false, $temp[self::VALUE], false); + $a[self::VALUE] = array_slice($a[self::VALUE], 1); + } + if (self::compareHelper($a[self::VALUE], false, $m, false) >= 0) { + $a = $class::subtractHelper($a[self::VALUE], false, $m, false); + } + return $a[self::VALUE]; + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Reductions/PowerOfTwo.php b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Reductions/PowerOfTwo.php new file mode 100644 index 00000000..9da133a1 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Reductions/PowerOfTwo.php @@ -0,0 +1,59 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Math\BigInteger\Engines\PHP\Reductions; + +use phpseclib3\Math\BigInteger\Engines\PHP\Base; + +/** + * PHP Power Of Two Modular Exponentiation Engine + * + * @author Jim Wigginton + */ +abstract class PowerOfTwo extends Base +{ + /** + * Prepare a number for use in Montgomery Modular Reductions + * + * @param array $x + * @param array $n + * @param string $class + * @return array + */ + protected static function prepareReduce(array $x, array $n, $class) + { + return self::reduce($x, $n, $class); + } + + /** + * Power Of Two Reduction + * + * @param array $x + * @param array $n + * @param string $class + * @return array + */ + protected static function reduce(array $x, array $n, $class) + { + $lhs = new $class(); + $lhs->value = $x; + $rhs = new $class(); + $rhs->value = $n; + + $temp = new $class(); + $temp->value = [1]; + + $result = $lhs->bitwise_and($rhs->subtract($temp)); + return $result->value; + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP32.php b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP32.php new file mode 100644 index 00000000..964cd170 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP32.php @@ -0,0 +1,371 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Math\BigInteger\Engines; + +/** + * Pure-PHP 32-bit Engine. + * + * Uses 64-bit floats if int size is 4 bits + * + * @author Jim Wigginton + */ +class PHP32 extends PHP +{ + // Constants used by PHP.php + const BASE = 26; + const BASE_FULL = 0x4000000; + const MAX_DIGIT = 0x3FFFFFF; + const MSB = 0x2000000; + + /** + * MAX10 in greatest MAX10LEN satisfying + * MAX10 = 10**MAX10LEN <= 2**BASE. + */ + const MAX10 = 10000000; + + /** + * MAX10LEN in greatest MAX10LEN satisfying + * MAX10 = 10**MAX10LEN <= 2**BASE. + */ + const MAX10LEN = 7; + const MAX_DIGIT2 = 4503599627370496; + + /** + * Initialize a PHP32 BigInteger Engine instance + * + * @param int $base + * @see parent::initialize() + */ + protected function initialize($base) + { + if ($base != 256 && $base != -256) { + return parent::initialize($base); + } + + $val = $this->value; + $this->value = []; + $vals = &$this->value; + $i = strlen($val); + if (!$i) { + return; + } + + while (true) { + $i -= 4; + if ($i < 0) { + if ($i == -4) { + break; + } + $val = substr($val, 0, 4 + $i); + $val = str_pad($val, 4, "\0", STR_PAD_LEFT); + if ($val == "\0\0\0\0") { + break; + } + $i = 0; + } + list(, $digit) = unpack('N', substr($val, $i, 4)); + if ($digit < 0) { + $digit += 0xFFFFFFFF + 1; + } + $step = count($vals) & 3; + if ($step) { + $digit = floor($digit / pow(2, 2 * $step)); + } + if ($step != 3) { + $digit &= static::MAX_DIGIT; + $i++; + } + $vals[] = $digit; + } + while (end($vals) === 0) { + array_pop($vals); + } + reset($vals); + } + + /** + * Test for engine validity + * + * @see parent::__construct() + * @return bool + */ + public static function isValidEngine() + { + return PHP_INT_SIZE >= 4; + } + + /** + * Adds two BigIntegers. + * + * @param PHP32 $y + * @return PHP32 + */ + public function add(PHP32 $y) + { + $temp = self::addHelper($this->value, $this->is_negative, $y->value, $y->is_negative); + + return $this->convertToObj($temp); + } + + /** + * Subtracts two BigIntegers. + * + * @param PHP32 $y + * @return PHP32 + */ + public function subtract(PHP32 $y) + { + $temp = self::subtractHelper($this->value, $this->is_negative, $y->value, $y->is_negative); + + return $this->convertToObj($temp); + } + + /** + * Multiplies two BigIntegers. + * + * @param PHP32 $y + * @return PHP32 + */ + public function multiply(PHP32 $y) + { + $temp = self::multiplyHelper($this->value, $this->is_negative, $y->value, $y->is_negative); + + return $this->convertToObj($temp); + } + + /** + * Divides two BigIntegers. + * + * Returns an array whose first element contains the quotient and whose second element contains the + * "common residue". If the remainder would be positive, the "common residue" and the remainder are the + * same. If the remainder would be negative, the "common residue" is equal to the sum of the remainder + * and the divisor (basically, the "common residue" is the first positive modulo). + * + * @param PHP32 $y + * @return array{PHP32, PHP32} + */ + public function divide(PHP32 $y) + { + return $this->divideHelper($y); + } + + /** + * Calculates modular inverses. + * + * Say you have (30 mod 17 * x mod 17) mod 17 == 1. x can be found using modular inverses. + * @param PHP32 $n + * @return false|PHP32 + */ + public function modInverse(PHP32 $n) + { + return $this->modInverseHelper($n); + } + + /** + * Calculates modular inverses. + * + * Say you have (30 mod 17 * x mod 17) mod 17 == 1. x can be found using modular inverses. + * @param PHP32 $n + * @return PHP32[] + */ + public function extendedGCD(PHP32 $n) + { + return $this->extendedGCDHelper($n); + } + + /** + * Calculates the greatest common divisor + * + * Say you have 693 and 609. The GCD is 21. + * + * @param PHP32 $n + * @return PHP32 + */ + public function gcd(PHP32 $n) + { + return $this->extendedGCD($n)['gcd']; + } + + /** + * Logical And + * + * @param PHP32 $x + * @return PHP32 + */ + public function bitwise_and(PHP32 $x) + { + return $this->bitwiseAndHelper($x); + } + + /** + * Logical Or + * + * @param PHP32 $x + * @return PHP32 + */ + public function bitwise_or(PHP32 $x) + { + return $this->bitwiseOrHelper($x); + } + + /** + * Logical Exclusive Or + * + * @param PHP32 $x + * @return PHP32 + */ + public function bitwise_xor(PHP32 $x) + { + return $this->bitwiseXorHelper($x); + } + + /** + * Compares two numbers. + * + * Although one might think !$x->compare($y) means $x != $y, it, in fact, means the opposite. The reason for this is + * demonstrated thusly: + * + * $x > $y: $x->compare($y) > 0 + * $x < $y: $x->compare($y) < 0 + * $x == $y: $x->compare($y) == 0 + * + * Note how the same comparison operator is used. If you want to test for equality, use $x->equals($y). + * + * {@internal Could return $this->subtract($x), but that's not as fast as what we do do.} + * + * @param PHP32 $y + * @return int in case < 0 if $this is less than $y; > 0 if $this is greater than $y, and 0 if they are equal. + * @see self::equals() + */ + public function compare(PHP32 $y) + { + return $this->compareHelper($this->value, $this->is_negative, $y->value, $y->is_negative); + } + + /** + * Tests the equality of two numbers. + * + * If you need to see if one number is greater than or less than another number, use BigInteger::compare() + * + * @param PHP32 $x + * @return bool + */ + public function equals(PHP32 $x) + { + return $this->value === $x->value && $this->is_negative == $x->is_negative; + } + + /** + * Performs modular exponentiation. + * + * @param PHP32 $e + * @param PHP32 $n + * @return PHP32 + */ + public function modPow(PHP32 $e, PHP32 $n) + { + return $this->powModOuter($e, $n); + } + + /** + * Performs modular exponentiation. + * + * Alias for modPow(). + * + * @param PHP32 $e + * @param PHP32 $n + * @return PHP32 + */ + public function powMod(PHP32 $e, PHP32 $n) + { + return $this->powModOuter($e, $n); + } + + /** + * Generate a random prime number between a range + * + * If there's not a prime within the given range, false will be returned. + * + * @param PHP32 $min + * @param PHP32 $max + * @return false|PHP32 + */ + public static function randomRangePrime(PHP32 $min, PHP32 $max) + { + return self::randomRangePrimeOuter($min, $max); + } + + /** + * Generate a random number between a range + * + * Returns a random number between $min and $max where $min and $max + * can be defined using one of the two methods: + * + * BigInteger::randomRange($min, $max) + * BigInteger::randomRange($max, $min) + * + * @param PHP32 $min + * @param PHP32 $max + * @return PHP32 + */ + public static function randomRange(PHP32 $min, PHP32 $max) + { + return self::randomRangeHelper($min, $max); + } + + /** + * Performs exponentiation. + * + * @param PHP32 $n + * @return PHP32 + */ + public function pow(PHP32 $n) + { + return $this->powHelper($n); + } + + /** + * Return the minimum BigInteger between an arbitrary number of BigIntegers. + * + * @param PHP32 ...$nums + * @return PHP32 + */ + public static function min(PHP32 ...$nums) + { + return self::minHelper($nums); + } + + /** + * Return the maximum BigInteger between an arbitrary number of BigIntegers. + * + * @param PHP32 ...$nums + * @return PHP32 + */ + public static function max(PHP32 ...$nums) + { + return self::maxHelper($nums); + } + + /** + * Tests BigInteger to see if it is between two integers, inclusive + * + * @param PHP32 $min + * @param PHP32 $max + * @return bool + */ + public function between(PHP32 $min, PHP32 $max) + { + return $this->compare($min) >= 0 && $this->compare($max) <= 0; + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP64.php b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP64.php new file mode 100644 index 00000000..ca11c08d --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP64.php @@ -0,0 +1,372 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Math\BigInteger\Engines; + +/** + * Pure-PHP 64-bit Engine. + * + * Uses 64-bit integers if int size is 8 bits + * + * @author Jim Wigginton + */ +class PHP64 extends PHP +{ + // Constants used by PHP.php + const BASE = 31; + const BASE_FULL = 0x80000000; + const MAX_DIGIT = 0x7FFFFFFF; + const MSB = 0x40000000; + + /** + * MAX10 in greatest MAX10LEN satisfying + * MAX10 = 10**MAX10LEN <= 2**BASE. + */ + const MAX10 = 1000000000; + + /** + * MAX10LEN in greatest MAX10LEN satisfying + * MAX10 = 10**MAX10LEN <= 2**BASE. + */ + const MAX10LEN = 9; + const MAX_DIGIT2 = 4611686018427387904; + + /** + * Initialize a PHP64 BigInteger Engine instance + * + * @param int $base + * @see parent::initialize() + */ + protected function initialize($base) + { + if ($base != 256 && $base != -256) { + return parent::initialize($base); + } + + $val = $this->value; + $this->value = []; + $vals = &$this->value; + $i = strlen($val); + if (!$i) { + return; + } + + while (true) { + $i -= 4; + if ($i < 0) { + if ($i == -4) { + break; + } + $val = substr($val, 0, 4 + $i); + $val = str_pad($val, 4, "\0", STR_PAD_LEFT); + if ($val == "\0\0\0\0") { + break; + } + $i = 0; + } + list(, $digit) = unpack('N', substr($val, $i, 4)); + $step = count($vals) & 7; + if (!$step) { + $digit &= static::MAX_DIGIT; + $i++; + } else { + $shift = 8 - $step; + $digit >>= $shift; + $shift = 32 - $shift; + $digit &= (1 << $shift) - 1; + $temp = $i > 0 ? ord($val[$i - 1]) : 0; + $digit |= ($temp << $shift) & 0x7F000000; + } + $vals[] = $digit; + } + while (end($vals) === 0) { + array_pop($vals); + } + reset($vals); + } + + /** + * Test for engine validity + * + * @see parent::__construct() + * @return bool + */ + public static function isValidEngine() + { + return PHP_INT_SIZE >= 8; + } + + /** + * Adds two BigIntegers. + * + * @param PHP64 $y + * @return PHP64 + */ + public function add(PHP64 $y) + { + $temp = self::addHelper($this->value, $this->is_negative, $y->value, $y->is_negative); + + return $this->convertToObj($temp); + } + + /** + * Subtracts two BigIntegers. + * + * @param PHP64 $y + * @return PHP64 + */ + public function subtract(PHP64 $y) + { + $temp = self::subtractHelper($this->value, $this->is_negative, $y->value, $y->is_negative); + + return $this->convertToObj($temp); + } + + /** + * Multiplies two BigIntegers. + * + * @param PHP64 $y + * @return PHP64 + */ + public function multiply(PHP64 $y) + { + $temp = self::multiplyHelper($this->value, $this->is_negative, $y->value, $y->is_negative); + + return $this->convertToObj($temp); + } + + /** + * Divides two BigIntegers. + * + * Returns an array whose first element contains the quotient and whose second element contains the + * "common residue". If the remainder would be positive, the "common residue" and the remainder are the + * same. If the remainder would be negative, the "common residue" is equal to the sum of the remainder + * and the divisor (basically, the "common residue" is the first positive modulo). + * + * @param PHP64 $y + * @return array{PHP64, PHP64} + */ + public function divide(PHP64 $y) + { + return $this->divideHelper($y); + } + + /** + * Calculates modular inverses. + * + * Say you have (30 mod 17 * x mod 17) mod 17 == 1. x can be found using modular inverses. + * @param PHP64 $n + * @return false|PHP64 + */ + public function modInverse(PHP64 $n) + { + return $this->modInverseHelper($n); + } + + /** + * Calculates modular inverses. + * + * Say you have (30 mod 17 * x mod 17) mod 17 == 1. x can be found using modular inverses. + * @param PHP64 $n + * @return PHP64[] + */ + public function extendedGCD(PHP64 $n) + { + return $this->extendedGCDHelper($n); + } + + /** + * Calculates the greatest common divisor + * + * Say you have 693 and 609. The GCD is 21. + * + * @param PHP64 $n + * @return PHP64 + */ + public function gcd(PHP64 $n) + { + return $this->extendedGCD($n)['gcd']; + } + + /** + * Logical And + * + * @param PHP64 $x + * @return PHP64 + */ + public function bitwise_and(PHP64 $x) + { + return $this->bitwiseAndHelper($x); + } + + /** + * Logical Or + * + * @param PHP64 $x + * @return PHP64 + */ + public function bitwise_or(PHP64 $x) + { + return $this->bitwiseOrHelper($x); + } + + /** + * Logical Exclusive Or + * + * @param PHP64 $x + * @return PHP64 + */ + public function bitwise_xor(PHP64 $x) + { + return $this->bitwiseXorHelper($x); + } + + /** + * Compares two numbers. + * + * Although one might think !$x->compare($y) means $x != $y, it, in fact, means the opposite. The reason for this is + * demonstrated thusly: + * + * $x > $y: $x->compare($y) > 0 + * $x < $y: $x->compare($y) < 0 + * $x == $y: $x->compare($y) == 0 + * + * Note how the same comparison operator is used. If you want to test for equality, use $x->equals($y). + * + * {@internal Could return $this->subtract($x), but that's not as fast as what we do do.} + * + * @param PHP64 $y + * @return int in case < 0 if $this is less than $y; > 0 if $this is greater than $y, and 0 if they are equal. + * @see self::equals() + */ + public function compare(PHP64 $y) + { + return parent::compareHelper($this->value, $this->is_negative, $y->value, $y->is_negative); + } + + /** + * Tests the equality of two numbers. + * + * If you need to see if one number is greater than or less than another number, use BigInteger::compare() + * + * @param PHP64 $x + * @return bool + */ + public function equals(PHP64 $x) + { + return $this->value === $x->value && $this->is_negative == $x->is_negative; + } + + /** + * Performs modular exponentiation. + * + * @param PHP64 $e + * @param PHP64 $n + * @return PHP64 + */ + public function modPow(PHP64 $e, PHP64 $n) + { + return $this->powModOuter($e, $n); + } + + /** + * Performs modular exponentiation. + * + * Alias for modPow(). + * + * @param PHP64 $e + * @param PHP64 $n + * @return PHP64|false + */ + public function powMod(PHP64 $e, PHP64 $n) + { + return $this->powModOuter($e, $n); + } + + /** + * Generate a random prime number between a range + * + * If there's not a prime within the given range, false will be returned. + * + * @param PHP64 $min + * @param PHP64 $max + * @return false|PHP64 + */ + public static function randomRangePrime(PHP64 $min, PHP64 $max) + { + return self::randomRangePrimeOuter($min, $max); + } + + /** + * Generate a random number between a range + * + * Returns a random number between $min and $max where $min and $max + * can be defined using one of the two methods: + * + * BigInteger::randomRange($min, $max) + * BigInteger::randomRange($max, $min) + * + * @param PHP64 $min + * @param PHP64 $max + * @return PHP64 + */ + public static function randomRange(PHP64 $min, PHP64 $max) + { + return self::randomRangeHelper($min, $max); + } + + /** + * Performs exponentiation. + * + * @param PHP64 $n + * @return PHP64 + */ + public function pow(PHP64 $n) + { + return $this->powHelper($n); + } + + /** + * Return the minimum BigInteger between an arbitrary number of BigIntegers. + * + * @param PHP64 ...$nums + * @return PHP64 + */ + public static function min(PHP64 ...$nums) + { + return self::minHelper($nums); + } + + /** + * Return the maximum BigInteger between an arbitrary number of BigIntegers. + * + * @param PHP64 ...$nums + * @return PHP64 + */ + public static function max(PHP64 ...$nums) + { + return self::maxHelper($nums); + } + + /** + * Tests BigInteger to see if it is between two integers, inclusive + * + * @param PHP64 $min + * @param PHP64 $max + * @return bool + */ + public function between(PHP64 $min, PHP64 $max) + { + return $this->compare($min) >= 0 && $this->compare($max) <= 0; + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Math/BinaryField.php b/vendor/phpseclib/phpseclib/phpseclib/Math/BinaryField.php new file mode 100644 index 00000000..3e21a67a --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Math/BinaryField.php @@ -0,0 +1,194 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + */ + +namespace phpseclib3\Math; + +use phpseclib3\Common\Functions\Strings; +use phpseclib3\Math\BinaryField\Integer; +use phpseclib3\Math\Common\FiniteField; + +/** + * Binary Finite Fields + * + * @author Jim Wigginton + */ +class BinaryField extends FiniteField +{ + /** + * Instance Counter + * + * @var int + */ + private static $instanceCounter = 0; + + /** + * Keeps track of current instance + * + * @var int + */ + protected $instanceID; + + /** @var BigInteger */ + private $randomMax; + + /** + * Default constructor + */ + public function __construct(...$indices) + { + $m = array_shift($indices); + $val = str_repeat('0', $m) . '1'; + foreach ($indices as $index) { + $val[$index] = '1'; + } + $modulo = static::base2ToBase256(strrev($val)); + + $mStart = 2 * $m - 2; + $t = ceil($m / 8); + $finalMask = chr((1 << ($m % 8)) - 1); + if ($finalMask == "\0") { + $finalMask = "\xFF"; + } + $bitLen = $mStart + 1; + $pad = ceil($bitLen / 8); + $h = $bitLen & 7; + $h = $h ? 8 - $h : 0; + + $r = rtrim(substr($val, 0, -1), '0'); + $u = [static::base2ToBase256(strrev($r))]; + for ($i = 1; $i < 8; $i++) { + $u[] = static::base2ToBase256(strrev(str_repeat('0', $i) . $r)); + } + + // implements algorithm 2.40 (in section 2.3.5) in "Guide to Elliptic Curve Cryptography" + // with W = 8 + $reduce = function ($c) use ($u, $mStart, $m, $t, $finalMask, $pad, $h) { + $c = str_pad($c, $pad, "\0", STR_PAD_LEFT); + for ($i = $mStart; $i >= $m;) { + $g = $h >> 3; + $mask = $h & 7; + $mask = $mask ? 1 << (7 - $mask) : 0x80; + for (; $mask > 0; $mask >>= 1, $i--, $h++) { + if (ord($c[$g]) & $mask) { + $temp = $i - $m; + $j = $temp >> 3; + $k = $temp & 7; + $t1 = $j ? substr($c, 0, -$j) : $c; + $length = strlen($t1); + if ($length) { + $t2 = str_pad($u[$k], $length, "\0", STR_PAD_LEFT); + $temp = $t1 ^ $t2; + $c = $j ? substr_replace($c, $temp, 0, $length) : $temp; + } + } + } + } + $c = substr($c, -$t); + if (strlen($c) == $t) { + $c[0] = $c[0] & $finalMask; + } + return ltrim($c, "\0"); + }; + + $this->instanceID = self::$instanceCounter++; + Integer::setModulo($this->instanceID, $modulo); + Integer::setRecurringModuloFunction($this->instanceID, $reduce); + + $this->randomMax = new BigInteger($modulo, 2); + } + + /** + * Returns an instance of a dynamically generated PrimeFieldInteger class + * + * @param string $num + * @return Integer + */ + public function newInteger($num) + { + return new Integer($this->instanceID, $num instanceof BigInteger ? $num->toBytes() : $num); + } + + /** + * Returns an integer on the finite field between one and the prime modulo + * + * @return Integer + */ + public function randomInteger() + { + static $one; + if (!isset($one)) { + $one = new BigInteger(1); + } + + return new Integer($this->instanceID, BigInteger::randomRange($one, $this->randomMax)->toBytes()); + } + + /** + * Returns the length of the modulo in bytes + * + * @return int + */ + public function getLengthInBytes() + { + return strlen(Integer::getModulo($this->instanceID)); + } + + /** + * Returns the length of the modulo in bits + * + * @return int + */ + public function getLength() + { + return strlen(Integer::getModulo($this->instanceID)) << 3; + } + + /** + * Converts a base-2 string to a base-256 string + * + * @param string $x + * @param int|null $size + * @return string + */ + public static function base2ToBase256($x, $size = null) + { + $str = Strings::bits2bin($x); + + $pad = strlen($x) >> 3; + if (strlen($x) & 3) { + $pad++; + } + $str = str_pad($str, $pad, "\0", STR_PAD_LEFT); + if (isset($size)) { + $str = str_pad($str, $size, "\0", STR_PAD_LEFT); + } + + return $str; + } + + /** + * Converts a base-256 string to a base-2 string + * + * @param string $x + * @return string + */ + public static function base256ToBase2($x) + { + if (function_exists('gmp_import')) { + return gmp_strval(gmp_import($x), 2); + } + + return Strings::bin2bits($x); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Math/BinaryField/Integer.php b/vendor/phpseclib/phpseclib/phpseclib/Math/BinaryField/Integer.php new file mode 100644 index 00000000..8e880589 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Math/BinaryField/Integer.php @@ -0,0 +1,516 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + */ + +namespace phpseclib3\Math\BinaryField; + +use phpseclib3\Common\Functions\Strings; +use phpseclib3\Math\BigInteger; +use phpseclib3\Math\BinaryField; +use phpseclib3\Math\Common\FiniteField\Integer as Base; + +/** + * Binary Finite Fields + * + * @author Jim Wigginton + */ +class Integer extends Base +{ + /** + * Holds the BinaryField's value + * + * @var string + */ + protected $value; + + /** + * Keeps track of current instance + * + * @var int + */ + protected $instanceID; + + /** + * Holds the PrimeField's modulo + * + * @var array + */ + protected static $modulo; + + /** + * Holds a pre-generated function to perform modulo reductions + * + * @var callable[] + */ + protected static $reduce; + + /** + * Default constructor + */ + public function __construct($instanceID, $num = '') + { + $this->instanceID = $instanceID; + if (!strlen($num)) { + $this->value = ''; + } else { + $reduce = static::$reduce[$instanceID]; + $this->value = $reduce($num); + } + } + + /** + * Set the modulo for a given instance + * @param int $instanceID + * @param string $modulo + */ + public static function setModulo($instanceID, $modulo) + { + static::$modulo[$instanceID] = $modulo; + } + + /** + * Set the modulo for a given instance + */ + public static function setRecurringModuloFunction($instanceID, callable $function) + { + static::$reduce[$instanceID] = $function; + } + + /** + * Tests a parameter to see if it's of the right instance + * + * Throws an exception if the incorrect class is being utilized + */ + private static function checkInstance(self $x, self $y) + { + if ($x->instanceID != $y->instanceID) { + throw new \UnexpectedValueException('The instances of the two BinaryField\Integer objects do not match'); + } + } + + /** + * Tests the equality of two numbers. + * + * @return bool + */ + public function equals(self $x) + { + static::checkInstance($this, $x); + + return $this->value == $x->value; + } + + /** + * Compares two numbers. + * + * @return int + */ + public function compare(self $x) + { + static::checkInstance($this, $x); + + $a = $this->value; + $b = $x->value; + + $length = max(strlen($a), strlen($b)); + + $a = str_pad($a, $length, "\0", STR_PAD_LEFT); + $b = str_pad($b, $length, "\0", STR_PAD_LEFT); + + return strcmp($a, $b); + } + + /** + * Returns the degree of the polynomial + * + * @param string $x + * @return int + */ + private static function deg($x) + { + $x = ltrim($x, "\0"); + $xbit = decbin(ord($x[0])); + $xlen = $xbit == '0' ? 0 : strlen($xbit); + $len = strlen($x); + if (!$len) { + return -1; + } + return 8 * strlen($x) - 9 + $xlen; + } + + /** + * Perform polynomial division + * + * @return string[] + * @link https://en.wikipedia.org/wiki/Polynomial_greatest_common_divisor#Euclidean_division + */ + private static function polynomialDivide($x, $y) + { + // in wikipedia's description of the algorithm, lc() is the leading coefficient. over a binary field that's + // always going to be 1. + + $q = chr(0); + $d = static::deg($y); + $r = $x; + while (($degr = static::deg($r)) >= $d) { + $s = '1' . str_repeat('0', $degr - $d); + $s = BinaryField::base2ToBase256($s); + $length = max(strlen($s), strlen($q)); + $q = !isset($q) ? $s : + str_pad($q, $length, "\0", STR_PAD_LEFT) ^ + str_pad($s, $length, "\0", STR_PAD_LEFT); + $s = static::polynomialMultiply($s, $y); + $length = max(strlen($r), strlen($s)); + $r = str_pad($r, $length, "\0", STR_PAD_LEFT) ^ + str_pad($s, $length, "\0", STR_PAD_LEFT); + } + + return [ltrim($q, "\0"), ltrim($r, "\0")]; + } + + /** + * Perform polynomial multiplation in the traditional way + * + * @return string + * @link https://en.wikipedia.org/wiki/Finite_field_arithmetic#Multiplication + */ + private static function regularPolynomialMultiply($x, $y) + { + $precomputed = [ltrim($x, "\0")]; + $x = strrev(BinaryField::base256ToBase2($x)); + $y = strrev(BinaryField::base256ToBase2($y)); + if (strlen($x) == strlen($y)) { + $length = strlen($x); + } else { + $length = max(strlen($x), strlen($y)); + $x = str_pad($x, $length, '0'); + $y = str_pad($y, $length, '0'); + } + $result = str_repeat('0', 2 * $length - 1); + $result = BinaryField::base2ToBase256($result); + $size = strlen($result); + $x = strrev($x); + + // precompute left shift 1 through 7 + for ($i = 1; $i < 8; $i++) { + $precomputed[$i] = BinaryField::base2ToBase256($x . str_repeat('0', $i)); + } + for ($i = 0; $i < strlen($y); $i++) { + if ($y[$i] == '1') { + $temp = $precomputed[$i & 7] . str_repeat("\0", $i >> 3); + $result ^= str_pad($temp, $size, "\0", STR_PAD_LEFT); + } + } + + return $result; + } + + /** + * Perform polynomial multiplation + * + * Uses karatsuba multiplication to reduce x-bit multiplications to a series of 32-bit multiplications + * + * @return string + * @link https://en.wikipedia.org/wiki/Karatsuba_algorithm + */ + private static function polynomialMultiply($x, $y) + { + if (strlen($x) == strlen($y)) { + $length = strlen($x); + } else { + $length = max(strlen($x), strlen($y)); + $x = str_pad($x, $length, "\0", STR_PAD_LEFT); + $y = str_pad($y, $length, "\0", STR_PAD_LEFT); + } + + switch (true) { + case PHP_INT_SIZE == 8 && $length <= 4: + return $length != 4 ? + self::subMultiply(str_pad($x, 4, "\0", STR_PAD_LEFT), str_pad($y, 4, "\0", STR_PAD_LEFT)) : + self::subMultiply($x, $y); + case PHP_INT_SIZE == 4 || $length > 32: + return self::regularPolynomialMultiply($x, $y); + } + + $m = $length >> 1; + + $x1 = substr($x, 0, -$m); + $x0 = substr($x, -$m); + $y1 = substr($y, 0, -$m); + $y0 = substr($y, -$m); + + $z2 = self::polynomialMultiply($x1, $y1); + $z0 = self::polynomialMultiply($x0, $y0); + $z1 = self::polynomialMultiply( + self::subAdd2($x1, $x0), + self::subAdd2($y1, $y0) + ); + + $z1 = self::subAdd3($z1, $z2, $z0); + + $xy = self::subAdd3( + $z2 . str_repeat("\0", 2 * $m), + $z1 . str_repeat("\0", $m), + $z0 + ); + + return ltrim($xy, "\0"); + } + + /** + * Perform polynomial multiplication on 2x 32-bit numbers, returning + * a 64-bit number + * + * @param string $x + * @param string $y + * @return string + * @link https://www.bearssl.org/constanttime.html#ghash-for-gcm + */ + private static function subMultiply($x, $y) + { + $x = unpack('N', $x)[1]; + $y = unpack('N', $y)[1]; + + $x0 = $x & 0x11111111; + $x1 = $x & 0x22222222; + $x2 = $x & 0x44444444; + $x3 = $x & 0x88888888; + + $y0 = $y & 0x11111111; + $y1 = $y & 0x22222222; + $y2 = $y & 0x44444444; + $y3 = $y & 0x88888888; + + $z0 = ($x0 * $y0) ^ ($x1 * $y3) ^ ($x2 * $y2) ^ ($x3 * $y1); + $z1 = ($x0 * $y1) ^ ($x1 * $y0) ^ ($x2 * $y3) ^ ($x3 * $y2); + $z2 = ($x0 * $y2) ^ ($x1 * $y1) ^ ($x2 * $y0) ^ ($x3 * $y3); + $z3 = ($x0 * $y3) ^ ($x1 * $y2) ^ ($x2 * $y1) ^ ($x3 * $y0); + + $z0 &= 0x1111111111111111; + $z1 &= 0x2222222222222222; + $z2 &= 0x4444444444444444; + $z3 &= -8608480567731124088; // 0x8888888888888888 gets interpreted as a float + + $z = $z0 | $z1 | $z2 | $z3; + + return pack('J', $z); + } + + /** + * Adds two numbers + * + * @param string $x + * @param string $y + * @return string + */ + private static function subAdd2($x, $y) + { + $length = max(strlen($x), strlen($y)); + $x = str_pad($x, $length, "\0", STR_PAD_LEFT); + $y = str_pad($y, $length, "\0", STR_PAD_LEFT); + return $x ^ $y; + } + + /** + * Adds three numbers + * + * @param string $x + * @param string $y + * @return string + */ + private static function subAdd3($x, $y, $z) + { + $length = max(strlen($x), strlen($y), strlen($z)); + $x = str_pad($x, $length, "\0", STR_PAD_LEFT); + $y = str_pad($y, $length, "\0", STR_PAD_LEFT); + $z = str_pad($z, $length, "\0", STR_PAD_LEFT); + return $x ^ $y ^ $z; + } + + /** + * Adds two BinaryFieldIntegers. + * + * @return static + */ + public function add(self $y) + { + static::checkInstance($this, $y); + + $length = strlen(static::$modulo[$this->instanceID]); + + $x = str_pad($this->value, $length, "\0", STR_PAD_LEFT); + $y = str_pad($y->value, $length, "\0", STR_PAD_LEFT); + + return new static($this->instanceID, $x ^ $y); + } + + /** + * Subtracts two BinaryFieldIntegers. + * + * @return static + */ + public function subtract(self $x) + { + return $this->add($x); + } + + /** + * Multiplies two BinaryFieldIntegers. + * + * @return static + */ + public function multiply(self $y) + { + static::checkInstance($this, $y); + + return new static($this->instanceID, static::polynomialMultiply($this->value, $y->value)); + } + + /** + * Returns the modular inverse of a BinaryFieldInteger + * + * @return static + */ + public function modInverse() + { + $remainder0 = static::$modulo[$this->instanceID]; + $remainder1 = $this->value; + + if ($remainder1 == '') { + return new static($this->instanceID); + } + + $aux0 = "\0"; + $aux1 = "\1"; + while ($remainder1 != "\1") { + list($q, $r) = static::polynomialDivide($remainder0, $remainder1); + $remainder0 = $remainder1; + $remainder1 = $r; + // the auxiliary in row n is given by the sum of the auxiliary in + // row n-2 and the product of the quotient and the auxiliary in row + // n-1 + $temp = static::polynomialMultiply($aux1, $q); + $aux = str_pad($aux0, strlen($temp), "\0", STR_PAD_LEFT) ^ + str_pad($temp, strlen($aux0), "\0", STR_PAD_LEFT); + $aux0 = $aux1; + $aux1 = $aux; + } + + $temp = new static($this->instanceID); + $temp->value = ltrim($aux1, "\0"); + return $temp; + } + + /** + * Divides two PrimeFieldIntegers. + * + * @return static + */ + public function divide(self $x) + { + static::checkInstance($this, $x); + + $x = $x->modInverse(); + return $this->multiply($x); + } + + /** + * Negate + * + * A negative number can be written as 0-12. With modulos, 0 is the same thing as the modulo + * so 0-12 is the same thing as modulo-12 + * + * @return object + */ + public function negate() + { + $x = str_pad($this->value, strlen(static::$modulo[$this->instanceID]), "\0", STR_PAD_LEFT); + + return new static($this->instanceID, $x ^ static::$modulo[$this->instanceID]); + } + + /** + * Returns the modulo + * + * @return string + */ + public static function getModulo($instanceID) + { + return static::$modulo[$instanceID]; + } + + /** + * Converts an Integer to a byte string (eg. base-256). + * + * @return string + */ + public function toBytes() + { + return str_pad($this->value, strlen(static::$modulo[$this->instanceID]), "\0", STR_PAD_LEFT); + } + + /** + * Converts an Integer to a hex string (eg. base-16). + * + * @return string + */ + public function toHex() + { + return Strings::bin2hex($this->toBytes()); + } + + /** + * Converts an Integer to a bit string (eg. base-2). + * + * @return string + */ + public function toBits() + { + //return str_pad(BinaryField::base256ToBase2($this->value), strlen(static::$modulo[$this->instanceID]), '0', STR_PAD_LEFT); + return BinaryField::base256ToBase2($this->value); + } + + /** + * Converts an Integer to a BigInteger + * + * @return string + */ + public function toBigInteger() + { + return new BigInteger($this->value, 256); + } + + /** + * __toString() magic method + * + */ + public function __toString() + { + return (string) $this->toBigInteger(); + } + + /** + * __debugInfo() magic method + * + */ + public function __debugInfo() + { + return ['value' => $this->toHex()]; + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Math/Common/FiniteField.php b/vendor/phpseclib/phpseclib/phpseclib/Math/Common/FiniteField.php new file mode 100644 index 00000000..2ea5f485 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Math/Common/FiniteField.php @@ -0,0 +1,22 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + */ + +namespace phpseclib3\Math\Common; + +/** + * Finite Fields + * + * @author Jim Wigginton + */ +abstract class FiniteField +{ +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Math/Common/FiniteField/Integer.php b/vendor/phpseclib/phpseclib/phpseclib/Math/Common/FiniteField/Integer.php new file mode 100644 index 00000000..4197ed37 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Math/Common/FiniteField/Integer.php @@ -0,0 +1,42 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + */ + +namespace phpseclib3\Math\Common\FiniteField; + +/** + * Finite Field Integer + * + * @author Jim Wigginton + */ +abstract class Integer implements \JsonSerializable +{ + /** + * JSON Serialize + * + * Will be called, automatically, when json_encode() is called on a BigInteger object. + * + * PHP Serialize isn't supported because unserializing would require the factory be + * serialized as well and that just sounds like too much + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return ['hex' => $this->toHex(true)]; + } + + /** + * Converts an Integer to a hex string (eg. base-16). + * + * @return string + */ + abstract public function toHex(); +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Math/PrimeField.php b/vendor/phpseclib/phpseclib/phpseclib/Math/PrimeField.php new file mode 100644 index 00000000..17ff87a0 --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Math/PrimeField.php @@ -0,0 +1,118 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +namespace phpseclib3\Math; + +use phpseclib3\Math\Common\FiniteField; +use phpseclib3\Math\PrimeField\Integer; + +/** + * Prime Finite Fields + * + * @author Jim Wigginton + */ +class PrimeField extends FiniteField +{ + /** + * Instance Counter + * + * @var int + */ + private static $instanceCounter = 0; + + /** + * Keeps track of current instance + * + * @var int + */ + protected $instanceID; + + /** + * Default constructor + */ + public function __construct(BigInteger $modulo) + { + //if (!$modulo->isPrime()) { + // throw new \UnexpectedValueException('PrimeField requires a prime number be passed to the constructor'); + //} + + $this->instanceID = self::$instanceCounter++; + Integer::setModulo($this->instanceID, $modulo); + Integer::setRecurringModuloFunction($this->instanceID, $modulo->createRecurringModuloFunction()); + } + + /** + * Use a custom defined modular reduction function + * + * @return void + */ + public function setReduction(\Closure $func) + { + $this->reduce = $func->bindTo($this, $this); + } + + /** + * Returns an instance of a dynamically generated PrimeFieldInteger class + * + * @return Integer + */ + public function newInteger(BigInteger $num) + { + return new Integer($this->instanceID, $num); + } + + /** + * Returns an integer on the finite field between one and the prime modulo + * + * @return Integer + */ + public function randomInteger() + { + static $one; + if (!isset($one)) { + $one = new BigInteger(1); + } + + return new Integer($this->instanceID, BigInteger::randomRange($one, Integer::getModulo($this->instanceID))); + } + + /** + * Returns the length of the modulo in bytes + * + * @return int + */ + public function getLengthInBytes() + { + return Integer::getModulo($this->instanceID)->getLengthInBytes(); + } + + /** + * Returns the length of the modulo in bits + * + * @return int + */ + public function getLength() + { + return Integer::getModulo($this->instanceID)->getLength(); + } + + /** + * Destructor + */ + public function __destruct() + { + Integer::cleanupCache($this->instanceID); + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Math/PrimeField/Integer.php b/vendor/phpseclib/phpseclib/phpseclib/Math/PrimeField/Integer.php new file mode 100644 index 00000000..2d0c10ca --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/Math/PrimeField/Integer.php @@ -0,0 +1,416 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + */ + +namespace phpseclib3\Math\PrimeField; + +use phpseclib3\Common\Functions\Strings; +use phpseclib3\Math\BigInteger; +use phpseclib3\Math\Common\FiniteField\Integer as Base; + +/** + * Prime Finite Fields + * + * @author Jim Wigginton + */ +class Integer extends Base +{ + /** + * Holds the PrimeField's value + * + * @var BigInteger + */ + protected $value; + + /** + * Keeps track of current instance + * + * @var int + */ + protected $instanceID; + + /** + * Holds the PrimeField's modulo + * + * @var array + */ + protected static $modulo; + + /** + * Holds a pre-generated function to perform modulo reductions + * + * @var array + */ + protected static $reduce; + + /** + * Zero + * + * @var BigInteger + */ + protected static $zero; + + /** + * Default constructor + * + * @param int $instanceID + */ + public function __construct($instanceID, BigInteger $num = null) + { + $this->instanceID = $instanceID; + if (!isset($num)) { + $this->value = clone static::$zero[static::class]; + } else { + $reduce = static::$reduce[$instanceID]; + $this->value = $reduce($num); + } + } + + /** + * Set the modulo for a given instance + * + * @param int $instanceID + * @return void + */ + public static function setModulo($instanceID, BigInteger $modulo) + { + static::$modulo[$instanceID] = $modulo; + } + + /** + * Set the modulo for a given instance + * + * @param int $instanceID + * @return void + */ + public static function setRecurringModuloFunction($instanceID, callable $function) + { + static::$reduce[$instanceID] = $function; + if (!isset(static::$zero[static::class])) { + static::$zero[static::class] = new BigInteger(); + } + } + + /** + * Delete the modulo for a given instance + */ + public static function cleanupCache($instanceID) + { + unset(static::$modulo[$instanceID]); + unset(static::$reduce[$instanceID]); + } + + /** + * Returns the modulo + * + * @param int $instanceID + * @return BigInteger + */ + public static function getModulo($instanceID) + { + return static::$modulo[$instanceID]; + } + + /** + * Tests a parameter to see if it's of the right instance + * + * Throws an exception if the incorrect class is being utilized + * + * @return void + */ + public static function checkInstance(self $x, self $y) + { + if ($x->instanceID != $y->instanceID) { + throw new \UnexpectedValueException('The instances of the two PrimeField\Integer objects do not match'); + } + } + + /** + * Tests the equality of two numbers. + * + * @return bool + */ + public function equals(self $x) + { + static::checkInstance($this, $x); + + return $this->value->equals($x->value); + } + + /** + * Compares two numbers. + * + * @return int + */ + public function compare(self $x) + { + static::checkInstance($this, $x); + + return $this->value->compare($x->value); + } + + /** + * Adds two PrimeFieldIntegers. + * + * @return static + */ + public function add(self $x) + { + static::checkInstance($this, $x); + + $temp = new static($this->instanceID); + $temp->value = $this->value->add($x->value); + if ($temp->value->compare(static::$modulo[$this->instanceID]) >= 0) { + $temp->value = $temp->value->subtract(static::$modulo[$this->instanceID]); + } + + return $temp; + } + + /** + * Subtracts two PrimeFieldIntegers. + * + * @return static + */ + public function subtract(self $x) + { + static::checkInstance($this, $x); + + $temp = new static($this->instanceID); + $temp->value = $this->value->subtract($x->value); + if ($temp->value->isNegative()) { + $temp->value = $temp->value->add(static::$modulo[$this->instanceID]); + } + + return $temp; + } + + /** + * Multiplies two PrimeFieldIntegers. + * + * @return static + */ + public function multiply(self $x) + { + static::checkInstance($this, $x); + + return new static($this->instanceID, $this->value->multiply($x->value)); + } + + /** + * Divides two PrimeFieldIntegers. + * + * @return static + */ + public function divide(self $x) + { + static::checkInstance($this, $x); + + $denominator = $x->value->modInverse(static::$modulo[$this->instanceID]); + return new static($this->instanceID, $this->value->multiply($denominator)); + } + + /** + * Performs power operation on a PrimeFieldInteger. + * + * @return static + */ + public function pow(BigInteger $x) + { + $temp = new static($this->instanceID); + $temp->value = $this->value->powMod($x, static::$modulo[$this->instanceID]); + + return $temp; + } + + /** + * Calculates the square root + * + * @link https://en.wikipedia.org/wiki/Tonelli%E2%80%93Shanks_algorithm + * @return static|false + */ + public function squareRoot() + { + static $one, $two; + if (!isset($one)) { + $one = new BigInteger(1); + $two = new BigInteger(2); + } + $reduce = static::$reduce[$this->instanceID]; + $p_1 = static::$modulo[$this->instanceID]->subtract($one); + $q = clone $p_1; + $s = BigInteger::scan1divide($q); + list($pow) = $p_1->divide($two); + for ($z = $one; !$z->equals(static::$modulo[$this->instanceID]); $z = $z->add($one)) { + $temp = $z->powMod($pow, static::$modulo[$this->instanceID]); + if ($temp->equals($p_1)) { + break; + } + } + + $m = new BigInteger($s); + $c = $z->powMod($q, static::$modulo[$this->instanceID]); + $t = $this->value->powMod($q, static::$modulo[$this->instanceID]); + list($temp) = $q->add($one)->divide($two); + $r = $this->value->powMod($temp, static::$modulo[$this->instanceID]); + + while (!$t->equals($one)) { + $i = clone $one; + + while (!$t->powMod($two->pow($i), static::$modulo[$this->instanceID])->equals($one)) { + $i = $i->add($one); + } + + if ($i->compare($m) >= 0) { + return false; + } + $b = $c->powMod($two->pow($m->subtract($i)->subtract($one)), static::$modulo[$this->instanceID]); + $m = $i; + $c = $reduce($b->multiply($b)); + $t = $reduce($t->multiply($c)); + $r = $reduce($r->multiply($b)); + } + + return new static($this->instanceID, $r); + } + + /** + * Is Odd? + * + * @return bool + */ + public function isOdd() + { + return $this->value->isOdd(); + } + + /** + * Negate + * + * A negative number can be written as 0-12. With modulos, 0 is the same thing as the modulo + * so 0-12 is the same thing as modulo-12 + * + * @return static + */ + public function negate() + { + return new static($this->instanceID, static::$modulo[$this->instanceID]->subtract($this->value)); + } + + /** + * Converts an Integer to a byte string (eg. base-256). + * + * @return string + */ + public function toBytes() + { + $length = static::$modulo[$this->instanceID]->getLengthInBytes(); + return str_pad($this->value->toBytes(), $length, "\0", STR_PAD_LEFT); + } + + /** + * Converts an Integer to a hex string (eg. base-16). + * + * @return string + */ + public function toHex() + { + return Strings::bin2hex($this->toBytes()); + } + + /** + * Converts an Integer to a bit string (eg. base-2). + * + * @return string + */ + public function toBits() + { + // return $this->value->toBits(); + static $length; + if (!isset($length)) { + $length = static::$modulo[$this->instanceID]->getLength(); + } + + return str_pad($this->value->toBits(), $length, '0', STR_PAD_LEFT); + } + + /** + * Returns the w-ary non-adjacent form (wNAF) + * + * @param int $w optional + * @return array + */ + public function getNAF($w = 1) + { + $w++; + + $mask = new BigInteger((1 << $w) - 1); + $sub = new BigInteger(1 << $w); + //$sub = new BigInteger(1 << ($w - 1)); + $d = $this->toBigInteger(); + $d_i = []; + + $i = 0; + while ($d->compare(static::$zero[static::class]) > 0) { + if ($d->isOdd()) { + // start mods + + $bigInteger = $d->testBit($w - 1) ? + $d->bitwise_and($mask)->subtract($sub) : + //$sub->subtract($d->bitwise_and($mask)) : + $d->bitwise_and($mask); + // end mods + $d = $d->subtract($bigInteger); + $d_i[$i] = (int) $bigInteger->toString(); + } else { + $d_i[$i] = 0; + } + $shift = !$d->equals(static::$zero[static::class]) && $d->bitwise_and($mask)->equals(static::$zero[static::class]) ? $w : 1; // $w or $w + 1? + $d = $d->bitwise_rightShift($shift); + while (--$shift > 0) { + $d_i[++$i] = 0; + } + $i++; + } + + return $d_i; + } + + /** + * Converts an Integer to a BigInteger + * + * @return BigInteger + */ + public function toBigInteger() + { + return clone $this->value; + } + + /** + * __toString() magic method + * + * @return string + */ + public function __toString() + { + return (string) $this->value; + } + + /** + * __debugInfo() magic method + * + * @return array + */ + public function __debugInfo() + { + return ['value' => $this->toHex()]; + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Net/SCP.php b/vendor/phpseclib/phpseclib/phpseclib/Net/SCP.php deleted file mode 100644 index cf13496c..00000000 --- a/vendor/phpseclib/phpseclib/phpseclib/Net/SCP.php +++ /dev/null @@ -1,342 +0,0 @@ - - * login('username', 'password')) { - * exit('bad login'); - * } - * $scp = new \phpseclib\Net\SCP($ssh); - * - * $scp->put('abcd', str_repeat('x', 1024*1024)); - * ?> - * - * - * @category Net - * @package SCP - * @author Jim Wigginton - * @copyright 2010 Jim Wigginton - * @license http://www.opensource.org/licenses/mit-license.html MIT License - * @link http://phpseclib.sourceforge.net - */ - -namespace phpseclib\Net; - -/** - * Pure-PHP implementations of SCP. - * - * @package SCP - * @author Jim Wigginton - * @access public - */ -class SCP -{ - /**#@+ - * @access public - * @see \phpseclib\Net\SCP::put() - */ - /** - * Reads data from a local file. - */ - const SOURCE_LOCAL_FILE = 1; - /** - * Reads data from a string. - */ - const SOURCE_STRING = 2; - /**#@-*/ - - /**#@+ - * @access private - * @see \phpseclib\Net\SCP::_send() - * @see \phpseclib\Net\SCP::_receive() - */ - /** - * SSH1 is being used. - */ - const MODE_SSH1 = 1; - /** - * SSH2 is being used. - */ - const MODE_SSH2 = 2; - /**#@-*/ - - /** - * SSH Object - * - * @var object - * @access private - */ - var $ssh; - - /** - * Packet Size - * - * @var int - * @access private - */ - var $packet_size; - - /** - * Mode - * - * @var int - * @access private - */ - var $mode; - - /** - * Default Constructor. - * - * Connects to an SSH server - * - * @param \phpseclib\Net\SSH1|\phpseclib\Net\SSH2 $ssh - * @return \phpseclib\Net\SCP - * @access public - */ - function __construct($ssh) - { - if ($ssh instanceof SSH2) { - $this->mode = self::MODE_SSH2; - } elseif ($ssh instanceof SSH1) { - $this->packet_size = 50000; - $this->mode = self::MODE_SSH1; - } else { - return; - } - - $this->ssh = $ssh; - } - - /** - * Uploads a file to the SCP server. - * - * By default, \phpseclib\Net\SCP::put() does not read from the local filesystem. $data is dumped directly into $remote_file. - * So, for example, if you set $data to 'filename.ext' and then do \phpseclib\Net\SCP::get(), you will get a file, twelve bytes - * long, containing 'filename.ext' as its contents. - * - * Setting $mode to self::SOURCE_LOCAL_FILE will change the above behavior. With self::SOURCE_LOCAL_FILE, $remote_file will - * contain as many bytes as filename.ext does on your local filesystem. If your filename.ext is 1MB then that is how - * large $remote_file will be, as well. - * - * Currently, only binary mode is supported. As such, if the line endings need to be adjusted, you will need to take - * care of that, yourself. - * - * @param string $remote_file - * @param string $data - * @param int $mode - * @param callable $callback - * @return bool - * @access public - */ - function put($remote_file, $data, $mode = self::SOURCE_STRING, $callback = null) - { - if (!isset($this->ssh)) { - return false; - } - - if (empty($remote_file)) { - user_error('remote_file cannot be blank', E_USER_NOTICE); - return false; - } - - if (!$this->ssh->exec('scp -t ' . escapeshellarg($remote_file), false)) { // -t = to - return false; - } - - $temp = $this->_receive(); - if ($temp !== chr(0)) { - return false; - } - - if ($this->mode == self::MODE_SSH2) { - $this->packet_size = $this->ssh->packet_size_client_to_server[SSH2::CHANNEL_EXEC] - 4; - } - - $remote_file = basename($remote_file); - - if ($mode == self::SOURCE_STRING) { - $size = strlen($data); - } else { - if (!is_file($data)) { - user_error("$data is not a valid file", E_USER_NOTICE); - return false; - } - - $fp = @fopen($data, 'rb'); - if (!$fp) { - return false; - } - $size = filesize($data); - } - - $this->_send('C0644 ' . $size . ' ' . $remote_file . "\n"); - - $temp = $this->_receive(); - if ($temp !== chr(0)) { - return false; - } - - $sent = 0; - while ($sent < $size) { - $temp = $mode & self::SOURCE_STRING ? substr($data, $sent, $this->packet_size) : fread($fp, $this->packet_size); - $this->_send($temp); - $sent+= strlen($temp); - - if (is_callable($callback)) { - call_user_func($callback, $sent); - } - } - $this->_close(); - - if ($mode != self::SOURCE_STRING) { - fclose($fp); - } - - return true; - } - - /** - * Downloads a file from the SCP server. - * - * Returns a string containing the contents of $remote_file if $local_file is left undefined or a boolean false if - * the operation was unsuccessful. If $local_file is defined, returns true or false depending on the success of the - * operation - * - * @param string $remote_file - * @param string $local_file - * @return mixed - * @access public - */ - function get($remote_file, $local_file = false) - { - if (!isset($this->ssh)) { - return false; - } - - if (!$this->ssh->exec('scp -f ' . escapeshellarg($remote_file), false)) { // -f = from - return false; - } - - $this->_send("\0"); - - if (!preg_match('#(?[^ ]+) (?\d+) (?.+)#', rtrim($this->_receive()), $info)) { - return false; - } - - $this->_send("\0"); - - $size = 0; - - if ($local_file !== false) { - $fp = @fopen($local_file, 'wb'); - if (!$fp) { - return false; - } - } - - $content = ''; - while ($size < $info['size']) { - $data = $this->_receive(); - // SCP usually seems to split stuff out into 16k chunks - $size+= strlen($data); - - if ($local_file === false) { - $content.= $data; - } else { - fputs($fp, $data); - } - } - - $this->_close(); - - if ($local_file !== false) { - fclose($fp); - return true; - } - - return $content; - } - - /** - * Sends a packet to an SSH server - * - * @param string $data - * @access private - */ - function _send($data) - { - switch ($this->mode) { - case self::MODE_SSH2: - $this->ssh->_send_channel_packet(SSH2::CHANNEL_EXEC, $data); - break; - case self::MODE_SSH1: - $data = pack('CNa*', NET_SSH1_CMSG_STDIN_DATA, strlen($data), $data); - $this->ssh->_send_binary_packet($data); - } - } - - /** - * Receives a packet from an SSH server - * - * @return string - * @access private - */ - function _receive() - { - switch ($this->mode) { - case self::MODE_SSH2: - return $this->ssh->_get_channel_packet(SSH2::CHANNEL_EXEC, true); - case self::MODE_SSH1: - if (!$this->ssh->bitmap) { - return false; - } - while (true) { - $response = $this->ssh->_get_binary_packet(); - switch ($response[SSH1::RESPONSE_TYPE]) { - case NET_SSH1_SMSG_STDOUT_DATA: - if (strlen($response[SSH1::RESPONSE_DATA]) < 4) { - return false; - } - extract(unpack('Nlength', $response[SSH1::RESPONSE_DATA])); - return $this->ssh->_string_shift($response[SSH1::RESPONSE_DATA], $length); - case NET_SSH1_SMSG_STDERR_DATA: - break; - case NET_SSH1_SMSG_EXITSTATUS: - $this->ssh->_send_binary_packet(chr(NET_SSH1_CMSG_EXIT_CONFIRMATION)); - fclose($this->ssh->fsock); - $this->ssh->bitmap = 0; - return false; - default: - user_error('Unknown packet received', E_USER_NOTICE); - return false; - } - } - } - } - - /** - * Closes the connection to an SSH server - * - * @access private - */ - function _close() - { - switch ($this->mode) { - case self::MODE_SSH2: - $this->ssh->_close_channel(SSH2::CHANNEL_EXEC, true); - break; - case self::MODE_SSH1: - $this->ssh->disconnect(); - } - } -} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Net/SFTP.php b/vendor/phpseclib/phpseclib/phpseclib/Net/SFTP.php index 34741831..d130e074 100644 --- a/vendor/phpseclib/phpseclib/phpseclib/Net/SFTP.php +++ b/vendor/phpseclib/phpseclib/phpseclib/Net/SFTP.php @@ -5,9 +5,7 @@ * * PHP version 5 * - * Currently only supports SFTPv2 and v3, which, according to wikipedia.org, "is the most widely used version, - * implemented by the popular OpenSSH SFTP server". If you want SFTPv4/5/6 support, provide me with access - * to an SFTPv4/5/6 server. + * Supports SFTPv2/3/4/5/6. Defaults to v3. * * The API for this library is modeled after the API from PHP's {@link http://php.net/book.ftp FTP extension}. * @@ -16,7 +14,7 @@ * login('username', 'password')) { * exit('Login Failed'); * } @@ -27,63 +25,66 @@ * ?> * * - * @category Net - * @package SFTP * @author Jim Wigginton * @copyright 2009 Jim Wigginton * @license http://www.opensource.org/licenses/mit-license.html MIT License * @link http://phpseclib.sourceforge.net */ -namespace phpseclib\Net; +namespace phpseclib3\Net; + +use phpseclib3\Common\Functions\Strings; +use phpseclib3\Exception\FileNotFoundException; /** * Pure-PHP implementations of SFTP. * - * @package SFTP * @author Jim Wigginton - * @access public */ class SFTP extends SSH2 { /** * SFTP channel constant * - * \phpseclib\Net\SSH2::exec() uses 0 and \phpseclib\Net\SSH2::read() / \phpseclib\Net\SSH2::write() use 1. + * \phpseclib3\Net\SSH2::exec() uses 0 and \phpseclib3\Net\SSH2::read() / \phpseclib3\Net\SSH2::write() use 1. * - * @see \phpseclib\Net\SSH2::_send_channel_packet() - * @see \phpseclib\Net\SSH2::_get_channel_packet() - * @access private + * @see \phpseclib3\Net\SSH2::send_channel_packet() + * @see \phpseclib3\Net\SSH2::get_channel_packet() */ const CHANNEL = 0x100; - /**#@+ - * @access public - * @see \phpseclib\Net\SFTP::put() - */ /** * Reads data from a local file. + * + * @see \phpseclib3\Net\SFTP::put() */ const SOURCE_LOCAL_FILE = 1; /** * Reads data from a string. + * + * @see \phpseclib3\Net\SFTP::put() */ // this value isn't really used anymore but i'm keeping it reserved for historical reasons const SOURCE_STRING = 2; /** * Reads data from callback: * function callback($length) returns string to proceed, null for EOF + * + * @see \phpseclib3\Net\SFTP::put() */ const SOURCE_CALLBACK = 16; /** * Resumes an upload + * + * @see \phpseclib3\Net\SFTP::put() */ const RESUME = 4; /** * Append a local file to an already existing remote file + * + * @see \phpseclib3\Net\SFTP::put() */ const RESUME_START = 8; - /**#@-*/ /** * Packet Types @@ -92,7 +93,7 @@ class SFTP extends SSH2 * @var array * @access private */ - var $packet_types = array(); + private $packet_types = []; /** * Status Codes @@ -101,7 +102,19 @@ class SFTP extends SSH2 * @var array * @access private */ - var $status_codes = array(); + private $status_codes = []; + + /** @var array */ + private $attributes; + + /** @var array */ + private $open_flags; + + /** @var array */ + private $open_flags5; + + /** @var array */ + private $file_types; /** * The Request ID @@ -111,9 +124,8 @@ class SFTP extends SSH2 * * @var boolean * @see self::_send_sftp_packet() - * @access private */ - var $use_request_id = false; + private $use_request_id = false; /** * The Packet Type @@ -123,64 +135,106 @@ class SFTP extends SSH2 * * @var int * @see self::_get_sftp_packet() - * @access private */ - var $packet_type = -1; + private $packet_type = -1; /** * Packet Buffer * * @var string * @see self::_get_sftp_packet() - * @access private */ - var $packet_buffer = ''; + private $packet_buffer = ''; /** * Extensions supported by the server * * @var array * @see self::_initChannel() - * @access private */ - var $extensions = array(); + private $extensions = []; /** * Server SFTP version * * @var int * @see self::_initChannel() - * @access private */ - var $version; + private $version; + + /** + * Default Server SFTP version + * + * @var int + * @see self::_initChannel() + */ + private $defaultVersion; + + /** + * Preferred SFTP version + * + * @var int + * @see self::_initChannel() + */ + private $preferredVersion = 3; /** * Current working directory * - * @var string + * @var string|bool * @see self::realpath() * @see self::chdir() - * @access private */ - var $pwd = false; + private $pwd = false; /** * Packet Type Log * * @see self::getLog() * @var array - * @access private */ - var $packet_type_log = array(); + private $packet_type_log = []; /** * Packet Log * * @see self::getLog() * @var array - * @access private */ - var $packet_log = array(); + private $packet_log = []; + + /** + * Real-time log file pointer + * + * @see self::_append_log() + * @var resource|closed-resource + */ + private $realtime_log_file; + + /** + * Real-time log file size + * + * @see self::_append_log() + * @var int + */ + private $realtime_log_size; + + /** + * Real-time log file wrap boolean + * + * @see self::_append_log() + * @var bool + */ + private $realtime_log_wrap; + + /** + * Current log size + * + * Should never exceed self::LOG_MAX_SIZE + * + * @var int + */ + private $log_size; /** * Error information @@ -188,9 +242,8 @@ class SFTP extends SSH2 * @see self::getSFTPErrors() * @see self::getLastSFTPError() * @var array - * @access private */ - var $sftp_errors = array(); + private $sftp_errors = []; /** * Stat Cache @@ -202,19 +255,17 @@ class SFTP extends SSH2 * @see self::_remove_from_stat_cache() * @see self::_query_stat_cache() * @var array - * @access private */ - var $stat_cache = array(); + private $stat_cache = []; /** * Max SFTP Packet Size * * @see self::__construct() * @see self::get() - * @var array - * @access private + * @var int */ - var $max_sftp_packet; + private $max_sftp_packet; /** * Stat Cache Flag @@ -222,9 +273,8 @@ class SFTP extends SSH2 * @see self::disableStatCache() * @see self::enableStatCache() * @var bool - * @access private */ - var $use_stat_cache = true; + private $use_stat_cache = true; /** * Sort Options @@ -232,9 +282,8 @@ class SFTP extends SSH2 * @see self::_comparator() * @see self::setListOrder() * @var array - * @access private */ - var $sortOptions = array(); + protected $sortOptions = []; /** * Canonicalization Flag @@ -246,18 +295,16 @@ class SFTP extends SSH2 * @see self::disablePathCanonicalization() * @see self::realpath() * @var bool - * @access private */ - var $canonicalize_paths = true; + private $canonicalize_paths = true; /** * Request Buffers * * @see self::_get_sftp_packet() * @var array - * @access private */ - var $requestBuffer = array(); + private $requestBuffer = []; /** * Preserve timestamps on file downloads / uploads @@ -265,9 +312,38 @@ class SFTP extends SSH2 * @see self::get() * @see self::put() * @var bool - * @access private */ - var $preserveTime = false; + private $preserveTime = false; + + /** + * Arbitrary Length Packets Flag + * + * Determines whether or not packets of any length should be allowed, + * in cases where the server chooses the packet length (such as + * directory listings). By default, packets are only allowed to be + * 256 * 1024 bytes (SFTP_MAX_MSG_LENGTH from OpenSSH's sftp-common.h) + * + * @see self::enableArbitraryLengthPackets() + * @see self::_get_sftp_packet() + * @var bool + */ + private $allow_arbitrary_length_packets = false; + + /** + * Was the last packet due to the channels being closed or not? + * + * @see self::get() + * @see self::get_sftp_packet() + * @var bool + */ + private $channel_close = false; + + /** + * Has the SFTP channel been partially negotiated? + * + * @var bool + */ + private $partial_init = false; /** * Default Constructor. @@ -277,27 +353,23 @@ class SFTP extends SSH2 * @param string $host * @param int $port * @param int $timeout - * @return \phpseclib\Net\SFTP - * @access public */ - function __construct($host, $port = 22, $timeout = 10) + public function __construct($host, $port = 22, $timeout = 10) { parent::__construct($host, $port, $timeout); $this->max_sftp_packet = 1 << 15; - $this->packet_types = array( + $this->packet_types = [ 1 => 'NET_SFTP_INIT', 2 => 'NET_SFTP_VERSION', - /* the format of SSH_FXP_OPEN changed between SFTPv4 and SFTPv5+: - SFTPv5+: http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.1.1 - pre-SFTPv5 : http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-6.3 */ 3 => 'NET_SFTP_OPEN', 4 => 'NET_SFTP_CLOSE', 5 => 'NET_SFTP_READ', 6 => 'NET_SFTP_WRITE', 7 => 'NET_SFTP_LSTAT', 9 => 'NET_SFTP_SETSTAT', + 10 => 'NET_SFTP_FSETSTAT', 11 => 'NET_SFTP_OPENDIR', 12 => 'NET_SFTP_READDIR', 13 => 'NET_SFTP_REMOVE', @@ -305,25 +377,20 @@ function __construct($host, $port = 22, $timeout = 10) 15 => 'NET_SFTP_RMDIR', 16 => 'NET_SFTP_REALPATH', 17 => 'NET_SFTP_STAT', - /* the format of SSH_FXP_RENAME changed between SFTPv4 and SFTPv5+: - SFTPv5+: http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.3 - pre-SFTPv5 : http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-6.5 */ 18 => 'NET_SFTP_RENAME', 19 => 'NET_SFTP_READLINK', 20 => 'NET_SFTP_SYMLINK', + 21 => 'NET_SFTP_LINK', - 101=> 'NET_SFTP_STATUS', - 102=> 'NET_SFTP_HANDLE', - /* the format of SSH_FXP_NAME changed between SFTPv3 and SFTPv4+: - SFTPv4+: http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-9.4 - pre-SFTPv4 : http://tools.ietf.org/html/draft-ietf-secsh-filexfer-02#section-7 */ - 103=> 'NET_SFTP_DATA', - 104=> 'NET_SFTP_NAME', - 105=> 'NET_SFTP_ATTRS', + 101 => 'NET_SFTP_STATUS', + 102 => 'NET_SFTP_HANDLE', + 103 => 'NET_SFTP_DATA', + 104 => 'NET_SFTP_NAME', + 105 => 'NET_SFTP_ATTRS', - 200=> 'NET_SFTP_EXTENDED' - ); - $this->status_codes = array( + 200 => 'NET_SFTP_EXTENDED' + ]; + $this->status_codes = [ 0 => 'NET_SFTP_STATUS_OK', 1 => 'NET_SFTP_STATUS_EOF', 2 => 'NET_SFTP_STATUS_NO_SUCH_FILE', @@ -356,51 +423,89 @@ function __construct($host, $port = 22, $timeout = 10) 29 => 'NET_SFTP_STATUS_OWNER_INVALID', 30 => 'NET_SFTP_STATUS_GROUP_INVALID', 31 => 'NET_SFTP_STATUS_NO_MATCHING_BYTE_RANGE_LOCK' - ); + ]; // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-7.1 - // the order, in this case, matters quite a lot - see \phpseclib\Net\SFTP::_parseAttributes() to understand why - $this->attributes = array( + // the order, in this case, matters quite a lot - see \phpseclib3\Net\SFTP::_parseAttributes() to understand why + $this->attributes = [ 0x00000001 => 'NET_SFTP_ATTR_SIZE', - 0x00000002 => 'NET_SFTP_ATTR_UIDGID', // defined in SFTPv3, removed in SFTPv4+ + 0x00000002 => 'NET_SFTP_ATTR_UIDGID', // defined in SFTPv3, removed in SFTPv4+ + 0x00000080 => 'NET_SFTP_ATTR_OWNERGROUP', // defined in SFTPv4+ 0x00000004 => 'NET_SFTP_ATTR_PERMISSIONS', 0x00000008 => 'NET_SFTP_ATTR_ACCESSTIME', + 0x00000010 => 'NET_SFTP_ATTR_CREATETIME', // SFTPv4+ + 0x00000020 => 'NET_SFTP_ATTR_MODIFYTIME', + 0x00000040 => 'NET_SFTP_ATTR_ACL', + 0x00000100 => 'NET_SFTP_ATTR_SUBSECOND_TIMES', + 0x00000200 => 'NET_SFTP_ATTR_BITS', // SFTPv5+ + 0x00000400 => 'NET_SFTP_ATTR_ALLOCATION_SIZE', // SFTPv6+ + 0x00000800 => 'NET_SFTP_ATTR_TEXT_HINT', + 0x00001000 => 'NET_SFTP_ATTR_MIME_TYPE', + 0x00002000 => 'NET_SFTP_ATTR_LINK_COUNT', + 0x00004000 => 'NET_SFTP_ATTR_UNTRANSLATED_NAME', + 0x00008000 => 'NET_SFTP_ATTR_CTIME', // 0x80000000 will yield a floating point on 32-bit systems and converting floating points to integers // yields inconsistent behavior depending on how php is compiled. so we left shift -1 (which, in // two's compliment, consists of all 1 bits) by 31. on 64-bit systems this'll yield 0xFFFFFFFF80000000. // that's not a problem, however, and 'anded' and a 32-bit number, as all the leading 1 bits are ignored. (-1 << 31) & 0xFFFFFFFF => 'NET_SFTP_ATTR_EXTENDED' - ); + ]; // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-6.3 // the flag definitions change somewhat in SFTPv5+. if SFTPv5+ support is added to this library, maybe name // the array for that $this->open5_flags and similarly alter the constant names. - $this->open_flags = array( + $this->open_flags = [ 0x00000001 => 'NET_SFTP_OPEN_READ', 0x00000002 => 'NET_SFTP_OPEN_WRITE', 0x00000004 => 'NET_SFTP_OPEN_APPEND', 0x00000008 => 'NET_SFTP_OPEN_CREATE', 0x00000010 => 'NET_SFTP_OPEN_TRUNCATE', - 0x00000020 => 'NET_SFTP_OPEN_EXCL' - ); + 0x00000020 => 'NET_SFTP_OPEN_EXCL', + 0x00000040 => 'NET_SFTP_OPEN_TEXT' // defined in SFTPv4 + ]; + // SFTPv5+ changed the flags up: + // https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-13#section-8.1.1.3 + $this->open_flags5 = [ + // when SSH_FXF_ACCESS_DISPOSITION is a 3 bit field that controls how the file is opened + 0x00000000 => 'NET_SFTP_OPEN_CREATE_NEW', + 0x00000001 => 'NET_SFTP_OPEN_CREATE_TRUNCATE', + 0x00000002 => 'NET_SFTP_OPEN_OPEN_EXISTING', + 0x00000003 => 'NET_SFTP_OPEN_OPEN_OR_CREATE', + 0x00000004 => 'NET_SFTP_OPEN_TRUNCATE_EXISTING', + // the rest of the flags are not supported + 0x00000008 => 'NET_SFTP_OPEN_APPEND_DATA', // "the offset field of SS_FXP_WRITE requests is ignored" + 0x00000010 => 'NET_SFTP_OPEN_APPEND_DATA_ATOMIC', + 0x00000020 => 'NET_SFTP_OPEN_TEXT_MODE', + 0x00000040 => 'NET_SFTP_OPEN_BLOCK_READ', + 0x00000080 => 'NET_SFTP_OPEN_BLOCK_WRITE', + 0x00000100 => 'NET_SFTP_OPEN_BLOCK_DELETE', + 0x00000200 => 'NET_SFTP_OPEN_BLOCK_ADVISORY', + 0x00000400 => 'NET_SFTP_OPEN_NOFOLLOW', + 0x00000800 => 'NET_SFTP_OPEN_DELETE_ON_CLOSE', + 0x00001000 => 'NET_SFTP_OPEN_ACCESS_AUDIT_ALARM_INFO', + 0x00002000 => 'NET_SFTP_OPEN_ACCESS_BACKUP', + 0x00004000 => 'NET_SFTP_OPEN_BACKUP_STREAM', + 0x00008000 => 'NET_SFTP_OPEN_OVERRIDE_OWNER', + ]; // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-5.2 - // see \phpseclib\Net\SFTP::_parseLongname() for an explanation - $this->file_types = array( + // see \phpseclib3\Net\SFTP::_parseLongname() for an explanation + $this->file_types = [ 1 => 'NET_SFTP_TYPE_REGULAR', 2 => 'NET_SFTP_TYPE_DIRECTORY', 3 => 'NET_SFTP_TYPE_SYMLINK', 4 => 'NET_SFTP_TYPE_SPECIAL', 5 => 'NET_SFTP_TYPE_UNKNOWN', - // the followin types were first defined for use in SFTPv5+ + // the following types were first defined for use in SFTPv5+ // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-05#section-5.2 6 => 'NET_SFTP_TYPE_SOCKET', 7 => 'NET_SFTP_TYPE_CHAR_DEVICE', 8 => 'NET_SFTP_TYPE_BLOCK_DEVICE', 9 => 'NET_SFTP_TYPE_FIFO' - ); - $this->_define_array( + ]; + $this->define_array( $this->packet_types, $this->status_codes, $this->attributes, $this->open_flags, + $this->open_flags5, $this->file_types ); @@ -413,58 +518,64 @@ function __construct($host, $port = 22, $timeout = 10) } /** - * Login + * Check a few things before SFTP functions are called * - * @param string $username * @return bool - * @access public */ - function login($username) + private function precheck() { - if (!call_user_func_array('parent::login', func_get_args())) { + if (!($this->bitmap & SSH2::MASK_LOGIN)) { return false; } + if ($this->pwd === false) { + return $this->init_sftp_connection(); + } + + return true; + } + + /** + * Partially initialize an SFTP connection + * + * @throws \UnexpectedValueException on receipt of unexpected packets + * @return bool + */ + private function partial_init_sftp_connection() + { $this->window_size_server_to_client[self::CHANNEL] = $this->window_size; - $packet = pack( - 'CNa*N3', + $packet = Strings::packSSH2( + 'CsN3', NET_SSH2_MSG_CHANNEL_OPEN, - strlen('session'), 'session', self::CHANNEL, $this->window_size, 0x4000 ); - if (!$this->_send_binary_packet($packet)) { - return false; - } + $this->send_binary_packet($packet); $this->channel_status[self::CHANNEL] = NET_SSH2_MSG_CHANNEL_OPEN; - $response = $this->_get_channel_packet(self::CHANNEL, true); - if ($response === false) { + $response = $this->get_channel_packet(self::CHANNEL, true); + if ($response === true && $this->isTimeout()) { return false; } - $packet = pack( - 'CNNa*CNa*', + $packet = Strings::packSSH2( + 'CNsbs', NET_SSH2_MSG_CHANNEL_REQUEST, $this->server_channels[self::CHANNEL], - strlen('subsystem'), 'subsystem', - 1, - strlen('sftp'), + true, 'sftp' ); - if (!$this->_send_binary_packet($packet)) { - return false; - } + $this->send_binary_packet($packet); $this->channel_status[self::CHANNEL] = NET_SSH2_MSG_CHANNEL_REQUEST; - $response = $this->_get_channel_packet(self::CHANNEL, true); + $response = $this->get_channel_packet(self::CHANNEL, true); if ($response === false) { // from PuTTY's psftp.exe $command = "test -x /usr/lib/sftp-server && exec /usr/lib/sftp-server\n" . @@ -472,74 +583,58 @@ function login($username) "exec sftp-server"; // we don't do $this->exec($command, false) because exec() operates on a different channel and plus the SSH_MSG_CHANNEL_OPEN that exec() does // is redundant - $packet = pack( - 'CNNa*CNa*', + $packet = Strings::packSSH2( + 'CNsCs', NET_SSH2_MSG_CHANNEL_REQUEST, $this->server_channels[self::CHANNEL], - strlen('exec'), 'exec', 1, - strlen($command), $command ); - if (!$this->_send_binary_packet($packet)) { - return false; - } + $this->send_binary_packet($packet); $this->channel_status[self::CHANNEL] = NET_SSH2_MSG_CHANNEL_REQUEST; - $response = $this->_get_channel_packet(self::CHANNEL, true); + $response = $this->get_channel_packet(self::CHANNEL, true); if ($response === false) { return false; } + } elseif ($response === true && $this->isTimeout()) { + return false; } $this->channel_status[self::CHANNEL] = NET_SSH2_MSG_CHANNEL_DATA; + $this->send_sftp_packet(NET_SFTP_INIT, "\0\0\0\3"); - if (!$this->_send_sftp_packet(NET_SFTP_INIT, "\0\0\0\3")) { - return false; - } - - $response = $this->_get_sftp_packet(); + $response = $this->get_sftp_packet(); if ($this->packet_type != NET_SFTP_VERSION) { - user_error('Expected SSH_FXP_VERSION'); - return false; + throw new \UnexpectedValueException('Expected NET_SFTP_VERSION. ' + . 'Got packet type: ' . $this->packet_type); } - if (strlen($response) < 4) { - return false; - } - extract(unpack('Nversion', $this->_string_shift($response, 4))); - $this->version = $version; + $this->use_request_id = true; + + list($this->defaultVersion) = Strings::unpackSSH2('N', $response); while (!empty($response)) { - if (strlen($response) < 4) { - return false; - } - extract(unpack('Nlength', $this->_string_shift($response, 4))); - $key = $this->_string_shift($response, $length); - if (strlen($response) < 4) { - return false; - } - extract(unpack('Nlength', $this->_string_shift($response, 4))); - $value = $this->_string_shift($response, $length); + list($key, $value) = Strings::unpackSSH2('ss', $response); $this->extensions[$key] = $value; } - /* - SFTPv4+ defines a 'newline' extension. SFTPv3 seems to have unofficial support for it via 'newline@vandyke.com', - however, I'm not sure what 'newline@vandyke.com' is supposed to do (the fact that it's unofficial means that it's - not in the official SFTPv3 specs) and 'newline@vandyke.com' / 'newline' are likely not drop-in substitutes for - one another due to the fact that 'newline' comes with a SSH_FXF_TEXT bitmask whereas it seems unlikely that - 'newline@vandyke.com' would. - */ - /* - if (isset($this->extensions['newline@vandyke.com'])) { - $this->extensions['newline'] = $this->extensions['newline@vandyke.com']; - unset($this->extensions['newline@vandyke.com']); - } - */ + $this->partial_init = true; - $this->use_request_id = true; + return true; + } + + /** + * (Re)initializes the SFTP channel + * + * @return bool + */ + private function init_sftp_connection() + { + if (!$this->partial_init && !$this->partial_init_sftp_connection()) { + return false; + } /* A Note on SFTPv4/5/6 support: @@ -561,20 +656,70 @@ function login($username) So what do you do if you have a client whose initial SSH_FXP_INIT packet says it implements v3 and a server whose initial SSH_FXP_VERSION reply says it implements v4 and only v4? If it only implements v4, the "versions" extension is likely not going to have been sent so version re-negotiation as discussed - in draft-ietf-secsh-filexfer-13 would be quite impossible. As such, what \phpseclib\Net\SFTP would do is close the + in draft-ietf-secsh-filexfer-13 would be quite impossible. As such, what \phpseclib3\Net\SFTP would do is close the channel and reopen it with a new and updated SSH_FXP_INIT packet. */ - switch ($this->version) { - case 2: - case 3: - break; - default: - return false; + $this->version = $this->defaultVersion; + if (isset($this->extensions['versions']) && (!$this->preferredVersion || $this->preferredVersion != $this->version)) { + $versions = explode(',', $this->extensions['versions']); + $supported = [6, 5, 4]; + if ($this->preferredVersion) { + $supported = array_diff($supported, [$this->preferredVersion]); + array_unshift($supported, $this->preferredVersion); + } + foreach ($supported as $ver) { + if (in_array($ver, $versions)) { + if ($ver === $this->version) { + break; + } + $this->version = (int) $ver; + $packet = Strings::packSSH2('ss', 'version-select', "$ver"); + $this->send_sftp_packet(NET_SFTP_EXTENDED, $packet); + $response = $this->get_sftp_packet(); + if ($this->packet_type != NET_SFTP_STATUS) { + throw new \UnexpectedValueException('Expected NET_SFTP_STATUS. ' + . 'Got packet type: ' . $this->packet_type); + } + list($status) = Strings::unpackSSH2('N', $response); + if ($status != NET_SFTP_STATUS_OK) { + $this->logError($response, $status); + throw new \UnexpectedValueException('Expected NET_SFTP_STATUS_OK. ' + . ' Got ' . $status); + } + break; + } + } } - $this->pwd = $this->_realpath('.'); + /* + SFTPv4+ defines a 'newline' extension. SFTPv3 seems to have unofficial support for it via 'newline@vandyke.com', + however, I'm not sure what 'newline@vandyke.com' is supposed to do (the fact that it's unofficial means that it's + not in the official SFTPv3 specs) and 'newline@vandyke.com' / 'newline' are likely not drop-in substitutes for + one another due to the fact that 'newline' comes with a SSH_FXF_TEXT bitmask whereas it seems unlikely that + 'newline@vandyke.com' would. + */ + /* + if (isset($this->extensions['newline@vandyke.com'])) { + $this->extensions['newline'] = $this->extensions['newline@vandyke.com']; + unset($this->extensions['newline@vandyke.com']); + } + */ + if ($this->version < 2 || $this->version > 6) { + return false; + } + + $this->pwd = true; + try { + $this->pwd = $this->realpath('.'); + } catch (\UnexpectedValueException $e) { + if (!$this->canonicalize_paths) { + throw $e; + } + $this->$this->canonicalize_paths = false; + $this->reset_connection(NET_SSH2_DISCONNECT_CONNECTION_LOST); + } - $this->_update_stat_cache($this->pwd, array()); + $this->update_stat_cache($this->pwd, []); return true; } @@ -582,9 +727,8 @@ function login($username) /** * Disable the stat cache * - * @access public */ - function disableStatCache() + public function disableStatCache() { $this->use_stat_cache = false; } @@ -592,9 +736,8 @@ function disableStatCache() /** * Enable the stat cache * - * @access public */ - function enableStatCache() + public function enableStatCache() { $this->use_stat_cache = true; } @@ -602,41 +745,61 @@ function enableStatCache() /** * Clear the stat cache * - * @access public */ - function clearStatCache() + public function clearStatCache() { - $this->stat_cache = array(); + $this->stat_cache = []; } /** * Enable path canonicalization * - * @access public */ - function enablePathCanonicalization() + public function enablePathCanonicalization() { $this->canonicalize_paths = true; } /** - * Enable path canonicalization + * Disable path canonicalization + * + * If this is enabled then $sftp->pwd() will not return the canonicalized absolute path * - * @access public */ - function disablePathCanonicalization() + public function disablePathCanonicalization() { $this->canonicalize_paths = false; } + /** + * Enable arbitrary length packets + * + */ + public function enableArbitraryLengthPackets() + { + $this->allow_arbitrary_length_packets = true; + } + + /** + * Disable arbitrary length packets + * + */ + public function disableArbitraryLengthPackets() + { + $this->allow_arbitrary_length_packets = false; + } + /** * Returns the current directory name * - * @return mixed - * @access public + * @return string|bool */ - function pwd() + public function pwd() { + if (!$this->precheck()) { + return false; + } + return $this->pwd; } @@ -645,42 +808,23 @@ function pwd() * * @param string $response * @param int $status - * @access public */ - function _logError($response, $status = -1) + private function logError($response, $status = -1) { if ($status == -1) { - if (strlen($response) < 4) { - return; - } - extract(unpack('Nstatus', $this->_string_shift($response, 4))); + list($status) = Strings::unpackSSH2('N', $response); } $error = $this->status_codes[$status]; - if ($this->version > 2 || strlen($response) < 4) { - extract(unpack('Nlength', $this->_string_shift($response, 4))); - $this->sftp_errors[] = $error . ': ' . $this->_string_shift($response, $length); + if ($this->version > 2) { + list($message) = Strings::unpackSSH2('s', $response); + $this->sftp_errors[] = "$error: $message"; } else { $this->sftp_errors[] = $error; } } - /** - * Returns canonicalized absolute pathname - * - * realpath() expands all symbolic links and resolves references to '/./', '/../' and extra '/' characters in the input - * path and returns the canonicalized absolute pathname. - * - * @param string $path - * @return mixed - * @access public - */ - function realpath($path) - { - return $this->_realpath($path); - } - /** * Canonicalize the Server-Side Path Name * @@ -692,39 +836,62 @@ function realpath($path) * @see self::chdir() * @see self::disablePathCanonicalization() * @param string $path + * @throws \UnexpectedValueException on receipt of unexpected packets * @return mixed - * @access private */ - function _realpath($path) + public function realpath($path) { + if ($this->precheck() === false) { + return false; + } + if (!$this->canonicalize_paths) { - return $path; + if ($this->pwd === true) { + return '.'; + } + if (!strlen($path) || $path[0] != '/') { + $path = $this->pwd . '/' . $path; + } + $parts = explode('/', $path); + $afterPWD = $beforePWD = []; + foreach ($parts as $part) { + switch ($part) { + //case '': // some SFTP servers /require/ double /'s. see https://github.com/phpseclib/phpseclib/pull/1137 + case '.': + break; + case '..': + if (!empty($afterPWD)) { + array_pop($afterPWD); + } else { + $beforePWD[] = '..'; + } + break; + default: + $afterPWD[] = $part; + } + } + $beforePWD = count($beforePWD) ? implode('/', $beforePWD) : '.'; + return $beforePWD . '/' . implode('/', $afterPWD); } - if ($this->pwd === false) { + if ($this->pwd === true) { // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.9 - if (!$this->_send_sftp_packet(NET_SFTP_REALPATH, pack('Na*', strlen($path), $path))) { - return false; - } + $this->send_sftp_packet(NET_SFTP_REALPATH, Strings::packSSH2('s', $path)); - $response = $this->_get_sftp_packet(); + $response = $this->get_sftp_packet(); switch ($this->packet_type) { case NET_SFTP_NAME: // although SSH_FXP_NAME is implemented differently in SFTPv3 than it is in SFTPv4+, the following // should work on all SFTP versions since the only part of the SSH_FXP_NAME packet the following looks // at is the first part and that part is defined the same in SFTP versions 3 through 6. - $this->_string_shift($response, 4); // skip over the count - it should be 1, anyway - if (strlen($response) < 4) { - return false; - } - extract(unpack('Nlength', $this->_string_shift($response, 4))); - return $this->_string_shift($response, $length); + list(, $filename) = Strings::unpackSSH2('Ns', $response); + return $filename; case NET_SFTP_STATUS: - $this->_logError($response); + $this->logError($response); return false; default: - user_error('Expected SSH_FXP_NAME or SSH_FXP_STATUS'); - return false; + throw new \UnexpectedValueException('Expected NET_SFTP_NAME or NET_SFTP_STATUS. ' + . 'Got packet type: ' . $this->packet_type); } } @@ -733,7 +900,7 @@ function _realpath($path) } $path = explode('/', $path); - $new = array(); + $new = []; foreach ($path as $dir) { if (!strlen($dir)) { continue; @@ -741,6 +908,7 @@ function _realpath($path) switch ($dir) { case '..': array_pop($new); + // fall-through case '.': break; default: @@ -755,12 +923,12 @@ function _realpath($path) * Changes the current directory * * @param string $dir + * @throws \UnexpectedValueException on receipt of unexpected packets * @return bool - * @access public */ - function chdir($dir) + public function chdir($dir) { - if (!($this->bitmap & SSH2::MASK_LOGIN)) { + if (!$this->precheck()) { return false; } @@ -769,13 +937,13 @@ function chdir($dir) $dir = './'; // suffix a slash if needed } elseif ($dir[strlen($dir) - 1] != '/') { - $dir.= '/'; + $dir .= '/'; } - $dir = $this->_realpath($dir); + $dir = $this->realpath($dir); // confirm that $dir is, in fact, a valid directory - if ($this->use_stat_cache && is_array($this->_query_stat_cache($dir))) { + if ($this->use_stat_cache && is_array($this->query_stat_cache($dir))) { $this->pwd = $dir; return true; } @@ -785,29 +953,27 @@ function chdir($dir) // the file's uid / gid match the currently logged in user's uid / gid but how there's no easy // way to get those with SFTP - if (!$this->_send_sftp_packet(NET_SFTP_OPENDIR, pack('Na*', strlen($dir), $dir))) { - return false; - } + $this->send_sftp_packet(NET_SFTP_OPENDIR, Strings::packSSH2('s', $dir)); - // see \phpseclib\Net\SFTP::nlist() for a more thorough explanation of the following - $response = $this->_get_sftp_packet(); + // see \phpseclib3\Net\SFTP::nlist() for a more thorough explanation of the following + $response = $this->get_sftp_packet(); switch ($this->packet_type) { case NET_SFTP_HANDLE: $handle = substr($response, 4); break; case NET_SFTP_STATUS: - $this->_logError($response); + $this->logError($response); return false; default: - user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS'); - return false; + throw new \UnexpectedValueException('Expected NET_SFTP_HANDLE or NET_SFTP_STATUS' . + 'Got packet type: ' . $this->packet_type); } - if (!$this->_close_handle($handle)) { + if (!$this->close_handle($handle)) { return false; } - $this->_update_stat_cache($dir, array()); + $this->update_stat_cache($dir, []); $this->pwd = $dir; return true; @@ -818,12 +984,11 @@ function chdir($dir) * * @param string $dir * @param bool $recursive - * @return mixed - * @access public + * @return array|false */ - function nlist($dir = '.', $recursive = false) + public function nlist($dir = '.', $recursive = false) { - return $this->_nlist_helper($dir, $recursive, ''); + return $this->nlist_helper($dir, $recursive, ''); } /** @@ -832,28 +997,25 @@ function nlist($dir = '.', $recursive = false) * @param string $dir * @param bool $recursive * @param string $relativeDir - * @return mixed - * @access private + * @return array|false */ - function _nlist_helper($dir, $recursive, $relativeDir) + private function nlist_helper($dir, $recursive, $relativeDir) { - $files = $this->_list($dir, false); + $files = $this->readlist($dir, false); if (!$recursive || $files === false) { return $files; } - $result = array(); + $result = []; foreach ($files as $value) { if ($value == '.' || $value == '..') { - if ($relativeDir == '') { - $result[] = $value; - } + $result[] = $relativeDir . $value; continue; } - if (is_array($this->_query_stat_cache($this->_realpath($dir . '/' . $value)))) { - $temp = $this->_nlist_helper($dir . '/' . $value, true, $relativeDir . $value . '/'); - $temp = is_array($temp) ? $temp : array(); + if (is_array($this->query_stat_cache($this->realpath($dir . '/' . $value)))) { + $temp = $this->nlist_helper($dir . '/' . $value, true, $relativeDir . $value . '/'); + $temp = is_array($temp) ? $temp : []; $result = array_merge($result, $temp); } else { $result[] = $relativeDir . $value; @@ -868,12 +1030,11 @@ function _nlist_helper($dir, $recursive, $relativeDir) * * @param string $dir * @param bool $recursive - * @return mixed - * @access public + * @return array|false */ - function rawlist($dir = '.', $recursive = false) + public function rawlist($dir = '.', $recursive = false) { - $files = $this->_list($dir, true); + $files = $this->readlist($dir, true); if (!$recursive || $files === false) { return $files; } @@ -888,7 +1049,7 @@ function rawlist($dir = '.', $recursive = false) $is_directory = false; if ($key != '.' && $key != '..') { if ($this->use_stat_cache) { - $is_directory = is_array($this->_query_stat_cache($this->_realpath($dir . '/' . $key))); + $is_directory = is_array($this->query_stat_cache($this->realpath($dir . '/' . $key))); } else { $stat = $this->lstat($dir . '/' . $key); $is_directory = $stat && $stat['type'] === NET_SFTP_TYPE_DIRECTORY; @@ -912,26 +1073,24 @@ function rawlist($dir = '.', $recursive = false) * * @param string $dir * @param bool $raw - * @return mixed - * @access private + * @return array|false + * @throws \UnexpectedValueException on receipt of unexpected packets */ - function _list($dir, $raw = true) + private function readlist($dir, $raw = true) { - if (!($this->bitmap & SSH2::MASK_LOGIN)) { + if (!$this->precheck()) { return false; } - $dir = $this->_realpath($dir . '/'); + $dir = $this->realpath($dir . '/'); if ($dir === false) { return false; } // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.1.2 - if (!$this->_send_sftp_packet(NET_SFTP_OPENDIR, pack('Na*', strlen($dir), $dir))) { - return false; - } + $this->send_sftp_packet(NET_SFTP_OPENDIR, Strings::packSSH2('s', $dir)); - $response = $this->_get_sftp_packet(); + $response = $this->get_sftp_packet(); switch ($this->packet_type) { case NET_SFTP_HANDLE: // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-9.2 @@ -941,87 +1100,75 @@ function _list($dir, $raw = true) break; case NET_SFTP_STATUS: // presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED - $this->_logError($response); + $this->logError($response); return false; default: - user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS'); - return false; + throw new \UnexpectedValueException('Expected NET_SFTP_HANDLE or NET_SFTP_STATUS. ' + . 'Got packet type: ' . $this->packet_type); } - $this->_update_stat_cache($dir, array()); + $this->update_stat_cache($dir, []); - $contents = array(); + $contents = []; while (true) { // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.2.2 // why multiple SSH_FXP_READDIR packets would be sent when the response to a single one can span arbitrarily many // SSH_MSG_CHANNEL_DATA messages is not known to me. - if (!$this->_send_sftp_packet(NET_SFTP_READDIR, pack('Na*', strlen($handle), $handle))) { - return false; - } + $this->send_sftp_packet(NET_SFTP_READDIR, Strings::packSSH2('s', $handle)); - $response = $this->_get_sftp_packet(); + $response = $this->get_sftp_packet(); switch ($this->packet_type) { case NET_SFTP_NAME: - if (strlen($response) < 4) { - return false; - } - extract(unpack('Ncount', $this->_string_shift($response, 4))); + list($count) = Strings::unpackSSH2('N', $response); for ($i = 0; $i < $count; $i++) { - if (strlen($response) < 4) { - return false; - } - extract(unpack('Nlength', $this->_string_shift($response, 4))); - $shortname = $this->_string_shift($response, $length); - if (strlen($response) < 4) { - return false; + list($shortname) = Strings::unpackSSH2('s', $response); + // SFTPv4 "removed the long filename from the names structure-- it can now be + // built from information available in the attrs structure." + if ($this->version < 4) { + list($longname) = Strings::unpackSSH2('s', $response); } - extract(unpack('Nlength', $this->_string_shift($response, 4))); - $longname = $this->_string_shift($response, $length); - $attributes = $this->_parseAttributes($response); - if (!isset($attributes['type'])) { - $fileType = $this->_parseLongname($longname); + $attributes = $this->parseAttributes($response); + if (!isset($attributes['type']) && $this->version < 4) { + $fileType = $this->parseLongname($longname); if ($fileType) { $attributes['type'] = $fileType; } } - $contents[$shortname] = $attributes + array('filename' => $shortname); + $contents[$shortname] = $attributes + ['filename' => $shortname]; if (isset($attributes['type']) && $attributes['type'] == NET_SFTP_TYPE_DIRECTORY && ($shortname != '.' && $shortname != '..')) { - $this->_update_stat_cache($dir . '/' . $shortname, array()); + $this->update_stat_cache($dir . '/' . $shortname, []); } else { if ($shortname == '..') { - $temp = $this->_realpath($dir . '/..') . '/.'; + $temp = $this->realpath($dir . '/..') . '/.'; } else { $temp = $dir . '/' . $shortname; } - $this->_update_stat_cache($temp, (object) array('lstat' => $attributes)); + $this->update_stat_cache($temp, (object) ['lstat' => $attributes]); } // SFTPv6 has an optional boolean end-of-list field, but we'll ignore that, since the // final SSH_FXP_STATUS packet should tell us that, already. } break; case NET_SFTP_STATUS: - if (strlen($response) < 4) { - return false; - } - extract(unpack('Nstatus', $this->_string_shift($response, 4))); + list($status) = Strings::unpackSSH2('N', $response); if ($status != NET_SFTP_STATUS_EOF) { - $this->_logError($response, $status); + $this->logError($response, $status); return false; } break 2; default: - user_error('Expected SSH_FXP_NAME or SSH_FXP_STATUS'); - return false; + throw new \UnexpectedValueException('Expected NET_SFTP_NAME or NET_SFTP_STATUS. ' + . 'Got packet type: ' . $this->packet_type); } } - if (!$this->_close_handle($handle)) { + if (!$this->close_handle($handle)) { return false; } if (count($this->sortOptions)) { - uasort($contents, array(&$this, '_comparator')); + uasort($contents, [&$this, 'comparator']); } return $raw ? $contents : array_map('strval', array_keys($contents)); @@ -1035,9 +1182,8 @@ function _list($dir, $raw = true) * @param array $a * @param array $b * @return int - * @access private */ - function _comparator($a, $b) + private function comparator(array $a, array $b) { switch (true) { case $a['filename'] === '.' || $b['filename'] === '.': @@ -1078,10 +1224,10 @@ function _comparator($a, $b) return $order === SORT_DESC ? -$result : $result; } break; - case 'permissions': case 'mode': - $a[$sort]&= 07777; - $b[$sort]&= 07777; + $a[$sort] &= 07777; + $b[$sort] &= 07777; + // fall-through default: if ($a[$sort] === $b[$sort]) { break; @@ -1109,44 +1255,21 @@ function _comparator($a, $b) * $sftp->setListOrder(); * Don't do any sort of sorting * - * @access public + * @param string ...$args */ - function setListOrder() + public function setListOrder(...$args) { - $this->sortOptions = array(); - $args = func_get_args(); + $this->sortOptions = []; if (empty($args)) { return; } $len = count($args) & 0x7FFFFFFE; - for ($i = 0; $i < $len; $i+=2) { + for ($i = 0; $i < $len; $i += 2) { $this->sortOptions[$args[$i]] = $args[$i + 1]; } if (!count($this->sortOptions)) { - $this->sortOptions = array('bogus' => true); - } - } - - /** - * Returns the file size, in bytes, or false, on failure - * - * Files larger than 4GB will show up as being exactly 4GB. - * - * @param string $filename - * @return mixed - * @access public - */ - function size($filename) - { - if (!($this->bitmap & SSH2::MASK_LOGIN)) { - return false; + $this->sortOptions = ['bogus' => true]; } - - $result = $this->stat($filename); - if ($result === false) { - return false; - } - return isset($result['size']) ? $result['size'] : -1; } /** @@ -1154,9 +1277,8 @@ function size($filename) * * @param string $path * @param mixed $value - * @access private */ - function _update_stat_cache($path, $value) + private function update_stat_cache($path, $value) { if ($this->use_stat_cache === false) { return; @@ -1172,10 +1294,10 @@ function _update_stat_cache($path, $value) // 1. a file was deleted and changed to a directory behind phpseclib's back // 2. it's a symlink. when lstat is done it's unclear what it's a symlink to if (is_object($temp)) { - $temp = array(); + $temp = []; } if (!isset($temp[$dir])) { - $temp[$dir] = array(); + $temp[$dir] = []; } if ($i === $max) { if (is_object($temp[$dir]) && is_object($value)) { @@ -1198,9 +1320,8 @@ function _update_stat_cache($path, $value) * * @param string $path * @return bool - * @access private */ - function _remove_from_stat_cache($path) + private function remove_from_stat_cache($path) { $dirs = explode('/', preg_replace('#^/|/(?=/)|/$#', '', $path)); @@ -1228,9 +1349,8 @@ function _remove_from_stat_cache($path) * * @param string $path * @return mixed - * @access private */ - function _query_stat_cache($path) + private function query_stat_cache($path) { $dirs = explode('/', preg_replace('#^/|/(?=/)|/$#', '', $path)); @@ -1253,22 +1373,21 @@ function _query_stat_cache($path) * Returns an array on success and false otherwise. * * @param string $filename - * @return mixed - * @access public + * @return array|false */ - function stat($filename) + public function stat($filename) { - if (!($this->bitmap & SSH2::MASK_LOGIN)) { + if (!$this->precheck()) { return false; } - $filename = $this->_realpath($filename); + $filename = $this->realpath($filename); if ($filename === false) { return false; } if ($this->use_stat_cache) { - $result = $this->_query_stat_cache($filename); + $result = $this->query_stat_cache($filename); if (is_array($result) && isset($result['.']) && isset($result['.']->stat)) { return $result['.']->stat; } @@ -1277,16 +1396,16 @@ function stat($filename) } } - $stat = $this->_stat($filename, NET_SFTP_STAT); + $stat = $this->stat_helper($filename, NET_SFTP_STAT); if ($stat === false) { - $this->_remove_from_stat_cache($filename); + $this->remove_from_stat_cache($filename); return false; } if (isset($stat['type'])) { if ($stat['type'] == NET_SFTP_TYPE_DIRECTORY) { - $filename.= '/.'; + $filename .= '/.'; } - $this->_update_stat_cache($filename, (object) array('stat' => $stat)); + $this->update_stat_cache($filename, (object) ['stat' => $stat]); return $stat; } @@ -1297,9 +1416,9 @@ function stat($filename) $this->pwd = $pwd; if ($stat['type'] == NET_SFTP_TYPE_DIRECTORY) { - $filename.= '/.'; + $filename .= '/.'; } - $this->_update_stat_cache($filename, (object) array('stat' => $stat)); + $this->update_stat_cache($filename, (object) ['stat' => $stat]); return $stat; } @@ -1310,22 +1429,21 @@ function stat($filename) * Returns an array on success and false otherwise. * * @param string $filename - * @return mixed - * @access public + * @return array|false */ - function lstat($filename) + public function lstat($filename) { - if (!($this->bitmap & SSH2::MASK_LOGIN)) { + if (!$this->precheck()) { return false; } - $filename = $this->_realpath($filename); + $filename = $this->realpath($filename); if ($filename === false) { return false; } if ($this->use_stat_cache) { - $result = $this->_query_stat_cache($filename); + $result = $this->query_stat_cache($filename); if (is_array($result) && isset($result['.']) && isset($result['.']->lstat)) { return $result['.']->lstat; } @@ -1334,24 +1452,24 @@ function lstat($filename) } } - $lstat = $this->_stat($filename, NET_SFTP_LSTAT); + $lstat = $this->stat_helper($filename, NET_SFTP_LSTAT); if ($lstat === false) { - $this->_remove_from_stat_cache($filename); + $this->remove_from_stat_cache($filename); return false; } if (isset($lstat['type'])) { if ($lstat['type'] == NET_SFTP_TYPE_DIRECTORY) { - $filename.= '/.'; + $filename .= '/.'; } - $this->_update_stat_cache($filename, (object) array('lstat' => $lstat)); + $this->update_stat_cache($filename, (object) ['lstat' => $lstat]); return $lstat; } - $stat = $this->_stat($filename, NET_SFTP_STAT); + $stat = $this->stat_helper($filename, NET_SFTP_STAT); if ($lstat != $stat) { - $lstat = array_merge($lstat, array('type' => NET_SFTP_TYPE_SYMLINK)); - $this->_update_stat_cache($filename, (object) array('lstat' => $lstat)); + $lstat = array_merge($lstat, ['type' => NET_SFTP_TYPE_SYMLINK]); + $this->update_stat_cache($filename, (object) ['lstat' => $lstat]); return $stat; } @@ -1362,9 +1480,9 @@ function lstat($filename) $this->pwd = $pwd; if ($lstat['type'] == NET_SFTP_TYPE_DIRECTORY) { - $filename.= '/.'; + $filename .= '/.'; } - $this->_update_stat_cache($filename, (object) array('lstat' => $lstat)); + $this->update_stat_cache($filename, (object) ['lstat' => $lstat]); return $lstat; } @@ -1372,33 +1490,31 @@ function lstat($filename) /** * Returns general information about a file or symbolic link * - * Determines information without calling \phpseclib\Net\SFTP::realpath(). + * Determines information without calling \phpseclib3\Net\SFTP::realpath(). * The second parameter can be either NET_SFTP_STAT or NET_SFTP_LSTAT. * * @param string $filename * @param int $type - * @return mixed - * @access private + * @throws \UnexpectedValueException on receipt of unexpected packets + * @return array|false */ - function _stat($filename, $type) + private function stat_helper($filename, $type) { // SFTPv4+ adds an additional 32-bit integer field - flags - to the following: - $packet = pack('Na*', strlen($filename), $filename); - if (!$this->_send_sftp_packet($type, $packet)) { - return false; - } + $packet = Strings::packSSH2('s', $filename); + $this->send_sftp_packet($type, $packet); - $response = $this->_get_sftp_packet(); + $response = $this->get_sftp_packet(); switch ($this->packet_type) { case NET_SFTP_ATTRS: - return $this->_parseAttributes($response); + return $this->parseAttributes($response); case NET_SFTP_STATUS: - $this->_logError($response); + $this->logError($response); return false; } - user_error('Expected SSH_FXP_ATTRS or SSH_FXP_STATUS'); - return false; + throw new \UnexpectedValueException('Expected NET_SFTP_ATTRS or NET_SFTP_STATUS. ' + . 'Got packet type: ' . $this->packet_type); } /** @@ -1407,13 +1523,12 @@ function _stat($filename, $type) * @param string $filename * @param int $new_size * @return bool - * @access public */ - function truncate($filename, $new_size) + public function truncate($filename, $new_size) { - $attr = pack('N3', NET_SFTP_ATTR_SIZE, $new_size / 4294967296, $new_size); // 4294967296 == 0x100000000 == 1<<32 + $attr = Strings::packSSH2('NQ', NET_SFTP_ATTR_SIZE, $new_size); - return $this->_setstat($filename, $attr, false); + return $this->setstat($filename, $attr, false); } /** @@ -1424,16 +1539,16 @@ function truncate($filename, $new_size) * @param string $filename * @param int $time * @param int $atime + * @throws \UnexpectedValueException on receipt of unexpected packets * @return bool - * @access public */ - function touch($filename, $time = null, $atime = null) + public function touch($filename, $time = null, $atime = null) { - if (!($this->bitmap & SSH2::MASK_LOGIN)) { + if (!$this->precheck()) { return false; } - $filename = $this->_realpath($filename); + $filename = $this->realpath($filename); if ($filename === false) { return false; } @@ -1445,64 +1560,102 @@ function touch($filename, $time = null, $atime = null) $atime = $time; } - $flags = NET_SFTP_OPEN_WRITE | NET_SFTP_OPEN_CREATE | NET_SFTP_OPEN_EXCL; - $attr = pack('N3', NET_SFTP_ATTR_ACCESSTIME, $time, $atime); - $packet = pack('Na*Na*', strlen($filename), $filename, $flags, $attr); - if (!$this->_send_sftp_packet(NET_SFTP_OPEN, $packet)) { - return false; - } + $attr = $this->version < 4 ? + pack('N3', NET_SFTP_ATTR_ACCESSTIME, $atime, $time) : + Strings::packSSH2('NQ2', NET_SFTP_ATTR_ACCESSTIME | NET_SFTP_ATTR_MODIFYTIME, $atime, $time); + + $packet = Strings::packSSH2('s', $filename); + $packet .= $this->version >= 5 ? + pack('N2', 0, NET_SFTP_OPEN_OPEN_EXISTING) : + pack('N', NET_SFTP_OPEN_WRITE | NET_SFTP_OPEN_CREATE | NET_SFTP_OPEN_EXCL); + $packet .= $attr; - $response = $this->_get_sftp_packet(); + $this->send_sftp_packet(NET_SFTP_OPEN, $packet); + + $response = $this->get_sftp_packet(); switch ($this->packet_type) { case NET_SFTP_HANDLE: - return $this->_close_handle(substr($response, 4)); + return $this->close_handle(substr($response, 4)); case NET_SFTP_STATUS: - $this->_logError($response); + $this->logError($response); break; default: - user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS'); - return false; + throw new \UnexpectedValueException('Expected NET_SFTP_HANDLE or NET_SFTP_STATUS. ' + . 'Got packet type: ' . $this->packet_type); } - return $this->_setstat($filename, $attr, false); + return $this->setstat($filename, $attr, false); } /** * Changes file or directory owner * + * $uid should be an int for SFTPv3 and a string for SFTPv4+. Ideally the string + * would be of the form "user@dns_domain" but it does not need to be. + * `$sftp->getSupportedVersions()['version']` will return the specific version + * that's being used. + * * Returns true on success or false on error. * * @param string $filename - * @param int $uid + * @param int|string $uid * @param bool $recursive * @return bool - * @access public */ - function chown($filename, $uid, $recursive = false) + public function chown($filename, $uid, $recursive = false) { - // quoting from , - // "if the owner or group is specified as -1, then that ID is not changed" - $attr = pack('N3', NET_SFTP_ATTR_UIDGID, $uid, -1); + /* + quoting , - return $this->_setstat($filename, $attr, $recursive); + "To avoid a representation that is tied to a particular underlying + implementation at the client or server, the use of UTF-8 strings has + been chosen. The string should be of the form "user@dns_domain". + This will allow for a client and server that do not use the same + local representation the ability to translate to a common syntax that + can be interpreted by both. In the case where there is no + translation available to the client or server, the attribute value + must be constructed without the "@"." + + phpseclib _could_ auto append the dns_domain to $uid BUT what if it shouldn't + have one? phpseclib would have no way of knowing so rather than guess phpseclib + will just use whatever value the user provided + */ + + $attr = $this->version < 4 ? + // quoting , + // "if the owner or group is specified as -1, then that ID is not changed" + pack('N3', NET_SFTP_ATTR_UIDGID, $uid, -1) : + // quoting , + // "If either the owner or group field is zero length, the field should be + // considered absent, and no change should be made to that specific field + // during a modification operation" + Strings::packSSH2('Nss', NET_SFTP_ATTR_OWNERGROUP, $uid, ''); + + return $this->setstat($filename, $attr, $recursive); } /** * Changes file or directory group * + * $gid should be an int for SFTPv3 and a string for SFTPv4+. Ideally the string + * would be of the form "user@dns_domain" but it does not need to be. + * `$sftp->getSupportedVersions()['version']` will return the specific version + * that's being used. + * * Returns true on success or false on error. * * @param string $filename - * @param int $gid + * @param int|string $gid * @param bool $recursive * @return bool - * @access public */ - function chgrp($filename, $gid, $recursive = false) + public function chgrp($filename, $gid, $recursive = false) { - $attr = pack('N3', NET_SFTP_ATTR_UIDGID, -1, $gid); + $attr = $this->version < 4 ? + pack('N3', NET_SFTP_ATTR_UIDGID, -1, $gid) : + Strings::packSSH2('Nss', NET_SFTP_ATTR_OWNERGROUP, '', $gid); - return $this->_setstat($filename, $attr, $recursive); + return $this->setstat($filename, $attr, $recursive); } /** @@ -1514,10 +1667,10 @@ function chgrp($filename, $gid, $recursive = false) * @param int $mode * @param string $filename * @param bool $recursive + * @throws \UnexpectedValueException on receipt of unexpected packets * @return mixed - * @access public */ - function chmod($mode, $filename, $recursive = false) + public function chmod($mode, $filename, $recursive = false) { if (is_string($mode) && is_int($filename)) { $temp = $mode; @@ -1526,7 +1679,7 @@ function chmod($mode, $filename, $recursive = false) } $attr = pack('N2', NET_SFTP_ATTR_PERMISSIONS, $mode & 07777); - if (!$this->_setstat($filename, $attr, $recursive)) { + if (!$this->setstat($filename, $attr, $recursive)) { return false; } if ($recursive) { @@ -1538,22 +1691,20 @@ function chmod($mode, $filename, $recursive = false) // tell us if the file actually exists. // incidentally, SFTPv4+ adds an additional 32-bit integer field - flags - to the following: $packet = pack('Na*', strlen($filename), $filename); - if (!$this->_send_sftp_packet(NET_SFTP_STAT, $packet)) { - return false; - } + $this->send_sftp_packet(NET_SFTP_STAT, $packet); - $response = $this->_get_sftp_packet(); + $response = $this->get_sftp_packet(); switch ($this->packet_type) { case NET_SFTP_ATTRS: - $attrs = $this->_parseAttributes($response); - return $attrs['permissions']; + $attrs = $this->parseAttributes($response); + return $attrs['mode']; case NET_SFTP_STATUS: - $this->_logError($response); + $this->logError($response); return false; } - user_error('Expected SSH_FXP_ATTRS or SSH_FXP_STATUS'); - return false; + throw new \UnexpectedValueException('Expected NET_SFTP_ATTRS or NET_SFTP_STATUS. ' + . 'Got packet type: ' . $this->packet_type); } /** @@ -1562,34 +1713,34 @@ function chmod($mode, $filename, $recursive = false) * @param string $filename * @param string $attr * @param bool $recursive + * @throws \UnexpectedValueException on receipt of unexpected packets * @return bool - * @access private */ - function _setstat($filename, $attr, $recursive) + private function setstat($filename, $attr, $recursive) { - if (!($this->bitmap & SSH2::MASK_LOGIN)) { + if (!$this->precheck()) { return false; } - $filename = $this->_realpath($filename); + $filename = $this->realpath($filename); if ($filename === false) { return false; } - $this->_remove_from_stat_cache($filename); + $this->remove_from_stat_cache($filename); if ($recursive) { $i = 0; - $result = $this->_setstat_recursive($filename, $attr, $i); - $this->_read_put_responses($i); + $result = $this->setstat_recursive($filename, $attr, $i); + $this->read_put_responses($i); return $result; } - // SFTPv4+ has an additional byte field - type - that would need to be sent, as well. setting it to - // SSH_FILEXFER_TYPE_UNKNOWN might work. if not, we'd have to do an SSH_FXP_STAT before doing an SSH_FXP_SETSTAT. - if (!$this->_send_sftp_packet(NET_SFTP_SETSTAT, pack('Na*a*', strlen($filename), $filename, $attr))) { - return false; - } + $packet = Strings::packSSH2('s', $filename); + $packet .= $this->version >= 4 ? + pack('a*Ca*', substr($attr, 0, 4), NET_SFTP_TYPE_UNKNOWN, substr($attr, 4)) : + $attr; + $this->send_sftp_packet(NET_SFTP_SETSTAT, $packet); /* "Because some systems must use separate system calls to set various attributes, it is possible that a failure @@ -1598,18 +1749,15 @@ function _setstat($filename, $attr, $recursive) -- http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.6 */ - $response = $this->_get_sftp_packet(); + $response = $this->get_sftp_packet(); if ($this->packet_type != NET_SFTP_STATUS) { - user_error('Expected SSH_FXP_STATUS'); - return false; + throw new \UnexpectedValueException('Expected NET_SFTP_STATUS. ' + . 'Got packet type: ' . $this->packet_type); } - if (strlen($response) < 4) { - return false; - } - extract(unpack('Nstatus', $this->_string_shift($response, 4))); + list($status) = Strings::unpackSSH2('N', $response); if ($status != NET_SFTP_STATUS_OK) { - $this->_logError($response, $status); + $this->logError($response, $status); return false; } @@ -1625,18 +1773,17 @@ function _setstat($filename, $attr, $recursive) * @param string $attr * @param int $i * @return bool - * @access private */ - function _setstat_recursive($path, $attr, &$i) + private function setstat_recursive($path, $attr, &$i) { - if (!$this->_read_put_responses($i)) { + if (!$this->read_put_responses($i)) { return false; } $i = 0; - $entries = $this->_list($path, true); + $entries = $this->readlist($path, true); if ($entries === false) { - return $this->_setstat($path, $attr, false); + return $this->setstat($path, $attr, false); } // normally $entries would have at least . and .. but it might not if the directories @@ -1653,18 +1800,20 @@ function _setstat_recursive($path, $attr, &$i) $temp = $path . '/' . $filename; if ($props['type'] == NET_SFTP_TYPE_DIRECTORY) { - if (!$this->_setstat_recursive($temp, $attr, $i)) { + if (!$this->setstat_recursive($temp, $attr, $i)) { return false; } } else { - if (!$this->_send_sftp_packet(NET_SFTP_SETSTAT, pack('Na*a*', strlen($temp), $temp, $attr))) { - return false; - } + $packet = Strings::packSSH2('s', $temp); + $packet .= $this->version >= 4 ? + pack('Ca*', NET_SFTP_TYPE_UNKNOWN, $attr) : + $attr; + $this->send_sftp_packet(NET_SFTP_SETSTAT, $packet); $i++; if ($i >= NET_SFTP_QUEUE_SIZE) { - if (!$this->_read_put_responses($i)) { + if (!$this->read_put_responses($i)) { return false; } $i = 0; @@ -1672,14 +1821,16 @@ function _setstat_recursive($path, $attr, &$i) } } - if (!$this->_send_sftp_packet(NET_SFTP_SETSTAT, pack('Na*a*', strlen($path), $path, $attr))) { - return false; - } + $packet = Strings::packSSH2('s', $path); + $packet .= $this->version >= 4 ? + pack('Ca*', NET_SFTP_TYPE_UNKNOWN, $attr) : + $attr; + $this->send_sftp_packet(NET_SFTP_SETSTAT, $packet); $i++; if ($i >= NET_SFTP_QUEUE_SIZE) { - if (!$this->_read_put_responses($i)) { + if (!$this->read_put_responses($i)) { return false; } $i = 0; @@ -1692,47 +1843,40 @@ function _setstat_recursive($path, $attr, &$i) * Return the target of a symbolic link * * @param string $link + * @throws \UnexpectedValueException on receipt of unexpected packets * @return mixed - * @access public */ - function readlink($link) + public function readlink($link) { - if (!($this->bitmap & SSH2::MASK_LOGIN)) { + if (!$this->precheck()) { return false; } - $link = $this->_realpath($link); + $link = $this->realpath($link); - if (!$this->_send_sftp_packet(NET_SFTP_READLINK, pack('Na*', strlen($link), $link))) { - return false; - } + $this->send_sftp_packet(NET_SFTP_READLINK, Strings::packSSH2('s', $link)); - $response = $this->_get_sftp_packet(); + $response = $this->get_sftp_packet(); switch ($this->packet_type) { case NET_SFTP_NAME: break; case NET_SFTP_STATUS: - $this->_logError($response); + $this->logError($response); return false; default: - user_error('Expected SSH_FXP_NAME or SSH_FXP_STATUS'); - return false; + throw new \UnexpectedValueException('Expected NET_SFTP_NAME or NET_SFTP_STATUS. ' + . 'Got packet type: ' . $this->packet_type); } - if (strlen($response) < 4) { - return false; - } - extract(unpack('Ncount', $this->_string_shift($response, 4))); + list($count) = Strings::unpackSSH2('N', $response); // the file isn't a symlink if (!$count) { return false; } - if (strlen($response) < 4) { - return false; - } - extract(unpack('Nlength', $this->_string_shift($response, 4))); - return $this->_string_shift($response, $length); + list($filename) = Strings::unpackSSH2('s', $response); + + return $filename; } /** @@ -1742,35 +1886,59 @@ function readlink($link) * * @param string $target * @param string $link + * @throws \UnexpectedValueException on receipt of unexpected packets * @return bool - * @access public */ - function symlink($target, $link) + public function symlink($target, $link) { - if (!($this->bitmap & SSH2::MASK_LOGIN)) { + if (!$this->precheck()) { return false; } - //$target = $this->_realpath($target); - $link = $this->_realpath($link); + //$target = $this->realpath($target); + $link = $this->realpath($link); - $packet = pack('Na*Na*', strlen($target), $target, strlen($link), $link); - if (!$this->_send_sftp_packet(NET_SFTP_SYMLINK, $packet)) { - return false; + /* quoting https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-09#section-12.1 : + + Changed the SYMLINK packet to be LINK and give it the ability to + create hard links. Also change it's packet number because many + implementation implemented SYMLINK with the arguments reversed. + Hopefully the new argument names make it clear which way is which. + */ + if ($this->version == 6) { + $type = NET_SFTP_LINK; + $packet = Strings::packSSH2('ssC', $link, $target, 1); + } else { + $type = NET_SFTP_SYMLINK; + /* quoting http://bxr.su/OpenBSD/usr.bin/ssh/PROTOCOL#347 : + + 3.1. sftp: Reversal of arguments to SSH_FXP_SYMLINK + + When OpenSSH's sftp-server was implemented, the order of the arguments + to the SSH_FXP_SYMLINK method was inadvertently reversed. Unfortunately, + the reversal was not noticed until the server was widely deployed. Since + fixing this to follow the specification would cause incompatibility, the + current order was retained. For correct operation, clients should send + SSH_FXP_SYMLINK as follows: + + uint32 id + string targetpath + string linkpath */ + $packet = substr($this->server_identifier, 0, 15) == 'SSH-2.0-OpenSSH' ? + Strings::packSSH2('ss', $target, $link) : + Strings::packSSH2('ss', $link, $target); } + $this->send_sftp_packet($type, $packet); - $response = $this->_get_sftp_packet(); + $response = $this->get_sftp_packet(); if ($this->packet_type != NET_SFTP_STATUS) { - user_error('Expected SSH_FXP_STATUS'); - return false; + throw new \UnexpectedValueException('Expected NET_SFTP_STATUS. ' + . 'Got packet type: ' . $this->packet_type); } - if (strlen($response) < 4) { - return false; - } - extract(unpack('Nstatus', $this->_string_shift($response, 4))); + list($status) = Strings::unpackSSH2('N', $response); if ($status != NET_SFTP_STATUS_OK) { - $this->_logError($response, $status); + $this->logError($response, $status); return false; } @@ -1784,15 +1952,14 @@ function symlink($target, $link) * @param int $mode * @param bool $recursive * @return bool - * @access public */ - function mkdir($dir, $mode = -1, $recursive = false) + public function mkdir($dir, $mode = -1, $recursive = false) { - if (!($this->bitmap & SSH2::MASK_LOGIN)) { + if (!$this->precheck()) { return false; } - $dir = $this->_realpath($dir); + $dir = $this->realpath($dir); if ($recursive) { $dirs = explode('/', preg_replace('#/(?=/)|/$#', '', $dir)); @@ -1803,12 +1970,12 @@ function mkdir($dir, $mode = -1, $recursive = false) for ($i = 0; $i < count($dirs); $i++) { $temp = array_slice($dirs, 0, $i + 1); $temp = implode('/', $temp); - $result = $this->_mkdir_helper($temp, $mode); + $result = $this->mkdir_helper($temp, $mode); } return $result; } - return $this->_mkdir_helper($dir, $mode); + return $this->mkdir_helper($dir, $mode); } /** @@ -1817,27 +1984,21 @@ function mkdir($dir, $mode = -1, $recursive = false) * @param string $dir * @param int $mode * @return bool - * @access private */ - function _mkdir_helper($dir, $mode) + private function mkdir_helper($dir, $mode) { // send SSH_FXP_MKDIR without any attributes (that's what the \0\0\0\0 is doing) - if (!$this->_send_sftp_packet(NET_SFTP_MKDIR, pack('Na*a*', strlen($dir), $dir, "\0\0\0\0"))) { - return false; - } + $this->send_sftp_packet(NET_SFTP_MKDIR, Strings::packSSH2('s', $dir) . "\0\0\0\0"); - $response = $this->_get_sftp_packet(); + $response = $this->get_sftp_packet(); if ($this->packet_type != NET_SFTP_STATUS) { - user_error('Expected SSH_FXP_STATUS'); - return false; + throw new \UnexpectedValueException('Expected NET_SFTP_STATUS. ' + . 'Got packet type: ' . $this->packet_type); } - if (strlen($response) < 4) { - return false; - } - extract(unpack('Nstatus', $this->_string_shift($response, 4))); + list($status) = Strings::unpackSSH2('N', $response); if ($status != NET_SFTP_STATUS_OK) { - $this->_logError($response, $status); + $this->logError($response, $status); return false; } @@ -1852,45 +2013,40 @@ function _mkdir_helper($dir, $mode) * Removes a directory. * * @param string $dir + * @throws \UnexpectedValueException on receipt of unexpected packets * @return bool - * @access public */ - function rmdir($dir) + public function rmdir($dir) { - if (!($this->bitmap & SSH2::MASK_LOGIN)) { + if (!$this->precheck()) { return false; } - $dir = $this->_realpath($dir); + $dir = $this->realpath($dir); if ($dir === false) { return false; } - if (!$this->_send_sftp_packet(NET_SFTP_RMDIR, pack('Na*', strlen($dir), $dir))) { - return false; - } + $this->send_sftp_packet(NET_SFTP_RMDIR, Strings::packSSH2('s', $dir)); - $response = $this->_get_sftp_packet(); + $response = $this->get_sftp_packet(); if ($this->packet_type != NET_SFTP_STATUS) { - user_error('Expected SSH_FXP_STATUS'); - return false; + throw new \UnexpectedValueException('Expected NET_SFTP_STATUS. ' + . 'Got packet type: ' . $this->packet_type); } - if (strlen($response) < 4) { - return false; - } - extract(unpack('Nstatus', $this->_string_shift($response, 4))); + list($status) = Strings::unpackSSH2('N', $response); if ($status != NET_SFTP_STATUS_OK) { // presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED? - $this->_logError($response, $status); + $this->logError($response, $status); return false; } - $this->_remove_from_stat_cache($dir); + $this->remove_from_stat_cache($dir); // the following will do a soft delete, which would be useful if you deleted a file // and then tried to do a stat on the deleted file. the above, in contrast, does // a hard delete - //$this->_update_stat_cache($dir, false); + //$this->update_stat_cache($dir, false); return true; } @@ -1898,15 +2054,16 @@ function rmdir($dir) /** * Uploads a file to the SFTP server. * - * By default, \phpseclib\Net\SFTP::put() does not read from the local filesystem. $data is dumped directly into $remote_file. - * So, for example, if you set $data to 'filename.ext' and then do \phpseclib\Net\SFTP::get(), you will get a file, twelve bytes + * By default, \phpseclib3\Net\SFTP::put() does not read from the local filesystem. $data is dumped directly into $remote_file. + * So, for example, if you set $data to 'filename.ext' and then do \phpseclib3\Net\SFTP::get(), you will get a file, twelve bytes * long, containing 'filename.ext' as its contents. * * Setting $mode to self::SOURCE_LOCAL_FILE will change the above behavior. With self::SOURCE_LOCAL_FILE, $remote_file will * contain as many bytes as filename.ext does on your local filesystem. If your filename.ext is 1MB then that is how * large $remote_file will be, as well. * - * Setting $mode to self::SOURCE_CALLBACK will use $data as callback function, which gets only one parameter -- number of bytes to return, and returns a string if there is some data or null if there is no more data + * Setting $mode to self::SOURCE_CALLBACK will use $data as callback function, which gets only one parameter -- number + * of bytes to return, and returns a string if there is some data or null if there is no more data * * If $data is a resource then it'll be used as a resource instead. * @@ -1930,61 +2087,75 @@ function rmdir($dir) * * Setting $local_start to > 0 or $mode | self::RESUME_START doesn't do anything unless $mode | self::SOURCE_LOCAL_FILE. * + * {@internal ASCII mode for SFTPv4/5/6 can be supported by adding a new function - \phpseclib3\Net\SFTP::setMode().} + * * @param string $remote_file * @param string|resource $data * @param int $mode * @param int $start * @param int $local_start * @param callable|null $progressCallback + * @throws \UnexpectedValueException on receipt of unexpected packets + * @throws \BadFunctionCallException if you're uploading via a callback and the callback function is invalid + * @throws \phpseclib3\Exception\FileNotFoundException if you're uploading via a file and the file doesn't exist * @return bool - * @access public - * @internal ASCII mode for SFTPv4/5/6 can be supported by adding a new function - \phpseclib\Net\SFTP::setMode(). */ - function put($remote_file, $data, $mode = self::SOURCE_STRING, $start = -1, $local_start = -1, $progressCallback = null) + public function put($remote_file, $data, $mode = self::SOURCE_STRING, $start = -1, $local_start = -1, $progressCallback = null) { - if (!($this->bitmap & SSH2::MASK_LOGIN)) { + if (!$this->precheck()) { return false; } - $remote_file = $this->_realpath($remote_file); + $remote_file = $this->realpath($remote_file); if ($remote_file === false) { return false; } - $this->_remove_from_stat_cache($remote_file); + $this->remove_from_stat_cache($remote_file); - $flags = NET_SFTP_OPEN_WRITE | NET_SFTP_OPEN_CREATE; - // according to the SFTP specs, NET_SFTP_OPEN_APPEND should "force all writes to append data at the end of the file." - // in practice, it doesn't seem to do that. - //$flags|= ($mode & self::RESUME) ? NET_SFTP_OPEN_APPEND : NET_SFTP_OPEN_TRUNCATE; + if ($this->version >= 5) { + $flags = NET_SFTP_OPEN_OPEN_OR_CREATE; + } else { + $flags = NET_SFTP_OPEN_WRITE | NET_SFTP_OPEN_CREATE; + // according to the SFTP specs, NET_SFTP_OPEN_APPEND should "force all writes to append data at the end of the file." + // in practice, it doesn't seem to do that. + //$flags|= ($mode & self::RESUME) ? NET_SFTP_OPEN_APPEND : NET_SFTP_OPEN_TRUNCATE; + } if ($start >= 0) { $offset = $start; } elseif ($mode & self::RESUME) { // if NET_SFTP_OPEN_APPEND worked as it should _size() wouldn't need to be called - $size = $this->size($remote_file); + $size = $this->stat($remote_file)['size']; $offset = $size !== false ? $size : 0; } else { $offset = 0; - $flags|= NET_SFTP_OPEN_TRUNCATE; + if ($this->version >= 5) { + $flags = NET_SFTP_OPEN_CREATE_TRUNCATE; + } else { + $flags |= NET_SFTP_OPEN_TRUNCATE; + } } - $packet = pack('Na*N2', strlen($remote_file), $remote_file, $flags, 0); - if (!$this->_send_sftp_packet(NET_SFTP_OPEN, $packet)) { - return false; - } + $this->remove_from_stat_cache($remote_file); + + $packet = Strings::packSSH2('s', $remote_file); + $packet .= $this->version >= 5 ? + pack('N3', 0, $flags, 0) : + pack('N2', $flags, 0); + $this->send_sftp_packet(NET_SFTP_OPEN, $packet); - $response = $this->_get_sftp_packet(); + $response = $this->get_sftp_packet(); switch ($this->packet_type) { case NET_SFTP_HANDLE: $handle = substr($response, 4); break; case NET_SFTP_STATUS: - $this->_logError($response); + $this->logError($response); return false; default: - user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS'); - return false; + throw new \UnexpectedValueException('Expected NET_SFTP_HANDLE or NET_SFTP_STATUS. ' + . 'Got packet type: ' . $this->packet_type); } // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.2.3 @@ -1992,7 +2163,7 @@ function put($remote_file, $data, $mode = self::SOURCE_STRING, $start = -1, $loc switch (true) { case $mode & self::SOURCE_CALLBACK: if (!is_callable($data)) { - user_error("\$data should be is_callable() if you specify SOURCE_CALLBACK flag"); + throw new \BadFunctionCallException("\$data should be is_callable() if you specify SOURCE_CALLBACK flag"); } $dataCallback = $data; // do nothing @@ -2000,7 +2171,7 @@ function put($remote_file, $data, $mode = self::SOURCE_STRING, $start = -1, $loc case is_resource($data): $mode = $mode & ~self::SOURCE_LOCAL_FILE; $info = stream_get_meta_data($data); - if ($info['wrapper_type'] == 'PHP' && $info['stream_type'] == 'Input') { + if (isset($info['wrapper_type']) && $info['wrapper_type'] == 'PHP' && $info['stream_type'] == 'Input') { $fp = fopen('php://memory', 'w+'); stream_copy_to_stream($data, $fp); rewind($fp); @@ -2010,8 +2181,7 @@ function put($remote_file, $data, $mode = self::SOURCE_STRING, $start = -1, $loc break; case $mode & self::SOURCE_LOCAL_FILE: if (!is_file($data)) { - user_error("$data is not a valid file"); - return false; + throw new FileNotFoundException("$data is not a valid file"); } $fp = @fopen($data, 'rb'); if (!$fp) { @@ -2025,7 +2195,7 @@ function put($remote_file, $data, $mode = self::SOURCE_STRING, $start = -1, $loc if ($local_start >= 0) { fseek($fp, $local_start); - $size-= $local_start; + $size -= $local_start; } } elseif ($dataCallback) { $size = 0; @@ -2038,11 +2208,11 @@ function put($remote_file, $data, $mode = self::SOURCE_STRING, $start = -1, $loc $sftp_packet_size = $this->max_sftp_packet; // make the SFTP packet be exactly the SFTP packet size by including the bytes in the NET_SFTP_WRITE packets "header" - $sftp_packet_size-= strlen($handle) + 25; + $sftp_packet_size -= strlen($handle) + 25; $i = $j = 0; while ($dataCallback || ($size === 0 || $sent < $size)) { if ($dataCallback) { - $temp = call_user_func($dataCallback, $sftp_packet_size); + $temp = $dataCallback($sftp_packet_size); if (is_null($temp)) { break; } @@ -2055,22 +2225,23 @@ function put($remote_file, $data, $mode = self::SOURCE_STRING, $start = -1, $loc $subtemp = $offset + $sent; $packet = pack('Na*N3a*', strlen($handle), $handle, $subtemp / 4294967296, $subtemp, strlen($temp), $temp); - if (!$this->_send_sftp_packet(NET_SFTP_WRITE, $packet, $j)) { + try { + $this->send_sftp_packet(NET_SFTP_WRITE, $packet, $j); + } catch (\Exception $e) { if ($mode & self::SOURCE_LOCAL_FILE) { fclose($fp); } - return false; + throw $e; } - $sent+= strlen($temp); + $sent += strlen($temp); if (is_callable($progressCallback)) { - call_user_func($progressCallback, $sent); + $progressCallback($sent); } $i++; $j++; - if ($i == NET_SFTP_UPLOAD_QUEUE_SIZE) { - if (!$this->_read_put_responses($i)) { + if (!$this->read_put_responses($i)) { $i = 0; break; } @@ -2078,26 +2249,33 @@ function put($remote_file, $data, $mode = self::SOURCE_STRING, $start = -1, $loc } } - if (!$this->_read_put_responses($i)) { + $result = $this->close_handle($handle); + + if (!$this->read_put_responses($i)) { if ($mode & self::SOURCE_LOCAL_FILE) { fclose($fp); } - $this->_close_handle($handle); + $this->close_handle($handle); return false; } - if ($mode & self::SOURCE_LOCAL_FILE) { - if ($this->preserveTime) { - $stat = fstat($fp); - $this->touch($remote_file, $stat['mtime'], $stat['atime']); - } - + if ($mode & SFTP::SOURCE_LOCAL_FILE) { if (isset($fp) && is_resource($fp)) { fclose($fp); } + + if ($this->preserveTime) { + $stat = stat($data); + $attr = $this->version < 4 ? + pack('N3', NET_SFTP_ATTR_ACCESSTIME, $stat['atime'], $stat['mtime']) : + Strings::packSSH2('NQ2', NET_SFTP_ATTR_ACCESSTIME | NET_SFTP_ATTR_MODIFYTIME, $stat['atime'], $stat['mtime']); + if (!$this->setstat($remote_file, $attr, false)) { + throw new \RuntimeException('Error setting file time'); + } + } } - return $this->_close_handle($handle); + return $result; } /** @@ -2108,23 +2286,20 @@ function put($remote_file, $data, $mode = self::SOURCE_STRING, $start = -1, $loc * * @param int $i * @return bool - * @access private + * @throws \UnexpectedValueException on receipt of unexpected packets */ - function _read_put_responses($i) + private function read_put_responses($i) { while ($i--) { - $response = $this->_get_sftp_packet(); + $response = $this->get_sftp_packet(); if ($this->packet_type != NET_SFTP_STATUS) { - user_error('Expected SSH_FXP_STATUS'); - return false; + throw new \UnexpectedValueException('Expected NET_SFTP_STATUS. ' + . 'Got packet type: ' . $this->packet_type); } - if (strlen($response) < 4) { - return false; - } - extract(unpack('Nstatus', $this->_string_shift($response, 4))); + list($status) = Strings::unpackSSH2('N', $response); if ($status != NET_SFTP_STATUS_OK) { - $this->_logError($response, $status); + $this->logError($response, $status); break; } } @@ -2137,28 +2312,23 @@ function _read_put_responses($i) * * @param string $handle * @return bool - * @access private + * @throws \UnexpectedValueException on receipt of unexpected packets */ - function _close_handle($handle) + private function close_handle($handle) { - if (!$this->_send_sftp_packet(NET_SFTP_CLOSE, pack('Na*', strlen($handle), $handle))) { - return false; - } + $this->send_sftp_packet(NET_SFTP_CLOSE, pack('Na*', strlen($handle), $handle)); // "The client MUST release all resources associated with the handle regardless of the status." // -- http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.1.3 - $response = $this->_get_sftp_packet(); + $response = $this->get_sftp_packet(); if ($this->packet_type != NET_SFTP_STATUS) { - user_error('Expected SSH_FXP_STATUS'); - return false; + throw new \UnexpectedValueException('Expected NET_SFTP_STATUS. ' + . 'Got packet type: ' . $this->packet_type); } - if (strlen($response) < 4) { - return false; - } - extract(unpack('Nstatus', $this->_string_shift($response, 4))); + list($status) = Strings::unpackSSH2('N', $response); if ($status != NET_SFTP_STATUS_OK) { - $this->_logError($response, $status); + $this->logError($response, $status); return false; } @@ -2175,40 +2345,41 @@ function _close_handle($handle) * $offset and $length can be used to download files in chunks. * * @param string $remote_file - * @param string $local_file + * @param string|bool|resource|callable $local_file * @param int $offset * @param int $length * @param callable|null $progressCallback - * @return mixed - * @access public + * @throws \UnexpectedValueException on receipt of unexpected packets + * @return string|bool */ - function get($remote_file, $local_file = false, $offset = 0, $length = -1, $progressCallback = null) + public function get($remote_file, $local_file = false, $offset = 0, $length = -1, $progressCallback = null) { - if (!($this->bitmap & SSH2::MASK_LOGIN)) { + if (!$this->precheck()) { return false; } - $remote_file = $this->_realpath($remote_file); + $remote_file = $this->realpath($remote_file); if ($remote_file === false) { return false; } - $packet = pack('Na*N2', strlen($remote_file), $remote_file, NET_SFTP_OPEN_READ, 0); - if (!$this->_send_sftp_packet(NET_SFTP_OPEN, $packet)) { - return false; - } + $packet = Strings::packSSH2('s', $remote_file); + $packet .= $this->version >= 5 ? + pack('N3', 0, NET_SFTP_OPEN_OPEN_EXISTING, 0) : + pack('N2', NET_SFTP_OPEN_READ, 0); + $this->send_sftp_packet(NET_SFTP_OPEN, $packet); - $response = $this->_get_sftp_packet(); + $response = $this->get_sftp_packet(); switch ($this->packet_type) { case NET_SFTP_HANDLE: $handle = substr($response, 4); break; case NET_SFTP_STATUS: // presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED - $this->_logError($response); + $this->logError($response); return false; default: - user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS'); - return false; + throw new \UnexpectedValueException('Expected NET_SFTP_HANDLE or NET_SFTP_STATUS. ' + . 'Got packet type: ' . $this->packet_type); } if (is_resource($local_file)) { @@ -2239,15 +2410,17 @@ function get($remote_file, $local_file = false, $offset = 0, $length = -1, $prog $packet_size = $length > 0 ? min($this->max_sftp_packet, $length - $read) : $this->max_sftp_packet; - $packet = pack('Na*N3', strlen($handle), $handle, $tempoffset / 4294967296, $tempoffset, $packet_size); - if (!$this->_send_sftp_packet(NET_SFTP_READ, $packet, $i)) { + $packet = Strings::packSSH2('sN3', $handle, $tempoffset / 4294967296, $tempoffset, $packet_size); + try { + $this->send_sftp_packet(NET_SFTP_READ, $packet, $i); + } catch (\Exception $e) { if ($fclose_check) { fclose($fp); } - return false; + throw $e; } $packet = null; - $read+= $packet_size; + $read += $packet_size; $i++; } @@ -2262,18 +2435,18 @@ function get($remote_file, $local_file = false, $offset = 0, $length = -1, $prog $i--; if ($clear_responses) { - $this->_get_sftp_packet($packets_sent - $i); + $this->get_sftp_packet($packets_sent - $i); continue; } else { - $response = $this->_get_sftp_packet($packets_sent - $i); + $response = $this->get_sftp_packet($packets_sent - $i); } switch ($this->packet_type) { case NET_SFTP_DATA: $temp = substr($response, 4); - $offset+= strlen($temp); + $offset += strlen($temp); if ($local_file === false) { - $content.= $temp; + $content .= $temp; } elseif (is_callable($local_file)) { $local_file($temp); } else { @@ -2286,14 +2459,21 @@ function get($remote_file, $local_file = false, $offset = 0, $length = -1, $prog break; case NET_SFTP_STATUS: // could, in theory, return false if !strlen($content) but we'll hold off for the time being - $this->_logError($response); + $this->logError($response); $clear_responses = true; // don't break out of the loop yet, so we can read the remaining responses break; default: if ($fclose_check) { fclose($fp); } - user_error('Expected SSH_FX_DATA or SSH_FXP_STATUS'); + if ($this->channel_close) { + $this->partial_init = false; + $this->init_sftp_connection(); + return false; + } else { + throw new \UnexpectedValueException('Expected NET_SFTP_DATA or NET_SFTP_STATUS. ' + . 'Got packet type: ' . $this->packet_type); + } } $response = null; } @@ -2320,7 +2500,7 @@ function get($remote_file, $local_file = false, $offset = 0, $length = -1, $prog } } - if (!$this->_close_handle($handle)) { + if (!$this->close_handle($handle)) { return false; } @@ -2334,11 +2514,11 @@ function get($remote_file, $local_file = false, $offset = 0, $length = -1, $prog * @param string $path * @param bool $recursive * @return bool - * @access public + * @throws \UnexpectedValueException on receipt of unexpected packets */ - function delete($path, $recursive = true) + public function delete($path, $recursive = true) { - if (!($this->bitmap & SSH2::MASK_LOGIN)) { + if (!$this->precheck()) { return false; } @@ -2351,39 +2531,35 @@ function delete($path, $recursive = true) return false; } - $path = $this->_realpath($path); + $path = $this->realpath($path); if ($path === false) { return false; } // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.3 - if (!$this->_send_sftp_packet(NET_SFTP_REMOVE, pack('Na*', strlen($path), $path))) { - return false; - } + $this->send_sftp_packet(NET_SFTP_REMOVE, pack('Na*', strlen($path), $path)); - $response = $this->_get_sftp_packet(); + $response = $this->get_sftp_packet(); if ($this->packet_type != NET_SFTP_STATUS) { - user_error('Expected SSH_FXP_STATUS'); - return false; + throw new \UnexpectedValueException('Expected NET_SFTP_STATUS. ' + . 'Got packet type: ' . $this->packet_type); } // if $status isn't SSH_FX_OK it's probably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED - if (strlen($response) < 4) { - return false; - } - extract(unpack('Nstatus', $this->_string_shift($response, 4))); + list($status) = Strings::unpackSSH2('N', $response); if ($status != NET_SFTP_STATUS_OK) { - $this->_logError($response, $status); + $this->logError($response, $status); if (!$recursive) { return false; } + $i = 0; - $result = $this->_delete_recursive($path, $i); - $this->_read_put_responses($i); + $result = $this->delete_recursive($path, $i); + $this->read_put_responses($i); return $result; } - $this->_remove_from_stat_cache($path); + $this->remove_from_stat_cache($path); return true; } @@ -2396,20 +2572,19 @@ function delete($path, $recursive = true) * @param string $path * @param int $i * @return bool - * @access private */ - function _delete_recursive($path, &$i) + private function delete_recursive($path, &$i) { - if (!$this->_read_put_responses($i)) { + if (!$this->read_put_responses($i)) { return false; } $i = 0; - $entries = $this->_list($path, true); + $entries = $this->readlist($path, true); // normally $entries would have at least . and .. but it might not if the directories // permissions didn't allow reading if (empty($entries)) { - return false; + $entries = []; } unset($entries['.'], $entries['..']); @@ -2420,19 +2595,17 @@ function _delete_recursive($path, &$i) $temp = $path . '/' . $filename; if ($props['type'] == NET_SFTP_TYPE_DIRECTORY) { - if (!$this->_delete_recursive($temp, $i)) { + if (!$this->delete_recursive($temp, $i)) { return false; } } else { - if (!$this->_send_sftp_packet(NET_SFTP_REMOVE, pack('Na*', strlen($temp), $temp))) { - return false; - } - $this->_remove_from_stat_cache($temp); + $this->send_sftp_packet(NET_SFTP_REMOVE, Strings::packSSH2('s', $temp)); + $this->remove_from_stat_cache($temp); $i++; if ($i >= NET_SFTP_QUEUE_SIZE) { - if (!$this->_read_put_responses($i)) { + if (!$this->read_put_responses($i)) { return false; } $i = 0; @@ -2440,15 +2613,13 @@ function _delete_recursive($path, &$i) } } - if (!$this->_send_sftp_packet(NET_SFTP_RMDIR, pack('Na*', strlen($path), $path))) { - return false; - } - $this->_remove_from_stat_cache($path); + $this->send_sftp_packet(NET_SFTP_RMDIR, Strings::packSSH2('s', $path)); + $this->remove_from_stat_cache($path); $i++; if ($i >= NET_SFTP_QUEUE_SIZE) { - if (!$this->_read_put_responses($i)) { + if (!$this->read_put_responses($i)) { return false; } $i = 0; @@ -2462,14 +2633,17 @@ function _delete_recursive($path, &$i) * * @param string $path * @return bool - * @access public */ - function file_exists($path) + public function file_exists($path) { if ($this->use_stat_cache) { - $path = $this->_realpath($path); + if (!$this->precheck()) { + return false; + } + + $path = $this->realpath($path); - $result = $this->_query_stat_cache($path); + $result = $this->query_stat_cache($path); if (isset($result)) { // return true if $result is an array or if it's an stdClass object @@ -2485,11 +2659,10 @@ function file_exists($path) * * @param string $path * @return bool - * @access public */ - function is_dir($path) + public function is_dir($path) { - $result = $this->_get_stat_cache_prop($path, 'type'); + $result = $this->get_stat_cache_prop($path, 'type'); if ($result === false) { return false; } @@ -2501,11 +2674,10 @@ function is_dir($path) * * @param string $path * @return bool - * @access public */ - function is_file($path) + public function is_file($path) { - $result = $this->_get_stat_cache_prop($path, 'type'); + $result = $this->get_stat_cache_prop($path, 'type'); if ($result === false) { return false; } @@ -2517,11 +2689,10 @@ function is_file($path) * * @param string $path * @return bool - * @access public */ - function is_link($path) + public function is_link($path) { - $result = $this->_get_lstat_cache_prop($path, 'type'); + $result = $this->get_lstat_cache_prop($path, 'type'); if ($result === false) { return false; } @@ -2533,26 +2704,25 @@ function is_link($path) * * @param string $path * @return bool - * @access public */ - function is_readable($path) + public function is_readable($path) { - $path = $this->_realpath($path); - - $packet = pack('Na*N2', strlen($path), $path, NET_SFTP_OPEN_READ, 0); - if (!$this->_send_sftp_packet(NET_SFTP_OPEN, $packet)) { + if (!$this->precheck()) { return false; } - $response = $this->_get_sftp_packet(); + $packet = Strings::packSSH2('sNN', $this->realpath($path), NET_SFTP_OPEN_READ, 0); + $this->send_sftp_packet(NET_SFTP_OPEN, $packet); + + $response = $this->get_sftp_packet(); switch ($this->packet_type) { case NET_SFTP_HANDLE: return true; case NET_SFTP_STATUS: // presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED return false; default: - user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS'); - return false; + throw new \UnexpectedValueException('Expected NET_SFTP_HANDLE or NET_SFTP_STATUS. ' + . 'Got packet type: ' . $this->packet_type); } } @@ -2561,26 +2731,25 @@ function is_readable($path) * * @param string $path * @return bool - * @access public */ - function is_writable($path) + public function is_writable($path) { - $path = $this->_realpath($path); - - $packet = pack('Na*N2', strlen($path), $path, NET_SFTP_OPEN_WRITE, 0); - if (!$this->_send_sftp_packet(NET_SFTP_OPEN, $packet)) { + if (!$this->precheck()) { return false; } - $response = $this->_get_sftp_packet(); + $packet = Strings::packSSH2('sNN', $this->realpath($path), NET_SFTP_OPEN_WRITE, 0); + $this->send_sftp_packet(NET_SFTP_OPEN, $packet); + + $response = $this->get_sftp_packet(); switch ($this->packet_type) { case NET_SFTP_HANDLE: return true; case NET_SFTP_STATUS: // presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED return false; default: - user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS'); - return false; + throw new \UnexpectedValueException('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS. ' + . 'Got packet type: ' . $this->packet_type); } } @@ -2591,9 +2760,8 @@ function is_writable($path) * * @param string $path * @return bool - * @access public */ - function is_writeable($path) + public function is_writeable($path) { return $this->is_writable($path); } @@ -2603,11 +2771,10 @@ function is_writeable($path) * * @param string $path * @return mixed - * @access public */ - function fileatime($path) + public function fileatime($path) { - return $this->_get_stat_cache_prop($path, 'atime'); + return $this->get_stat_cache_prop($path, 'atime'); } /** @@ -2615,11 +2782,10 @@ function fileatime($path) * * @param string $path * @return mixed - * @access public */ - function filemtime($path) + public function filemtime($path) { - return $this->_get_stat_cache_prop($path, 'mtime'); + return $this->get_stat_cache_prop($path, 'mtime'); } /** @@ -2627,11 +2793,10 @@ function filemtime($path) * * @param string $path * @return mixed - * @access public */ - function fileperms($path) + public function fileperms($path) { - return $this->_get_stat_cache_prop($path, 'permissions'); + return $this->get_stat_cache_prop($path, 'mode'); } /** @@ -2639,11 +2804,10 @@ function fileperms($path) * * @param string $path * @return mixed - * @access public */ - function fileowner($path) + public function fileowner($path) { - return $this->_get_stat_cache_prop($path, 'uid'); + return $this->get_stat_cache_prop($path, 'uid'); } /** @@ -2651,11 +2815,10 @@ function fileowner($path) * * @param string $path * @return mixed - * @access public */ - function filegroup($path) + public function filegroup($path) { - return $this->_get_stat_cache_prop($path, 'gid'); + return $this->get_stat_cache_prop($path, 'gid'); } /** @@ -2663,23 +2826,21 @@ function filegroup($path) * * @param string $path * @return mixed - * @access public */ - function filesize($path) + public function filesize($path) { - return $this->_get_stat_cache_prop($path, 'size'); + return $this->get_stat_cache_prop($path, 'size'); } /** * Gets file type * * @param string $path - * @return mixed - * @access public + * @return string|false */ - function filetype($path) + public function filetype($path) { - $type = $this->_get_stat_cache_prop($path, 'type'); + $type = $this->get_stat_cache_prop($path, 'type'); if ($type === false) { return false; } @@ -2710,11 +2871,10 @@ function filetype($path) * @param string $path * @param string $prop * @return mixed - * @access private */ - function _get_stat_cache_prop($path, $prop) + private function get_stat_cache_prop($path, $prop) { - return $this->_get_xstat_cache_prop($path, $prop, 'stat'); + return $this->get_xstat_cache_prop($path, $prop, 'stat'); } /** @@ -2725,11 +2885,10 @@ function _get_stat_cache_prop($path, $prop) * @param string $path * @param string $prop * @return mixed - * @access private */ - function _get_lstat_cache_prop($path, $prop) + private function get_lstat_cache_prop($path, $prop) { - return $this->_get_xstat_cache_prop($path, $prop, 'lstat'); + return $this->get_xstat_cache_prop($path, $prop, 'lstat'); } /** @@ -2739,16 +2898,19 @@ function _get_lstat_cache_prop($path, $prop) * * @param string $path * @param string $prop - * @param mixed $type + * @param string $type * @return mixed - * @access private */ - function _get_xstat_cache_prop($path, $prop, $type) + private function get_xstat_cache_prop($path, $prop, $type) { + if (!$this->precheck()) { + return false; + } + if ($this->use_stat_cache) { - $path = $this->_realpath($path); + $path = $this->realpath($path); - $result = $this->_query_stat_cache($path); + $result = $this->query_stat_cache($path); if (is_object($result) && isset($result->$type)) { return $result->{$type}[$prop]; @@ -2765,56 +2927,85 @@ function _get_xstat_cache_prop($path, $prop, $type) } /** - * Renames a file or a directory on the SFTP server + * Renames a file or a directory on the SFTP server. + * + * If the file already exists this will return false * * @param string $oldname * @param string $newname * @return bool - * @access public + * @throws \UnexpectedValueException on receipt of unexpected packets */ - function rename($oldname, $newname) + public function rename($oldname, $newname) { - if (!($this->bitmap & SSH2::MASK_LOGIN)) { + if (!$this->precheck()) { return false; } - $oldname = $this->_realpath($oldname); - $newname = $this->_realpath($newname); + $oldname = $this->realpath($oldname); + $newname = $this->realpath($newname); if ($oldname === false || $newname === false) { return false; } // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.3 - $packet = pack('Na*Na*', strlen($oldname), $oldname, strlen($newname), $newname); - if (!$this->_send_sftp_packet(NET_SFTP_RENAME, $packet)) { - return false; + $packet = Strings::packSSH2('ss', $oldname, $newname); + if ($this->version >= 5) { + /* quoting https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-05#section-6.5 , + + 'flags' is 0 or a combination of: + + SSH_FXP_RENAME_OVERWRITE 0x00000001 + SSH_FXP_RENAME_ATOMIC 0x00000002 + SSH_FXP_RENAME_NATIVE 0x00000004 + + (none of these are currently supported) */ + $packet .= "\0\0\0\0"; } + $this->send_sftp_packet(NET_SFTP_RENAME, $packet); - $response = $this->_get_sftp_packet(); + $response = $this->get_sftp_packet(); if ($this->packet_type != NET_SFTP_STATUS) { - user_error('Expected SSH_FXP_STATUS'); - return false; + throw new \UnexpectedValueException('Expected NET_SFTP_STATUS. ' + . 'Got packet type: ' . $this->packet_type); } // if $status isn't SSH_FX_OK it's probably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED - if (strlen($response) < 4) { - return false; - } - extract(unpack('Nstatus', $this->_string_shift($response, 4))); + list($status) = Strings::unpackSSH2('N', $response); if ($status != NET_SFTP_STATUS_OK) { - $this->_logError($response, $status); + $this->logError($response, $status); return false; } // don't move the stat cache entry over since this operation could very well change the // atime and mtime attributes - //$this->_update_stat_cache($newname, $this->_query_stat_cache($oldname)); - $this->_remove_from_stat_cache($oldname); - $this->_remove_from_stat_cache($newname); + //$this->update_stat_cache($newname, $this->query_stat_cache($oldname)); + $this->remove_from_stat_cache($oldname); + $this->remove_from_stat_cache($newname); return true; } + /** + * Parse Time + * + * See '7.7. Times' of draft-ietf-secsh-filexfer-13 for more info. + * + * @param string $key + * @param int $flags + * @param string $response + * @return array + */ + private function parseTime($key, $flags, &$response) + { + $attr = []; + list($attr[$key]) = Strings::unpackSSH2('Q', $response); + if ($flags & NET_SFTP_ATTR_SUBSECOND_TIMES) { + list($attr[$key . '-nseconds']) = Strings::unpackSSH2('N', $response); + } + return $attr; + } + /** * Parse Attributes * @@ -2822,75 +3013,139 @@ function rename($oldname, $newname) * * @param string $response * @return array - * @access private */ - function _parseAttributes(&$response) + protected function parseAttributes(&$response) { - $attr = array(); - if (strlen($response) < 4) { - user_error('Malformed file attributes'); - return array(); + if ($this->version >= 4) { + list($flags, $attr['type']) = Strings::unpackSSH2('NC', $response); + } else { + list($flags) = Strings::unpackSSH2('N', $response); } - extract(unpack('Nflags', $this->_string_shift($response, 4))); - // SFTPv4+ have a type field (a byte) that follows the above flag field + foreach ($this->attributes as $key => $value) { switch ($flags & $key) { - case NET_SFTP_ATTR_SIZE: // 0x00000001 + case NET_SFTP_ATTR_UIDGID: + if ($this->version > 3) { + continue 2; + } + break; + case NET_SFTP_ATTR_CREATETIME: + case NET_SFTP_ATTR_MODIFYTIME: + case NET_SFTP_ATTR_ACL: + case NET_SFTP_ATTR_OWNERGROUP: + case NET_SFTP_ATTR_SUBSECOND_TIMES: + if ($this->version < 4) { + continue 2; + } + break; + case NET_SFTP_ATTR_BITS: + if ($this->version < 5) { + continue 2; + } + break; + case NET_SFTP_ATTR_ALLOCATION_SIZE: + case NET_SFTP_ATTR_TEXT_HINT: + case NET_SFTP_ATTR_MIME_TYPE: + case NET_SFTP_ATTR_LINK_COUNT: + case NET_SFTP_ATTR_UNTRANSLATED_NAME: + case NET_SFTP_ATTR_CTIME: + if ($this->version < 6) { + continue 2; + } + } + switch ($flags & $key) { + case NET_SFTP_ATTR_SIZE: // 0x00000001 // The size attribute is defined as an unsigned 64-bit integer. // The following will use floats on 32-bit platforms, if necessary. // As can be seen in the BigInteger class, floats are generally // IEEE 754 binary64 "double precision" on such platforms and // as such can represent integers of at least 2^50 without loss // of precision. Interpreted in filesize, 2^50 bytes = 1024 TiB. - $attr['size'] = hexdec(bin2hex($this->_string_shift($response, 8))); + list($attr['size']) = Strings::unpackSSH2('Q', $response); break; case NET_SFTP_ATTR_UIDGID: // 0x00000002 (SFTPv3 only) - if (strlen($response) < 8) { - user_error('Malformed file attributes'); - return $attr; - } - $attr+= unpack('Nuid/Ngid', $this->_string_shift($response, 8)); + list($attr['uid'], $attr['gid']) = Strings::unpackSSH2('NN', $response); break; case NET_SFTP_ATTR_PERMISSIONS: // 0x00000004 - if (strlen($response) < 4) { - user_error('Malformed file attributes'); - return $attr; - } - $attr+= unpack('Npermissions', $this->_string_shift($response, 4)); - // mode == permissions; permissions was the original array key and is retained for bc purposes. - // mode was added because that's the more industry standard terminology - $attr+= array('mode' => $attr['permissions']); - $fileType = $this->_parseMode($attr['permissions']); - if ($fileType !== false) { - $attr+= array('type' => $fileType); + list($attr['mode']) = Strings::unpackSSH2('N', $response); + $fileType = $this->parseMode($attr['mode']); + if ($this->version < 4 && $fileType !== false) { + $attr += ['type' => $fileType]; } break; case NET_SFTP_ATTR_ACCESSTIME: // 0x00000008 - if (strlen($response) < 8) { - user_error('Malformed file attributes'); - return $attr; + if ($this->version >= 4) { + $attr += $this->parseTime('atime', $flags, $response); + break; } - $attr+= unpack('Natime/Nmtime', $this->_string_shift($response, 8)); + list($attr['atime'], $attr['mtime']) = Strings::unpackSSH2('NN', $response); break; - case NET_SFTP_ATTR_EXTENDED: // 0x80000000 - if (strlen($response) < 4) { - user_error('Malformed file attributes'); - return $attr; + case NET_SFTP_ATTR_CREATETIME: // 0x00000010 (SFTPv4+) + $attr += $this->parseTime('createtime', $flags, $response); + break; + case NET_SFTP_ATTR_MODIFYTIME: // 0x00000020 + $attr += $this->parseTime('mtime', $flags, $response); + break; + case NET_SFTP_ATTR_ACL: // 0x00000040 + // access control list + // see https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-04#section-5.7 + // currently unsupported + list($count) = Strings::unpackSSH2('N', $response); + for ($i = 0; $i < $count; $i++) { + list($type, $flag, $mask, $who) = Strings::unpackSSH2('N3s', $result); } - extract(unpack('Ncount', $this->_string_shift($response, 4))); + break; + case NET_SFTP_ATTR_OWNERGROUP: // 0x00000080 + list($attr['owner'], $attr['$group']) = Strings::unpackSSH2('ss', $response); + break; + case NET_SFTP_ATTR_SUBSECOND_TIMES: // 0x00000100 + break; + case NET_SFTP_ATTR_BITS: // 0x00000200 (SFTPv5+) + // see https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-05#section-5.8 + // currently unsupported + // tells if you file is: + // readonly, system, hidden, case inensitive, archive, encrypted, compressed, sparse + // append only, immutable, sync + list($attrib_bits, $attrib_bits_valid) = Strings::unpackSSH2('N2', $response); + // if we were actually gonna implement the above it ought to be + // $attr['attrib-bits'] and $attr['attrib-bits-valid'] + // eg. - instead of _ + break; + case NET_SFTP_ATTR_ALLOCATION_SIZE: // 0x00000400 (SFTPv6+) + // see https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-13#section-7.4 + // represents the number of bytes that the file consumes on the disk. will + // usually be larger than the 'size' field + list($attr['allocation-size']) = Strings::unpackSSH2('Q', $response); + break; + case NET_SFTP_ATTR_TEXT_HINT: // 0x00000800 + // https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-13#section-7.10 + // currently unsupported + // tells if file is "known text", "guessed text", "known binary", "guessed binary" + list($text_hint) = Strings::unpackSSH2('C', $response); + // the above should be $attr['text-hint'] + break; + case NET_SFTP_ATTR_MIME_TYPE: // 0x00001000 + // see https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-13#section-7.11 + list($attr['mime-type']) = Strings::unpackSSH2('s', $response); + break; + case NET_SFTP_ATTR_LINK_COUNT: // 0x00002000 + // see https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-13#section-7.12 + list($attr['link-count']) = Strings::unpackSSH2('N', $response); + break; + case NET_SFTP_ATTR_UNTRANSLATED_NAME:// 0x00004000 + // see https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-13#section-7.13 + list($attr['untranslated-name']) = Strings::unpackSSH2('s', $response); + break; + case NET_SFTP_ATTR_CTIME: // 0x00008000 + // 'ctime' contains the last time the file attributes were changed. The + // exact meaning of this field depends on the server. + $attr += $this->parseTime('ctime', $flags, $response); + break; + case NET_SFTP_ATTR_EXTENDED: // 0x80000000 + list($count) = Strings::unpackSSH2('N', $response); for ($i = 0; $i < $count; $i++) { - if (strlen($response) < 4) { - user_error('Malformed file attributes'); - return $attr; - } - extract(unpack('Nlength', $this->_string_shift($response, 4))); - $key = $this->_string_shift($response, $length); - if (strlen($response) < 4) { - user_error('Malformed file attributes'); - return $attr; - } - extract(unpack('Nlength', $this->_string_shift($response, 4))); - $attr[$key] = $this->_string_shift($response, $length); + list($key, $value) = Strings::unpackSSH2('ss', $response); + $attr[$key] = $value; } } } @@ -2904,9 +3159,8 @@ function _parseAttributes(&$response) * * @param int $mode * @return int - * @access private */ - function _parseMode($mode) + private function parseMode($mode) { // values come from http://lxr.free-electrons.com/source/include/uapi/linux/stat.h#L12 // see, also, http://linux.die.net/man/2/stat @@ -2951,9 +3205,8 @@ function _parseMode($mode) * * @param string $longname * @return mixed - * @access private */ - function _parseLongname($longname) + private function parseLongname($longname) { // http://en.wikipedia.org/wiki/Unix_file_types // http://en.wikipedia.org/wiki/Filesystem_permissions#Notation_of_traditional_Unix_permissions @@ -2982,11 +3235,10 @@ function _parseLongname($longname) * @param string $data * @param int $request_id * @see self::_get_sftp_packet() - * @see self::_send_channel_packet() - * @return bool - * @access private + * @see self::send_channel_packet() + * @return void */ - function _send_sftp_packet($type, $data, $request_id = 1) + private function send_sftp_packet($type, $data, $request_id = 1) { // in SSH2.php the timeout is cumulative per function call. eg. exec() will // timeout after 10s. but for SFTP.php it's cumulative per packet @@ -2994,50 +3246,30 @@ function _send_sftp_packet($type, $data, $request_id = 1) $packet = $this->use_request_id ? pack('NCNa*', strlen($data) + 5, $type, $request_id, $data) : - pack('NCa*', strlen($data) + 1, $type, $data); + pack('NCa*', strlen($data) + 1, $type, $data); - $start = strtok(microtime(), ' ') + strtok(''); // http://php.net/microtime#61838 - $result = $this->_send_channel_packet(self::CHANNEL, $packet); - $stop = strtok(microtime(), ' ') + strtok(''); + $start = microtime(true); + $this->send_channel_packet(self::CHANNEL, $packet); + $stop = microtime(true); if (defined('NET_SFTP_LOGGING')) { $packet_type = '-> ' . $this->packet_types[$type] . ' (' . round($stop - $start, 4) . 's)'; - if (NET_SFTP_LOGGING == self::LOG_REALTIME) { - switch (PHP_SAPI) { - case 'cli': - $start = $stop = "\r\n"; - break; - default: - $start = '
';
-                        $stop = '
'; - } - echo $start . $this->_format_log(array($data), array($packet_type)) . $stop; - @flush(); - @ob_flush(); - } else { - $this->packet_type_log[] = $packet_type; - if (NET_SFTP_LOGGING == self::LOG_COMPLEX) { - $this->packet_log[] = $data; - } - } + $this->append_log($packet_type, $data); } - - return $result; } /** * Resets a connection for re-use * * @param int $reason - * @access private */ - function _reset_connection($reason) + protected function reset_connection($reason) { - parent::_reset_connection($reason); + parent::reset_connection($reason); $this->use_request_id = false; $this->pwd = false; - $this->requestBuffer = array(); + $this->requestBuffer = []; } /** @@ -3051,10 +3283,11 @@ function _reset_connection($reason) * * @see self::_send_sftp_packet() * @return string - * @access private */ - function _get_sftp_packet($request_id = null) + private function get_sftp_packet($request_id = null) { + $this->channel_close = false; + if (isset($request_id) && isset($this->requestBuffer[$request_id])) { $this->packet_type = $this->requestBuffer[$request_id]['packet_type']; $temp = $this->requestBuffer[$request_id]['packet']; @@ -3066,100 +3299,111 @@ function _get_sftp_packet($request_id = null) // timeout after 10s. but for SFTP.php it's cumulative per packet $this->curTimeout = $this->timeout; - $start = strtok(microtime(), ' ') + strtok(''); // http://php.net/microtime#61838 + $start = microtime(true); // SFTP packet length while (strlen($this->packet_buffer) < 4) { - $temp = $this->_get_channel_packet(self::CHANNEL, true); - if (is_bool($temp)) { + $temp = $this->get_channel_packet(self::CHANNEL, true); + if ($temp === true) { + if ($this->channel_status[self::CHANNEL] === NET_SSH2_MSG_CHANNEL_CLOSE) { + $this->channel_close = true; + } $this->packet_type = false; $this->packet_buffer = ''; return false; } - $this->packet_buffer.= $temp; + $this->packet_buffer .= $temp; } if (strlen($this->packet_buffer) < 4) { - return false; + throw new \RuntimeException('Packet is too small'); } - extract(unpack('Nlength', $this->_string_shift($this->packet_buffer, 4))); - $tempLength = $length; - $tempLength-= strlen($this->packet_buffer); + extract(unpack('Nlength', Strings::shift($this->packet_buffer, 4))); + /** @var integer $length */ + $tempLength = $length; + $tempLength -= strlen($this->packet_buffer); // 256 * 1024 is what SFTP_MAX_MSG_LENGTH is set to in OpenSSH's sftp-common.h - if ($tempLength > 256 * 1024) { - user_error('Invalid SFTP packet size'); - return false; + if (!$this->allow_arbitrary_length_packets && !$this->use_request_id && $tempLength > 256 * 1024) { + throw new \RuntimeException('Invalid Size'); } // SFTP packet type and data payload while ($tempLength > 0) { - $temp = $this->_get_channel_packet(self::CHANNEL, true); - if (is_bool($temp)) { + $temp = $this->get_channel_packet(self::CHANNEL, true); + if ($temp === true) { + if ($this->channel_status[self::CHANNEL] === NET_SSH2_MSG_CHANNEL_CLOSE) { + $this->channel_close = true; + } $this->packet_type = false; $this->packet_buffer = ''; return false; } - $this->packet_buffer.= $temp; - $tempLength-= strlen($temp); + $this->packet_buffer .= $temp; + $tempLength -= strlen($temp); } - $stop = strtok(microtime(), ' ') + strtok(''); + $stop = microtime(true); - $this->packet_type = ord($this->_string_shift($this->packet_buffer)); + $this->packet_type = ord(Strings::shift($this->packet_buffer)); if ($this->use_request_id) { - extract(unpack('Npacket_id', $this->_string_shift($this->packet_buffer, 4))); // remove the request id - $length-= 5; // account for the request id and the packet type + extract(unpack('Npacket_id', Strings::shift($this->packet_buffer, 4))); // remove the request id + $length -= 5; // account for the request id and the packet type } else { - $length-= 1; // account for the packet type + $length -= 1; // account for the packet type } - $packet = $this->_string_shift($this->packet_buffer, $length); + $packet = Strings::shift($this->packet_buffer, $length); if (defined('NET_SFTP_LOGGING')) { $packet_type = '<- ' . $this->packet_types[$this->packet_type] . ' (' . round($stop - $start, 4) . 's)'; - if (NET_SFTP_LOGGING == self::LOG_REALTIME) { - switch (PHP_SAPI) { - case 'cli': - $start = $stop = "\r\n"; - break; - default: - $start = '
';
-                        $stop = '
'; - } - echo $start . $this->_format_log(array($packet), array($packet_type)) . $stop; - @flush(); - @ob_flush(); - } else { - $this->packet_type_log[] = $packet_type; - if (NET_SFTP_LOGGING == self::LOG_COMPLEX) { - $this->packet_log[] = $packet; - } - } + $this->append_log($packet_type, $packet); } if (isset($request_id) && $this->use_request_id && $packet_id != $request_id) { - $this->requestBuffer[$packet_id] = array( + $this->requestBuffer[$packet_id] = [ 'packet_type' => $this->packet_type, 'packet' => $packet - ); - return $this->_get_sftp_packet($request_id); + ]; + return $this->get_sftp_packet($request_id); } return $packet; } + /** + * Logs data packets + * + * Makes sure that only the last 1MB worth of packets will be logged + * + * @param string $message_number + * @param string $message + */ + private function append_log($message_number, $message) + { + $this->append_log_helper( + NET_SFTP_LOGGING, + $message_number, + $message, + $this->packet_type_log, + $this->packet_log, + $this->log_size, + $this->realtime_log_file, + $this->realtime_log_wrap, + $this->realtime_log_size + ); + } + /** * Returns a log of the packets that have been sent and received. * - * Returns a string if NET_SFTP_LOGGING == NET_SFTP_LOG_COMPLEX, an array if NET_SFTP_LOGGING == NET_SFTP_LOG_SIMPLE and false if !defined('NET_SFTP_LOGGING') + * Returns a string if NET_SFTP_LOGGING == self::LOG_COMPLEX, an array if NET_SFTP_LOGGING == self::LOG_SIMPLE and false if !defined('NET_SFTP_LOGGING') * - * @access public - * @return string or Array + * @return array|string */ - function getSFTPLog() + public function getSFTPLog() { if (!defined('NET_SFTP_LOGGING')) { return false; @@ -3167,7 +3411,7 @@ function getSFTPLog() switch (NET_SFTP_LOGGING) { case self::LOG_COMPLEX: - return $this->_format_log($this->packet_log, $this->packet_type_log); + return $this->format_log($this->packet_log, $this->packet_type_log); break; //case self::LOG_SIMPLE: default: @@ -3179,9 +3423,8 @@ function getSFTPLog() * Returns all errors * * @return array - * @access public */ - function getSFTPErrors() + public function getSFTPErrors() { return $this->sftp_errors; } @@ -3190,9 +3433,8 @@ function getSFTPErrors() * Returns the last error * * @return string - * @access public */ - function getLastSFTPError() + public function getLastSFTPError() { return count($this->sftp_errors) ? $this->sftp_errors[count($this->sftp_errors) - 1] : ''; } @@ -3201,36 +3443,69 @@ function getLastSFTPError() * Get supported SFTP versions * * @return array - * @access public */ - function getSupportedVersions() + public function getSupportedVersions() { - $temp = array('version' => $this->version); + if (!($this->bitmap & SSH2::MASK_LOGIN)) { + return false; + } + + if (!$this->partial_init) { + $this->partial_init_sftp_connection(); + } + + $temp = ['version' => $this->defaultVersion]; if (isset($this->extensions['versions'])) { $temp['extensions'] = $this->extensions['versions']; } return $temp; } + /** + * Get supported SFTP versions + * + * @return int|false + */ + public function getNegotiatedVersion() + { + if (!$this->precheck()) { + return false; + } + + return $this->version; + } + + /** + * Set preferred version + * + * If you're preferred version isn't supported then the highest supported + * version of SFTP will be utilized. Set to null or false or int(0) to + * unset the preferred version + * + * @param int $version + */ + public function setPreferredVersion($version) + { + $this->preferredVersion = $version; + } + /** * Disconnect * * @param int $reason - * @return bool - * @access private + * @return false */ - function _disconnect($reason) + protected function disconnect_helper($reason) { $this->pwd = false; - parent::_disconnect($reason); + return parent::disconnect_helper($reason); } /** * Enable Date Preservation * - * @access public */ - function enableDatePreservation() + public function enableDatePreservation() { $this->preserveTime = true; } @@ -3238,9 +3513,8 @@ function enableDatePreservation() /** * Disable Date Preservation * - * @access public */ - function disableDatePreservation() + public function disableDatePreservation() { $this->preserveTime = false; } diff --git a/vendor/phpseclib/phpseclib/phpseclib/Net/SFTP/Stream.php b/vendor/phpseclib/phpseclib/phpseclib/Net/SFTP/Stream.php index ec9e5841..24047b4b 100644 --- a/vendor/phpseclib/phpseclib/phpseclib/Net/SFTP/Stream.php +++ b/vendor/phpseclib/phpseclib/phpseclib/Net/SFTP/Stream.php @@ -7,25 +7,22 @@ * * PHP version 5 * - * @category Net - * @package SFTP * @author Jim Wigginton * @copyright 2013 Jim Wigginton * @license http://www.opensource.org/licenses/mit-license.html MIT License * @link http://phpseclib.sourceforge.net */ -namespace phpseclib\Net\SFTP; +namespace phpseclib3\Net\SFTP; -use phpseclib\Crypt\RSA; -use phpseclib\Net\SFTP; +use phpseclib3\Crypt\Common\PrivateKey; +use phpseclib3\Net\SFTP; +use phpseclib3\Net\SSH2; /** * SFTP Stream Wrapper * - * @package SFTP * @author Jim Wigginton - * @access public */ class Stream { @@ -36,90 +33,80 @@ class Stream * * @var array */ - static $instances; + public static $instances; /** * SFTP instance * * @var object - * @access private */ - var $sftp; + private $sftp; /** * Path * * @var string - * @access private */ - var $path; + private $path; /** * Mode * * @var string - * @access private */ - var $mode; + private $mode; /** * Position * * @var int - * @access private */ - var $pos; + private $pos; /** * Size * * @var int - * @access private */ - var $size; + private $size; /** * Directory entries * * @var array - * @access private */ - var $entries; + private $entries; /** * EOF flag * * @var bool - * @access private */ - var $eof; + private $eof; /** * Context resource * - * Technically this needs to be publically accessible so PHP can set it directly + * Technically this needs to be publicly accessible so PHP can set it directly * * @var resource - * @access public */ - var $context; + public $context; /** * Notification callback function * * @var callable - * @access public */ - var $notification; + private $notification; /** * Registers this class as a URL wrapper. * * @param string $protocol The wrapper name to be registered. * @return bool True on success, false otherwise. - * @access public */ - static function register($protocol = 'sftp') + public static function register($protocol = 'sftp') { if (in_array($protocol, stream_get_wrappers(), true)) { return false; @@ -130,9 +117,8 @@ static function register($protocol = 'sftp') /** * The Constructor * - * @access public */ - function __construct() + public function __construct() { if (defined('NET_SFTP_STREAM_LOGGING')) { echo "__construct()\r\n"; @@ -149,21 +135,20 @@ function __construct() * * @param string $path * @return string - * @access private */ - function _parse_path($path) + protected function parse_path($path) { $orig = $path; - extract(parse_url($path) + array('port' => 22)); + extract(parse_url($path) + ['port' => 22]); if (isset($query)) { - $path.= '?' . $query; + $path .= '?' . $query; } elseif (preg_match('/(\?|\?#)$/', $orig)) { - $path.= '?'; + $path .= '?'; } if (isset($fragment)) { - $path.= '#' . $fragment; + $path .= '#' . $fragment; } elseif ($orig[strlen($orig) - 1] == '#') { - $path.= '#'; + $path .= '#'; } if (!isset($host)) { @@ -177,13 +162,12 @@ function _parse_path($path) } } - if ($host[0] == '$') { - $host = substr($host, 1); - global ${$host}; - if (($$host instanceof SFTP) === false) { + if (preg_match('/^{[a-z0-9]+}$/i', $host)) { + $host = SSH2::getConnectionByResourceId($host); + if ($host === false) { return false; } - $this->sftp = $$host; + $this->sftp = $host; } else { if (isset($this->context)) { $context = stream_context_get_options($this->context); @@ -204,7 +188,7 @@ function _parse_path($path) if (isset($context[$scheme]['password'])) { $pass = $context[$scheme]['password']; } - if (isset($context[$scheme]['privkey']) && $context[$scheme]['privkey'] instanceof RSA) { + if (isset($context[$scheme]['privkey']) && $context[$scheme]['privkey'] instanceof PrivateKey) { $pass = $context[$scheme]['privkey']; } @@ -212,7 +196,7 @@ function _parse_path($path) return false; } - // casting $pass to a string is necessary in the event that it's a \phpseclib\Crypt\RSA object + // casting $pass to a string is necessary in the event that it's a \phpseclib3\Crypt\RSA object if (isset(self::$instances[$host][$port][$user][(string) $pass])) { $this->sftp = self::$instances[$host][$port][$user][(string) $pass]; } else { @@ -255,18 +239,17 @@ function _parse_path($path) * @param int $options * @param string $opened_path * @return bool - * @access public */ - function _stream_open($path, $mode, $options, &$opened_path) + private function _stream_open($path, $mode, $options, &$opened_path) { - $path = $this->_parse_path($path); + $path = $this->parse_path($path); if ($path === false) { return false; } $this->path = $path; - $this->size = $this->sftp->size($path); + $this->size = $this->sftp->filesize($path); $this->mode = preg_replace('#[bt]$#', '', $mode); $this->eof = false; @@ -297,9 +280,8 @@ function _stream_open($path, $mode, $options, &$opened_path) * * @param int $count * @return mixed - * @access public */ - function _stream_read($count) + private function _stream_read($count) { switch ($this->mode) { case 'w': @@ -329,7 +311,7 @@ function _stream_read($count) $this->eof = true; return false; } - $this->pos+= strlen($result); + $this->pos += strlen($result); return $result; } @@ -338,10 +320,9 @@ function _stream_read($count) * Write to stream * * @param string $data - * @return mixed - * @access public + * @return int|false */ - function _stream_write($data) + private function _stream_write($data) { switch ($this->mode) { case 'r': @@ -361,7 +342,7 @@ function _stream_write($data) if ($result === false) { return false; } - $this->pos+= strlen($data); + $this->pos += strlen($data); if ($this->pos > $this->size) { $this->size = $this->pos; } @@ -373,9 +354,8 @@ function _stream_write($data) * Retrieve the current position of a stream * * @return int - * @access public */ - function _stream_tell() + private function _stream_tell() { return $this->pos; } @@ -391,9 +371,8 @@ function _stream_tell() * will return false. do fread($fp, 1) and feof() will then return true. * * @return bool - * @access public */ - function _stream_eof() + private function _stream_eof() { return $this->eof; } @@ -404,9 +383,8 @@ function _stream_eof() * @param int $offset * @param int $whence * @return bool - * @access public */ - function _stream_seek($offset, $whence) + private function _stream_seek($offset, $whence) { switch ($whence) { case SEEK_SET: @@ -415,10 +393,10 @@ function _stream_seek($offset, $whence) } break; case SEEK_CUR: - $offset+= $this->pos; + $offset += $this->pos; break; case SEEK_END: - $offset+= $this->size; + $offset += $this->size; } $this->pos = $offset; @@ -433,11 +411,10 @@ function _stream_seek($offset, $whence) * @param int $option * @param mixed $var * @return bool - * @access public */ - function _stream_metadata($path, $option, $var) + private function _stream_metadata($path, $option, $var) { - $path = $this->_parse_path($path); + $path = $this->parse_path($path); if ($path === false) { return false; } @@ -467,9 +444,8 @@ function _stream_metadata($path, $option, $var) * * @param int $cast_as * @return resource - * @access public */ - function _stream_cast($cast_as) + private function _stream_cast($cast_as) { return $this->sftp->fsock; } @@ -479,9 +455,8 @@ function _stream_cast($cast_as) * * @param int $operation * @return bool - * @access public */ - function _stream_lock($operation) + private function _stream_lock($operation) { return false; } @@ -490,15 +465,14 @@ function _stream_lock($operation) * Renames a file or directory * * Attempts to rename oldname to newname, moving it between directories if necessary. - * If newname exists, it will be overwritten. This is a departure from what \phpseclib\Net\SFTP + * If newname exists, it will be overwritten. This is a departure from what \phpseclib3\Net\SFTP * does. * * @param string $path_from * @param string $path_to * @return bool - * @access public */ - function _rename($path_from, $path_to) + private function _rename($path_from, $path_to) { $path1 = parse_url($path_from); $path2 = parse_url($path_to); @@ -507,7 +481,7 @@ function _rename($path_from, $path_to) return false; } - $path_from = $this->_parse_path($path_from); + $path_from = $this->parse_path($path_from); $path_to = parse_url($path_to); if ($path_from === false) { return false; @@ -548,11 +522,10 @@ function _rename($path_from, $path_to) * @param string $path * @param int $options * @return bool - * @access public */ - function _dir_opendir($path, $options) + private function _dir_opendir($path, $options) { - $path = $this->_parse_path($path); + $path = $this->parse_path($path); if ($path === false) { return false; } @@ -565,9 +538,8 @@ function _dir_opendir($path, $options) * Read entry from directory handle * * @return mixed - * @access public */ - function _dir_readdir() + private function _dir_readdir() { if (isset($this->entries[$this->pos])) { return $this->entries[$this->pos++]; @@ -579,9 +551,8 @@ function _dir_readdir() * Rewind directory handle * * @return bool - * @access public */ - function _dir_rewinddir() + private function _dir_rewinddir() { $this->pos = 0; return true; @@ -591,9 +562,8 @@ function _dir_rewinddir() * Close directory handle * * @return bool - * @access public */ - function _dir_closedir() + private function _dir_closedir() { return true; } @@ -607,11 +577,10 @@ function _dir_closedir() * @param int $mode * @param int $options * @return bool - * @access public */ - function _mkdir($path, $mode, $options) + private function _mkdir($path, $mode, $options) { - $path = $this->_parse_path($path); + $path = $this->parse_path($path); if ($path === false) { return false; } @@ -630,11 +599,10 @@ function _mkdir($path, $mode, $options) * @param string $path * @param int $options * @return bool - * @access public */ - function _rmdir($path, $options) + private function _rmdir($path, $options) { - $path = $this->_parse_path($path); + $path = $this->parse_path($path); if ($path === false) { return false; } @@ -645,12 +613,11 @@ function _rmdir($path, $options) /** * Flushes the output * - * See . Always returns true because \phpseclib\Net\SFTP doesn't cache stuff before writing + * See . Always returns true because \phpseclib3\Net\SFTP doesn't cache stuff before writing * * @return bool - * @access public */ - function _stream_flush() + private function _stream_flush() { return true; } @@ -659,9 +626,8 @@ function _stream_flush() * Retrieve information about a file resource * * @return mixed - * @access public */ - function _stream_stat() + private function _stream_stat() { $results = $this->sftp->stat($this->path); if ($results === false) { @@ -675,11 +641,10 @@ function _stream_stat() * * @param string $path * @return bool - * @access public */ - function _unlink($path) + private function _unlink($path) { - $path = $this->_parse_path($path); + $path = $this->parse_path($path); if ($path === false) { return false; } @@ -690,18 +655,17 @@ function _unlink($path) /** * Retrieve information about a file * - * Ignores the STREAM_URL_STAT_QUIET flag because the entirety of \phpseclib\Net\SFTP\Stream is quiet by default + * Ignores the STREAM_URL_STAT_QUIET flag because the entirety of \phpseclib3\Net\SFTP\Stream is quiet by default * might be worthwhile to reconstruct bits 12-16 (ie. the file type) if mode doesn't have them but we'll * cross that bridge when and if it's reached * * @param string $path * @param int $flags * @return mixed - * @access public */ - function _url_stat($path, $flags) + private function _url_stat($path, $flags) { - $path = $this->_parse_path($path); + $path = $this->parse_path($path); if ($path === false) { return false; } @@ -719,9 +683,8 @@ function _url_stat($path, $flags) * * @param int $new_size * @return bool - * @access public */ - function _stream_truncate($new_size) + private function _stream_truncate($new_size) { if (!$this->sftp->truncate($this->path, $new_size)) { return false; @@ -737,15 +700,14 @@ function _stream_truncate($new_size) * Change stream options * * STREAM_OPTION_WRITE_BUFFER isn't supported for the same reason stream_flush isn't. - * The other two aren't supported because of limitations in \phpseclib\Net\SFTP. + * The other two aren't supported because of limitations in \phpseclib3\Net\SFTP. * * @param int $option * @param int $arg1 * @param int $arg2 * @return bool - * @access public */ - function _stream_set_option($option, $arg1, $arg2) + private function _stream_set_option($option, $arg1, $arg2) { return false; } @@ -753,9 +715,8 @@ function _stream_set_option($option, $arg1, $arg2) /** * Close an resource * - * @access public */ - function _stream_close() + private function _stream_close() { } @@ -772,9 +733,8 @@ function _stream_close() * @param string $name * @param array $arguments * @return mixed - * @access public */ - function __call($name, $arguments) + public function __call($name, array $arguments) { if (defined('NET_SFTP_STREAM_LOGGING')) { echo $name . '('; @@ -791,6 +751,6 @@ function __call($name, $arguments) if (!method_exists($this, $name)) { return false; } - return call_user_func_array(array($this, $name), $arguments); + return $this->$name(...$arguments); } } diff --git a/vendor/phpseclib/phpseclib/phpseclib/Net/SSH1.php b/vendor/phpseclib/phpseclib/phpseclib/Net/SSH1.php deleted file mode 100644 index e372b8b9..00000000 --- a/vendor/phpseclib/phpseclib/phpseclib/Net/SSH1.php +++ /dev/null @@ -1,1646 +0,0 @@ - - * login('username', 'password')) { - * exit('Login Failed'); - * } - * - * echo $ssh->exec('ls -la'); - * ?> - * - * - * Here's another short example: - * - * login('username', 'password')) { - * exit('Login Failed'); - * } - * - * echo $ssh->read('username@username:~$'); - * $ssh->write("ls -la\n"); - * echo $ssh->read('username@username:~$'); - * ?> - * - * - * More information on the SSHv1 specification can be found by reading - * {@link http://www.snailbook.com/docs/protocol-1.5.txt protocol-1.5.txt}. - * - * @category Net - * @package SSH1 - * @author Jim Wigginton - * @copyright 2007 Jim Wigginton - * @license http://www.opensource.org/licenses/mit-license.html MIT License - * @link http://phpseclib.sourceforge.net - */ - -namespace phpseclib\Net; - -use phpseclib\Crypt\DES; -use phpseclib\Crypt\Random; -use phpseclib\Crypt\TripleDES; -use phpseclib\Math\BigInteger; - -/** - * Pure-PHP implementation of SSHv1. - * - * @package SSH1 - * @author Jim Wigginton - * @access public - */ -class SSH1 -{ - /**#@+ - * Encryption Methods - * - * @see \phpseclib\Net\SSH1::getSupportedCiphers() - * @access public - */ - /** - * No encryption - * - * Not supported. - */ - const CIPHER_NONE = 0; - /** - * IDEA in CFB mode - * - * Not supported. - */ - const CIPHER_IDEA = 1; - /** - * DES in CBC mode - */ - const CIPHER_DES = 2; - /** - * Triple-DES in CBC mode - * - * All implementations are required to support this - */ - const CIPHER_3DES = 3; - /** - * TRI's Simple Stream encryption CBC - * - * Not supported nor is it defined in the official SSH1 specs. OpenSSH, however, does define it (see cipher.h), - * although it doesn't use it (see cipher.c) - */ - const CIPHER_BROKEN_TSS = 4; - /** - * RC4 - * - * Not supported. - * - * @internal According to the SSH1 specs: - * - * "The first 16 bytes of the session key are used as the key for - * the server to client direction. The remaining 16 bytes are used - * as the key for the client to server direction. This gives - * independent 128-bit keys for each direction." - * - * This library currently only supports encryption when the same key is being used for both directions. This is - * because there's only one $crypto object. Two could be added ($encrypt and $decrypt, perhaps). - */ - const CIPHER_RC4 = 5; - /** - * Blowfish - * - * Not supported nor is it defined in the official SSH1 specs. OpenSSH, however, defines it (see cipher.h) and - * uses it (see cipher.c) - */ - const CIPHER_BLOWFISH = 6; - /**#@-*/ - - /**#@+ - * Authentication Methods - * - * @see \phpseclib\Net\SSH1::getSupportedAuthentications() - * @access public - */ - /** - * .rhosts or /etc/hosts.equiv - */ - const AUTH_RHOSTS = 1; - /** - * pure RSA authentication - */ - const AUTH_RSA = 2; - /** - * password authentication - * - * This is the only method that is supported by this library. - */ - const AUTH_PASSWORD = 3; - /** - * .rhosts with RSA host authentication - */ - const AUTH_RHOSTS_RSA = 4; - /**#@-*/ - - /**#@+ - * Terminal Modes - * - * @link http://3sp.com/content/developer/maverick-net/docs/Maverick.SSH.PseudoTerminalModesMembers.html - * @access private - */ - const TTY_OP_END = 0; - /**#@-*/ - - /** - * The Response Type - * - * @see \phpseclib\Net\SSH1::_get_binary_packet() - * @access private - */ - const RESPONSE_TYPE = 1; - - /** - * The Response Data - * - * @see \phpseclib\Net\SSH1::_get_binary_packet() - * @access private - */ - const RESPONSE_DATA = 2; - - /**#@+ - * Execution Bitmap Masks - * - * @see \phpseclib\Net\SSH1::bitmap - * @access private - */ - const MASK_CONSTRUCTOR = 0x00000001; - const MASK_CONNECTED = 0x00000002; - const MASK_LOGIN = 0x00000004; - const MASK_SHELL = 0x00000008; - /**#@-*/ - - /**#@+ - * @access public - * @see \phpseclib\Net\SSH1::getLog() - */ - /** - * Returns the message numbers - */ - const LOG_SIMPLE = 1; - /** - * Returns the message content - */ - const LOG_COMPLEX = 2; - /** - * Outputs the content real-time - */ - const LOG_REALTIME = 3; - /** - * Dumps the content real-time to a file - */ - const LOG_REALTIME_FILE = 4; - /**#@-*/ - - /**#@+ - * @access public - * @see \phpseclib\Net\SSH1::read() - */ - /** - * Returns when a string matching $expect exactly is found - */ - const READ_SIMPLE = 1; - /** - * Returns when a string matching the regular expression $expect is found - */ - const READ_REGEX = 2; - /**#@-*/ - - /** - * The SSH identifier - * - * @var string - * @access private - */ - var $identifier = 'SSH-1.5-phpseclib'; - - /** - * The Socket Object - * - * @var object - * @access private - */ - var $fsock; - - /** - * The cryptography object - * - * @var object - * @access private - */ - var $crypto = false; - - /** - * Execution Bitmap - * - * The bits that are set represent functions that have been called already. This is used to determine - * if a requisite function has been successfully executed. If not, an error should be thrown. - * - * @var int - * @access private - */ - var $bitmap = 0; - - /** - * The Server Key Public Exponent - * - * Logged for debug purposes - * - * @see self::getServerKeyPublicExponent() - * @var string - * @access private - */ - var $server_key_public_exponent; - - /** - * The Server Key Public Modulus - * - * Logged for debug purposes - * - * @see self::getServerKeyPublicModulus() - * @var string - * @access private - */ - var $server_key_public_modulus; - - /** - * The Host Key Public Exponent - * - * Logged for debug purposes - * - * @see self::getHostKeyPublicExponent() - * @var string - * @access private - */ - var $host_key_public_exponent; - - /** - * The Host Key Public Modulus - * - * Logged for debug purposes - * - * @see self::getHostKeyPublicModulus() - * @var string - * @access private - */ - var $host_key_public_modulus; - - /** - * Supported Ciphers - * - * Logged for debug purposes - * - * @see self::getSupportedCiphers() - * @var array - * @access private - */ - var $supported_ciphers = array( - self::CIPHER_NONE => 'No encryption', - self::CIPHER_IDEA => 'IDEA in CFB mode', - self::CIPHER_DES => 'DES in CBC mode', - self::CIPHER_3DES => 'Triple-DES in CBC mode', - self::CIPHER_BROKEN_TSS => 'TRI\'s Simple Stream encryption CBC', - self::CIPHER_RC4 => 'RC4', - self::CIPHER_BLOWFISH => 'Blowfish' - ); - - /** - * Supported Authentications - * - * Logged for debug purposes - * - * @see self::getSupportedAuthentications() - * @var array - * @access private - */ - var $supported_authentications = array( - self::AUTH_RHOSTS => '.rhosts or /etc/hosts.equiv', - self::AUTH_RSA => 'pure RSA authentication', - self::AUTH_PASSWORD => 'password authentication', - self::AUTH_RHOSTS_RSA => '.rhosts with RSA host authentication' - ); - - /** - * Server Identification - * - * @see self::getServerIdentification() - * @var string - * @access private - */ - var $server_identification = ''; - - /** - * Protocol Flags - * - * @see self::__construct() - * @var array - * @access private - */ - var $protocol_flags = array(); - - /** - * Protocol Flag Log - * - * @see self::getLog() - * @var array - * @access private - */ - var $protocol_flag_log = array(); - - /** - * Message Log - * - * @see self::getLog() - * @var array - * @access private - */ - var $message_log = array(); - - /** - * Real-time log file pointer - * - * @see self::_append_log() - * @var resource - * @access private - */ - var $realtime_log_file; - - /** - * Real-time log file size - * - * @see self::_append_log() - * @var int - * @access private - */ - var $realtime_log_size; - - /** - * Real-time log file wrap boolean - * - * @see self::_append_log() - * @var bool - * @access private - */ - var $realtime_log_wrap; - - /** - * Interactive Buffer - * - * @see self::read() - * @var array - * @access private - */ - var $interactiveBuffer = ''; - - /** - * Timeout - * - * @see self::setTimeout() - * @access private - */ - var $timeout; - - /** - * Current Timeout - * - * @see self::_get_channel_packet() - * @access private - */ - var $curTimeout; - - /** - * Log Boundary - * - * @see self::_format_log() - * @access private - */ - var $log_boundary = ':'; - - /** - * Log Long Width - * - * @see self::_format_log() - * @access private - */ - var $log_long_width = 65; - - /** - * Log Short Width - * - * @see self::_format_log() - * @access private - */ - var $log_short_width = 16; - - /** - * Hostname - * - * @see self::__construct() - * @see self::_connect() - * @var string - * @access private - */ - var $host; - - /** - * Port Number - * - * @see self::__construct() - * @see self::_connect() - * @var int - * @access private - */ - var $port; - - /** - * Timeout for initial connection - * - * Set by the constructor call. Calling setTimeout() is optional. If it's not called functions like - * exec() won't timeout unless some PHP setting forces it too. The timeout specified in the constructor, - * however, is non-optional. There will be a timeout, whether or not you set it. If you don't it'll be - * 10 seconds. It is used by fsockopen() in that function. - * - * @see self::__construct() - * @see self::_connect() - * @var int - * @access private - */ - var $connectionTimeout; - - /** - * Default cipher - * - * @see self::__construct() - * @see self::_connect() - * @var int - * @access private - */ - var $cipher; - - /** - * Default Constructor. - * - * Connects to an SSHv1 server - * - * @param string $host - * @param int $port - * @param int $timeout - * @param int $cipher - * @return \phpseclib\Net\SSH1 - * @access public - */ - function __construct($host, $port = 22, $timeout = 10, $cipher = self::CIPHER_3DES) - { - $this->protocol_flags = array( - 1 => 'NET_SSH1_MSG_DISCONNECT', - 2 => 'NET_SSH1_SMSG_PUBLIC_KEY', - 3 => 'NET_SSH1_CMSG_SESSION_KEY', - 4 => 'NET_SSH1_CMSG_USER', - 9 => 'NET_SSH1_CMSG_AUTH_PASSWORD', - 10 => 'NET_SSH1_CMSG_REQUEST_PTY', - 12 => 'NET_SSH1_CMSG_EXEC_SHELL', - 13 => 'NET_SSH1_CMSG_EXEC_CMD', - 14 => 'NET_SSH1_SMSG_SUCCESS', - 15 => 'NET_SSH1_SMSG_FAILURE', - 16 => 'NET_SSH1_CMSG_STDIN_DATA', - 17 => 'NET_SSH1_SMSG_STDOUT_DATA', - 18 => 'NET_SSH1_SMSG_STDERR_DATA', - 19 => 'NET_SSH1_CMSG_EOF', - 20 => 'NET_SSH1_SMSG_EXITSTATUS', - 33 => 'NET_SSH1_CMSG_EXIT_CONFIRMATION' - ); - - $this->_define_array($this->protocol_flags); - - $this->host = $host; - $this->port = $port; - $this->connectionTimeout = $timeout; - $this->cipher = $cipher; - } - - /** - * Connect to an SSHv1 server - * - * @return bool - * @access private - */ - function _connect() - { - $this->fsock = @fsockopen($this->host, $this->port, $errno, $errstr, $this->connectionTimeout); - if (!$this->fsock) { - user_error(rtrim("Cannot connect to {$this->host}:{$this->port}. Error $errno. $errstr")); - return false; - } - - $this->server_identification = $init_line = fgets($this->fsock, 255); - - if (defined('NET_SSH1_LOGGING')) { - $this->_append_log('<-', $this->server_identification); - $this->_append_log('->', $this->identifier . "\r\n"); - } - - if (!preg_match('#SSH-([0-9\.]+)-(.+)#', $init_line, $parts)) { - user_error('Can only connect to SSH servers'); - return false; - } - if ($parts[1][0] != 1) { - user_error("Cannot connect to SSH $parts[1] servers"); - return false; - } - - fputs($this->fsock, $this->identifier."\r\n"); - - $response = $this->_get_binary_packet(); - if ($response[self::RESPONSE_TYPE] != NET_SSH1_SMSG_PUBLIC_KEY) { - user_error('Expected SSH_SMSG_PUBLIC_KEY'); - return false; - } - - $anti_spoofing_cookie = $this->_string_shift($response[self::RESPONSE_DATA], 8); - - $this->_string_shift($response[self::RESPONSE_DATA], 4); - - if (strlen($response[self::RESPONSE_DATA]) < 2) { - return false; - } - $temp = unpack('nlen', $this->_string_shift($response[self::RESPONSE_DATA], 2)); - $server_key_public_exponent = new BigInteger($this->_string_shift($response[self::RESPONSE_DATA], ceil($temp['len'] / 8)), 256); - $this->server_key_public_exponent = $server_key_public_exponent; - - if (strlen($response[self::RESPONSE_DATA]) < 2) { - return false; - } - $temp = unpack('nlen', $this->_string_shift($response[self::RESPONSE_DATA], 2)); - $server_key_public_modulus = new BigInteger($this->_string_shift($response[self::RESPONSE_DATA], ceil($temp['len'] / 8)), 256); - - $this->server_key_public_modulus = $server_key_public_modulus; - - $this->_string_shift($response[self::RESPONSE_DATA], 4); - - if (strlen($response[self::RESPONSE_DATA]) < 2) { - return false; - } - $temp = unpack('nlen', $this->_string_shift($response[self::RESPONSE_DATA], 2)); - $host_key_public_exponent = new BigInteger($this->_string_shift($response[self::RESPONSE_DATA], ceil($temp['len'] / 8)), 256); - $this->host_key_public_exponent = $host_key_public_exponent; - - if (strlen($response[self::RESPONSE_DATA]) < 2) { - return false; - } - $temp = unpack('nlen', $this->_string_shift($response[self::RESPONSE_DATA], 2)); - $host_key_public_modulus = new BigInteger($this->_string_shift($response[self::RESPONSE_DATA], ceil($temp['len'] / 8)), 256); - - $this->host_key_public_modulus = $host_key_public_modulus; - - $this->_string_shift($response[self::RESPONSE_DATA], 4); - - // get a list of the supported ciphers - if (strlen($response[self::RESPONSE_DATA]) < 4) { - return false; - } - extract(unpack('Nsupported_ciphers_mask', $this->_string_shift($response[self::RESPONSE_DATA], 4))); - - foreach ($this->supported_ciphers as $mask => $name) { - if (($supported_ciphers_mask & (1 << $mask)) == 0) { - unset($this->supported_ciphers[$mask]); - } - } - - // get a list of the supported authentications - if (strlen($response[self::RESPONSE_DATA]) < 4) { - return false; - } - extract(unpack('Nsupported_authentications_mask', $this->_string_shift($response[self::RESPONSE_DATA], 4))); - foreach ($this->supported_authentications as $mask => $name) { - if (($supported_authentications_mask & (1 << $mask)) == 0) { - unset($this->supported_authentications[$mask]); - } - } - - $session_id = pack('H*', md5($host_key_public_modulus->toBytes() . $server_key_public_modulus->toBytes() . $anti_spoofing_cookie)); - - $session_key = Random::string(32); - $double_encrypted_session_key = $session_key ^ str_pad($session_id, 32, chr(0)); - - if ($server_key_public_modulus->compare($host_key_public_modulus) < 0) { - $double_encrypted_session_key = $this->_rsa_crypt( - $double_encrypted_session_key, - array( - $server_key_public_exponent, - $server_key_public_modulus - ) - ); - $double_encrypted_session_key = $this->_rsa_crypt( - $double_encrypted_session_key, - array( - $host_key_public_exponent, - $host_key_public_modulus - ) - ); - } else { - $double_encrypted_session_key = $this->_rsa_crypt( - $double_encrypted_session_key, - array( - $host_key_public_exponent, - $host_key_public_modulus - ) - ); - $double_encrypted_session_key = $this->_rsa_crypt( - $double_encrypted_session_key, - array( - $server_key_public_exponent, - $server_key_public_modulus - ) - ); - } - - $cipher = isset($this->supported_ciphers[$this->cipher]) ? $this->cipher : self::CIPHER_3DES; - $data = pack('C2a*na*N', NET_SSH1_CMSG_SESSION_KEY, $cipher, $anti_spoofing_cookie, 8 * strlen($double_encrypted_session_key), $double_encrypted_session_key, 0); - - if (!$this->_send_binary_packet($data)) { - user_error('Error sending SSH_CMSG_SESSION_KEY'); - return false; - } - - switch ($cipher) { - //case self::CIPHER_NONE: - // $this->crypto = new \phpseclib\Crypt\Null(); - // break; - case self::CIPHER_DES: - $this->crypto = new DES(); - $this->crypto->disablePadding(); - $this->crypto->enableContinuousBuffer(); - $this->crypto->setKey(substr($session_key, 0, 8)); - break; - case self::CIPHER_3DES: - $this->crypto = new TripleDES(TripleDES::MODE_3CBC); - $this->crypto->disablePadding(); - $this->crypto->enableContinuousBuffer(); - $this->crypto->setKey(substr($session_key, 0, 24)); - break; - //case self::CIPHER_RC4: - // $this->crypto = new RC4(); - // $this->crypto->enableContinuousBuffer(); - // $this->crypto->setKey(substr($session_key, 0, 16)); - // break; - } - - $response = $this->_get_binary_packet(); - - if ($response[self::RESPONSE_TYPE] != NET_SSH1_SMSG_SUCCESS) { - user_error('Expected SSH_SMSG_SUCCESS'); - return false; - } - - $this->bitmap = self::MASK_CONNECTED; - - return true; - } - - /** - * Login - * - * @param string $username - * @param string $password - * @return bool - * @access public - */ - function login($username, $password = '') - { - if (!($this->bitmap & self::MASK_CONSTRUCTOR)) { - $this->bitmap |= self::MASK_CONSTRUCTOR; - if (!$this->_connect()) { - return false; - } - } - - if (!($this->bitmap & self::MASK_CONNECTED)) { - return false; - } - - $data = pack('CNa*', NET_SSH1_CMSG_USER, strlen($username), $username); - - if (!$this->_send_binary_packet($data)) { - user_error('Error sending SSH_CMSG_USER'); - return false; - } - - $response = $this->_get_binary_packet(); - - if ($response === true) { - return false; - } - if ($response[self::RESPONSE_TYPE] == NET_SSH1_SMSG_SUCCESS) { - $this->bitmap |= self::MASK_LOGIN; - return true; - } elseif ($response[self::RESPONSE_TYPE] != NET_SSH1_SMSG_FAILURE) { - user_error('Expected SSH_SMSG_SUCCESS or SSH_SMSG_FAILURE'); - return false; - } - - $data = pack('CNa*', NET_SSH1_CMSG_AUTH_PASSWORD, strlen($password), $password); - - if (!$this->_send_binary_packet($data)) { - user_error('Error sending SSH_CMSG_AUTH_PASSWORD'); - return false; - } - - // remove the username and password from the last logged packet - if (defined('NET_SSH1_LOGGING') && NET_SSH1_LOGGING == self::LOG_COMPLEX) { - $data = pack('CNa*', NET_SSH1_CMSG_AUTH_PASSWORD, strlen('password'), 'password'); - $this->message_log[count($this->message_log) - 1] = $data; - } - - $response = $this->_get_binary_packet(); - - if ($response === true) { - return false; - } - if ($response[self::RESPONSE_TYPE] == NET_SSH1_SMSG_SUCCESS) { - $this->bitmap |= self::MASK_LOGIN; - return true; - } elseif ($response[self::RESPONSE_TYPE] == NET_SSH1_SMSG_FAILURE) { - return false; - } else { - user_error('Expected SSH_SMSG_SUCCESS or SSH_SMSG_FAILURE'); - return false; - } - } - - /** - * Set Timeout - * - * $ssh->exec('ping 127.0.0.1'); on a Linux host will never return and will run indefinitely. setTimeout() makes it so it'll timeout. - * Setting $timeout to false or 0 will mean there is no timeout. - * - * @param mixed $timeout - */ - function setTimeout($timeout) - { - $this->timeout = $this->curTimeout = $timeout; - } - - /** - * Executes a command on a non-interactive shell, returns the output, and quits. - * - * An SSH1 server will close the connection after a command has been executed on a non-interactive shell. SSH2 - * servers don't, however, this isn't an SSH2 client. The way this works, on the server, is by initiating a - * shell with the -s option, as discussed in the following links: - * - * {@link http://www.faqs.org/docs/bashman/bashref_65.html http://www.faqs.org/docs/bashman/bashref_65.html} - * {@link http://www.faqs.org/docs/bashman/bashref_62.html http://www.faqs.org/docs/bashman/bashref_62.html} - * - * To execute further commands, a new \phpseclib\Net\SSH1 object will need to be created. - * - * Returns false on failure and the output, otherwise. - * - * @see self::interactiveRead() - * @see self::interactiveWrite() - * @param string $cmd - * @param bool $block - * @return mixed - * @access public - */ - function exec($cmd, $block = true) - { - if (!($this->bitmap & self::MASK_LOGIN)) { - user_error('Operation disallowed prior to login()'); - return false; - } - - $data = pack('CNa*', NET_SSH1_CMSG_EXEC_CMD, strlen($cmd), $cmd); - - if (!$this->_send_binary_packet($data)) { - user_error('Error sending SSH_CMSG_EXEC_CMD'); - return false; - } - - if (!$block) { - return true; - } - - $output = ''; - $response = $this->_get_binary_packet(); - - if ($response !== false) { - do { - $output.= substr($response[self::RESPONSE_DATA], 4); - $response = $this->_get_binary_packet(); - } while (is_array($response) && $response[self::RESPONSE_TYPE] != NET_SSH1_SMSG_EXITSTATUS); - } - - $data = pack('C', NET_SSH1_CMSG_EXIT_CONFIRMATION); - - // i don't think it's really all that important if this packet gets sent or not. - $this->_send_binary_packet($data); - - fclose($this->fsock); - - // reset the execution bitmap - a new \phpseclib\Net\SSH1 object needs to be created. - $this->bitmap = 0; - - return $output; - } - - /** - * Creates an interactive shell - * - * @see self::interactiveRead() - * @see self::interactiveWrite() - * @return bool - * @access private - */ - function _initShell() - { - // connect using the sample parameters in protocol-1.5.txt. - // according to wikipedia.org's entry on text terminals, "the fundamental type of application running on a text - // terminal is a command line interpreter or shell". thus, opening a terminal session to run the shell. - $data = pack('CNa*N4C', NET_SSH1_CMSG_REQUEST_PTY, strlen('vt100'), 'vt100', 24, 80, 0, 0, self::TTY_OP_END); - - if (!$this->_send_binary_packet($data)) { - user_error('Error sending SSH_CMSG_REQUEST_PTY'); - return false; - } - - $response = $this->_get_binary_packet(); - - if ($response === true) { - return false; - } - if ($response[self::RESPONSE_TYPE] != NET_SSH1_SMSG_SUCCESS) { - user_error('Expected SSH_SMSG_SUCCESS'); - return false; - } - - $data = pack('C', NET_SSH1_CMSG_EXEC_SHELL); - - if (!$this->_send_binary_packet($data)) { - user_error('Error sending SSH_CMSG_EXEC_SHELL'); - return false; - } - - $this->bitmap |= self::MASK_SHELL; - - //stream_set_blocking($this->fsock, 0); - - return true; - } - - /** - * Inputs a command into an interactive shell. - * - * @see self::interactiveWrite() - * @param string $cmd - * @return bool - * @access public - */ - function write($cmd) - { - return $this->interactiveWrite($cmd); - } - - /** - * Returns the output of an interactive shell when there's a match for $expect - * - * $expect can take the form of a string literal or, if $mode == self::READ_REGEX, - * a regular expression. - * - * @see self::write() - * @param string $expect - * @param int $mode - * @return bool - * @access public - */ - function read($expect, $mode = self::READ_SIMPLE) - { - if (!($this->bitmap & self::MASK_LOGIN)) { - user_error('Operation disallowed prior to login()'); - return false; - } - - if (!($this->bitmap & self::MASK_SHELL) && !$this->_initShell()) { - user_error('Unable to initiate an interactive shell session'); - return false; - } - - $match = $expect; - while (true) { - if ($mode == self::READ_REGEX) { - preg_match($expect, $this->interactiveBuffer, $matches); - $match = isset($matches[0]) ? $matches[0] : ''; - } - $pos = strlen($match) ? strpos($this->interactiveBuffer, $match) : false; - if ($pos !== false) { - return $this->_string_shift($this->interactiveBuffer, $pos + strlen($match)); - } - $response = $this->_get_binary_packet(); - - if ($response === true) { - return $this->_string_shift($this->interactiveBuffer, strlen($this->interactiveBuffer)); - } - $this->interactiveBuffer.= substr($response[self::RESPONSE_DATA], 4); - } - } - - /** - * Inputs a command into an interactive shell. - * - * @see self::interactiveRead() - * @param string $cmd - * @return bool - * @access public - */ - function interactiveWrite($cmd) - { - if (!($this->bitmap & self::MASK_LOGIN)) { - user_error('Operation disallowed prior to login()'); - return false; - } - - if (!($this->bitmap & self::MASK_SHELL) && !$this->_initShell()) { - user_error('Unable to initiate an interactive shell session'); - return false; - } - - $data = pack('CNa*', NET_SSH1_CMSG_STDIN_DATA, strlen($cmd), $cmd); - - if (!$this->_send_binary_packet($data)) { - user_error('Error sending SSH_CMSG_STDIN'); - return false; - } - - return true; - } - - /** - * Returns the output of an interactive shell when no more output is available. - * - * Requires PHP 4.3.0 or later due to the use of the stream_select() function. If you see stuff like - * "^[[00m", you're seeing ANSI escape codes. According to - * {@link http://support.microsoft.com/kb/101875 How to Enable ANSI.SYS in a Command Window}, "Windows NT - * does not support ANSI escape sequences in Win32 Console applications", so if you're a Windows user, - * there's not going to be much recourse. - * - * @see self::interactiveRead() - * @return string - * @access public - */ - function interactiveRead() - { - if (!($this->bitmap & self::MASK_LOGIN)) { - user_error('Operation disallowed prior to login()'); - return false; - } - - if (!($this->bitmap & self::MASK_SHELL) && !$this->_initShell()) { - user_error('Unable to initiate an interactive shell session'); - return false; - } - - $read = array($this->fsock); - $write = $except = null; - if (stream_select($read, $write, $except, 0)) { - $response = $this->_get_binary_packet(); - return substr($response[self::RESPONSE_DATA], 4); - } else { - return ''; - } - } - - /** - * Disconnect - * - * @access public - */ - function disconnect() - { - $this->_disconnect(); - } - - /** - * Destructor. - * - * Will be called, automatically, if you're supporting just PHP5. If you're supporting PHP4, you'll need to call - * disconnect(). - * - * @access public - */ - function __destruct() - { - $this->_disconnect(); - } - - /** - * Disconnect - * - * @param string $msg - * @access private - */ - function _disconnect($msg = 'Client Quit') - { - if ($this->bitmap) { - $data = pack('C', NET_SSH1_CMSG_EOF); - $this->_send_binary_packet($data); - /* - $response = $this->_get_binary_packet(); - if ($response === true) { - $response = array(self::RESPONSE_TYPE => -1); - } - switch ($response[self::RESPONSE_TYPE]) { - case NET_SSH1_SMSG_EXITSTATUS: - $data = pack('C', NET_SSH1_CMSG_EXIT_CONFIRMATION); - break; - default: - $data = pack('CNa*', NET_SSH1_MSG_DISCONNECT, strlen($msg), $msg); - } - */ - $data = pack('CNa*', NET_SSH1_MSG_DISCONNECT, strlen($msg), $msg); - - $this->_send_binary_packet($data); - fclose($this->fsock); - $this->bitmap = 0; - } - } - - /** - * Gets Binary Packets - * - * See 'The Binary Packet Protocol' of protocol-1.5.txt for more info. - * - * Also, this function could be improved upon by adding detection for the following exploit: - * http://www.securiteam.com/securitynews/5LP042K3FY.html - * - * @see self::_send_binary_packet() - * @return array - * @access private - */ - function _get_binary_packet() - { - if (feof($this->fsock)) { - //user_error('connection closed prematurely'); - return false; - } - - if ($this->curTimeout) { - $read = array($this->fsock); - $write = $except = null; - - $start = strtok(microtime(), ' ') + strtok(''); // http://php.net/microtime#61838 - $sec = floor($this->curTimeout); - $usec = 1000000 * ($this->curTimeout - $sec); - // on windows this returns a "Warning: Invalid CRT parameters detected" error - if (!@stream_select($read, $write, $except, $sec, $usec) && !count($read)) { - //$this->_disconnect('Timeout'); - return true; - } - $elapsed = strtok(microtime(), ' ') + strtok('') - $start; - $this->curTimeout-= $elapsed; - } - - $start = strtok(microtime(), ' ') + strtok(''); // http://php.net/microtime#61838 - $data = fread($this->fsock, 4); - if (strlen($data) < 4) { - return false; - } - $temp = unpack('Nlength', $data); - - $padding_length = 8 - ($temp['length'] & 7); - $length = $temp['length'] + $padding_length; - $raw = ''; - - while ($length > 0) { - $temp = fread($this->fsock, $length); - if (strlen($temp) != $length) { - return false; - } - $raw.= $temp; - $length-= strlen($temp); - } - $stop = strtok(microtime(), ' ') + strtok(''); - - if (strlen($raw) && $this->crypto !== false) { - $raw = $this->crypto->decrypt($raw); - } - - $padding = substr($raw, 0, $padding_length); - $type = $raw[$padding_length]; - $data = substr($raw, $padding_length + 1, -4); - - if (strlen($raw) < 4) { - return false; - } - $temp = unpack('Ncrc', substr($raw, -4)); - - //if ( $temp['crc'] != $this->_crc($padding . $type . $data) ) { - // user_error('Bad CRC in packet from server'); - // return false; - //} - - $type = ord($type); - - if (defined('NET_SSH1_LOGGING')) { - $temp = isset($this->protocol_flags[$type]) ? $this->protocol_flags[$type] : 'UNKNOWN'; - $temp = '<- ' . $temp . - ' (' . round($stop - $start, 4) . 's)'; - $this->_append_log($temp, $data); - } - - return array( - self::RESPONSE_TYPE => $type, - self::RESPONSE_DATA => $data - ); - } - - /** - * Sends Binary Packets - * - * Returns true on success, false on failure. - * - * @see self::_get_binary_packet() - * @param string $data - * @return bool - * @access private - */ - function _send_binary_packet($data) - { - if (feof($this->fsock)) { - //user_error('connection closed prematurely'); - return false; - } - - $length = strlen($data) + 4; - - $padding = Random::string(8 - ($length & 7)); - - $orig = $data; - $data = $padding . $data; - $data.= pack('N', $this->_crc($data)); - - if ($this->crypto !== false) { - $data = $this->crypto->encrypt($data); - } - - $packet = pack('Na*', $length, $data); - - $start = strtok(microtime(), ' ') + strtok(''); // http://php.net/microtime#61838 - $result = strlen($packet) == fputs($this->fsock, $packet); - $stop = strtok(microtime(), ' ') + strtok(''); - - if (defined('NET_SSH1_LOGGING')) { - $temp = isset($this->protocol_flags[ord($orig[0])]) ? $this->protocol_flags[ord($orig[0])] : 'UNKNOWN'; - $temp = '-> ' . $temp . - ' (' . round($stop - $start, 4) . 's)'; - $this->_append_log($temp, $orig); - } - - return $result; - } - - /** - * Cyclic Redundancy Check (CRC) - * - * PHP's crc32 function is implemented slightly differently than the one that SSH v1 uses, so - * we've reimplemented it. A more detailed discussion of the differences can be found after - * $crc_lookup_table's initialization. - * - * @see self::_get_binary_packet() - * @see self::_send_binary_packet() - * @param string $data - * @return int - * @access private - */ - function _crc($data) - { - static $crc_lookup_table = array( - 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, - 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, - 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, - 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, - 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, - 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, - 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, - 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, - 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, - 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, - 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, - 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, - 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, - 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, - 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, - 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, - 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, - 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, - 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, - 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, - 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, - 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, - 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, - 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, - 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, - 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, - 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, - 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, - 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, - 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, - 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, - 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, - 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, - 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, - 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, - 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, - 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, - 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, - 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, - 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, - 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, - 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, - 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, - 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, - 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, - 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, - 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, - 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, - 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, - 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, - 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, - 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, - 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, - 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, - 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, - 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, - 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, - 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, - 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, - 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, - 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, - 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, - 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, - 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D - ); - - // For this function to yield the same output as PHP's crc32 function, $crc would have to be - // set to 0xFFFFFFFF, initially - not 0x00000000 as it currently is. - $crc = 0x00000000; - $length = strlen($data); - - for ($i=0; $i<$length; $i++) { - // We AND $crc >> 8 with 0x00FFFFFF because we want the eight newly added bits to all - // be zero. PHP, unfortunately, doesn't always do this. 0x80000000 >> 8, as an example, - // yields 0xFF800000 - not 0x00800000. The following link elaborates: - // http://www.php.net/manual/en/language.operators.bitwise.php#57281 - $crc = (($crc >> 8) & 0x00FFFFFF) ^ $crc_lookup_table[($crc & 0xFF) ^ ord($data[$i])]; - } - - // In addition to having to set $crc to 0xFFFFFFFF, initially, the return value must be XOR'd with - // 0xFFFFFFFF for this function to return the same thing that PHP's crc32 function would. - return $crc; - } - - /** - * String Shift - * - * Inspired by array_shift - * - * @param string $string - * @param int $index - * @return string - * @access private - */ - function _string_shift(&$string, $index = 1) - { - $substr = substr($string, 0, $index); - $string = substr($string, $index); - return $substr; - } - - /** - * RSA Encrypt - * - * Returns mod(pow($m, $e), $n), where $n should be the product of two (large) primes $p and $q and where $e - * should be a number with the property that gcd($e, ($p - 1) * ($q - 1)) == 1. Could just make anything that - * calls this call modexp, instead, but I think this makes things clearer, maybe... - * - * @see self::__construct() - * @param BigInteger $m - * @param array $key - * @return BigInteger - * @access private - */ - function _rsa_crypt($m, $key) - { - /* - $rsa = new RSA(); - $rsa->loadKey($key, RSA::PUBLIC_FORMAT_RAW); - $rsa->setEncryptionMode(RSA::ENCRYPTION_PKCS1); - return $rsa->encrypt($m); - */ - - // To quote from protocol-1.5.txt: - // The most significant byte (which is only partial as the value must be - // less than the public modulus, which is never a power of two) is zero. - // - // The next byte contains the value 2 (which stands for public-key - // encrypted data in the PKCS standard [PKCS#1]). Then, there are non- - // zero random bytes to fill any unused space, a zero byte, and the data - // to be encrypted in the least significant bytes, the last byte of the - // data in the least significant byte. - - // Presumably the part of PKCS#1 they're refering to is "Section 7.2.1 Encryption Operation", - // under "7.2 RSAES-PKCS1-v1.5" and "7 Encryption schemes" of the following URL: - // ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-1/pkcs-1v2-1.pdf - $modulus = $key[1]->toBytes(); - $length = strlen($modulus) - strlen($m) - 3; - $random = ''; - while (strlen($random) != $length) { - $block = Random::string($length - strlen($random)); - $block = str_replace("\x00", '', $block); - $random.= $block; - } - $temp = chr(0) . chr(2) . $random . chr(0) . $m; - - $m = new BigInteger($temp, 256); - $m = $m->modPow($key[0], $key[1]); - - return $m->toBytes(); - } - - /** - * Define Array - * - * Takes any number of arrays whose indices are integers and whose values are strings and defines a bunch of - * named constants from it, using the value as the name of the constant and the index as the value of the constant. - * If any of the constants that would be defined already exists, none of the constants will be defined. - * - * @access private - */ - function _define_array() - { - $args = func_get_args(); - foreach ($args as $arg) { - foreach ($arg as $key => $value) { - if (!defined($value)) { - define($value, $key); - } else { - break 2; - } - } - } - } - - /** - * Returns a log of the packets that have been sent and received. - * - * Returns a string if NET_SSH1_LOGGING == self::LOG_COMPLEX, an array if NET_SSH1_LOGGING == self::LOG_SIMPLE and false if !defined('NET_SSH1_LOGGING') - * - * @access public - * @return array|false|string - */ - function getLog() - { - if (!defined('NET_SSH1_LOGGING')) { - return false; - } - - switch (NET_SSH1_LOGGING) { - case self::LOG_SIMPLE: - return $this->message_number_log; - break; - case self::LOG_COMPLEX: - return $this->_format_log($this->message_log, $this->protocol_flags_log); - break; - default: - return false; - } - } - - /** - * Formats a log for printing - * - * @param array $message_log - * @param array $message_number_log - * @access private - * @return string - */ - function _format_log($message_log, $message_number_log) - { - $output = ''; - for ($i = 0; $i < count($message_log); $i++) { - $output.= $message_number_log[$i] . "\r\n"; - $current_log = $message_log[$i]; - $j = 0; - do { - if (strlen($current_log)) { - $output.= str_pad(dechex($j), 7, '0', STR_PAD_LEFT) . '0 '; - } - $fragment = $this->_string_shift($current_log, $this->log_short_width); - $hex = substr(preg_replace_callback('#.#s', array($this, '_format_log_helper'), $fragment), strlen($this->log_boundary)); - // replace non ASCII printable characters with dots - // http://en.wikipedia.org/wiki/ASCII#ASCII_printable_characters - // also replace < with a . since < messes up the output on web browsers - $raw = preg_replace('#[^\x20-\x7E]|<#', '.', $fragment); - $output.= str_pad($hex, $this->log_long_width - $this->log_short_width, ' ') . $raw . "\r\n"; - $j++; - } while (strlen($current_log)); - $output.= "\r\n"; - } - - return $output; - } - - /** - * Helper function for _format_log - * - * For use with preg_replace_callback() - * - * @param array $matches - * @access private - * @return string - */ - function _format_log_helper($matches) - { - return $this->log_boundary . str_pad(dechex(ord($matches[0])), 2, '0', STR_PAD_LEFT); - } - - /** - * Return the server key public exponent - * - * Returns, by default, the base-10 representation. If $raw_output is set to true, returns, instead, - * the raw bytes. This behavior is similar to PHP's md5() function. - * - * @param bool $raw_output - * @return string - * @access public - */ - function getServerKeyPublicExponent($raw_output = false) - { - return $raw_output ? $this->server_key_public_exponent->toBytes() : $this->server_key_public_exponent->toString(); - } - - /** - * Return the server key public modulus - * - * Returns, by default, the base-10 representation. If $raw_output is set to true, returns, instead, - * the raw bytes. This behavior is similar to PHP's md5() function. - * - * @param bool $raw_output - * @return string - * @access public - */ - function getServerKeyPublicModulus($raw_output = false) - { - return $raw_output ? $this->server_key_public_modulus->toBytes() : $this->server_key_public_modulus->toString(); - } - - /** - * Return the host key public exponent - * - * Returns, by default, the base-10 representation. If $raw_output is set to true, returns, instead, - * the raw bytes. This behavior is similar to PHP's md5() function. - * - * @param bool $raw_output - * @return string - * @access public - */ - function getHostKeyPublicExponent($raw_output = false) - { - return $raw_output ? $this->host_key_public_exponent->toBytes() : $this->host_key_public_exponent->toString(); - } - - /** - * Return the host key public modulus - * - * Returns, by default, the base-10 representation. If $raw_output is set to true, returns, instead, - * the raw bytes. This behavior is similar to PHP's md5() function. - * - * @param bool $raw_output - * @return string - * @access public - */ - function getHostKeyPublicModulus($raw_output = false) - { - return $raw_output ? $this->host_key_public_modulus->toBytes() : $this->host_key_public_modulus->toString(); - } - - /** - * Return a list of ciphers supported by SSH1 server. - * - * Just because a cipher is supported by an SSH1 server doesn't mean it's supported by this library. If $raw_output - * is set to true, returns, instead, an array of constants. ie. instead of array('Triple-DES in CBC mode'), you'll - * get array(self::CIPHER_3DES). - * - * @param bool $raw_output - * @return array - * @access public - */ - function getSupportedCiphers($raw_output = false) - { - return $raw_output ? array_keys($this->supported_ciphers) : array_values($this->supported_ciphers); - } - - /** - * Return a list of authentications supported by SSH1 server. - * - * Just because a cipher is supported by an SSH1 server doesn't mean it's supported by this library. If $raw_output - * is set to true, returns, instead, an array of constants. ie. instead of array('password authentication'), you'll - * get array(self::AUTH_PASSWORD). - * - * @param bool $raw_output - * @return array - * @access public - */ - function getSupportedAuthentications($raw_output = false) - { - return $raw_output ? array_keys($this->supported_authentications) : array_values($this->supported_authentications); - } - - /** - * Return the server identification. - * - * @return string - * @access public - */ - function getServerIdentification() - { - return rtrim($this->server_identification); - } - - /** - * Logs data packets - * - * Makes sure that only the last 1MB worth of packets will be logged - * - * @param int $protocol_flags - * @param string $message - * @access private - */ - function _append_log($protocol_flags, $message) - { - switch (NET_SSH1_LOGGING) { - // useful for benchmarks - case self::LOG_SIMPLE: - $this->protocol_flags_log[] = $protocol_flags; - break; - // the most useful log for SSH1 - case self::LOG_COMPLEX: - $this->protocol_flags_log[] = $protocol_flags; - $this->_string_shift($message); - $this->log_size+= strlen($message); - $this->message_log[] = $message; - while ($this->log_size > self::LOG_MAX_SIZE) { - $this->log_size-= strlen(array_shift($this->message_log)); - array_shift($this->protocol_flags_log); - } - break; - // dump the output out realtime; packets may be interspersed with non packets, - // passwords won't be filtered out and select other packets may not be correctly - // identified - case self::LOG_REALTIME: - echo "
\r\n" . $this->_format_log(array($message), array($protocol_flags)) . "\r\n
\r\n"; - @flush(); - @ob_flush(); - break; - // basically the same thing as self::LOG_REALTIME with the caveat that self::LOG_REALTIME_FILE - // needs to be defined and that the resultant log file will be capped out at self::LOG_MAX_SIZE. - // the earliest part of the log file is denoted by the first <<< START >>> and is not going to necessarily - // at the beginning of the file - case self::LOG_REALTIME_FILE: - if (!isset($this->realtime_log_file)) { - // PHP doesn't seem to like using constants in fopen() - $filename = self::LOG_REALTIME_FILE; - $fp = fopen($filename, 'w'); - $this->realtime_log_file = $fp; - } - if (!is_resource($this->realtime_log_file)) { - break; - } - $entry = $this->_format_log(array($message), array($protocol_flags)); - if ($this->realtime_log_wrap) { - $temp = "<<< START >>>\r\n"; - $entry.= $temp; - fseek($this->realtime_log_file, ftell($this->realtime_log_file) - strlen($temp)); - } - $this->realtime_log_size+= strlen($entry); - if ($this->realtime_log_size > self::LOG_MAX_SIZE) { - fseek($this->realtime_log_file, 0); - $this->realtime_log_size = strlen($entry); - $this->realtime_log_wrap = true; - } - fputs($this->realtime_log_file, $entry); - } - } -} diff --git a/vendor/phpseclib/phpseclib/phpseclib/Net/SSH2.php b/vendor/phpseclib/phpseclib/phpseclib/Net/SSH2.php index f8f8dcfd..25e7c912 100644 --- a/vendor/phpseclib/phpseclib/phpseclib/Net/SSH2.php +++ b/vendor/phpseclib/phpseclib/phpseclib/Net/SSH2.php @@ -10,7 +10,7 @@ * login('username', 'password')) { * exit('Login Failed'); * } @@ -24,11 +24,9 @@ * setPassword('whatever'); - * $key->loadKey(file_get_contents('privatekey')); + * $key = \phpseclib3\Crypt\PublicKeyLoader::load('...', '(optional) password'); * - * $ssh = new \phpseclib\Net\SSH2('www.domain.tld'); + * $ssh = new \phpseclib3\Net\SSH2('www.domain.tld'); * if (!$ssh->login('username', $key)) { * exit('Login Failed'); * } @@ -39,84 +37,104 @@ * ?> * * - * @category Net - * @package SSH2 * @author Jim Wigginton * @copyright 2007 Jim Wigginton * @license http://www.opensource.org/licenses/mit-license.html MIT License * @link http://phpseclib.sourceforge.net */ -namespace phpseclib\Net; - -use phpseclib\Crypt\Base; -use phpseclib\Crypt\Blowfish; -use phpseclib\Crypt\Hash; -use phpseclib\Crypt\Random; -use phpseclib\Crypt\RC4; -use phpseclib\Crypt\Rijndael; -use phpseclib\Crypt\RSA; -use phpseclib\Crypt\TripleDES; -use phpseclib\Crypt\Twofish; -use phpseclib\Math\BigInteger; // Used to do Diffie-Hellman key exchange and DSA/RSA signature verification. -use phpseclib\System\SSH\Agent; +namespace phpseclib3\Net; + +use phpseclib3\Common\Functions\Strings; +use phpseclib3\Crypt\Blowfish; +use phpseclib3\Crypt\ChaCha20; +use phpseclib3\Crypt\Common\AsymmetricKey; +use phpseclib3\Crypt\Common\PrivateKey; +use phpseclib3\Crypt\Common\PublicKey; +use phpseclib3\Crypt\Common\SymmetricKey; +use phpseclib3\Crypt\DH; +use phpseclib3\Crypt\DSA; +use phpseclib3\Crypt\EC; +use phpseclib3\Crypt\Hash; +use phpseclib3\Crypt\Random; +use phpseclib3\Crypt\RC4; +use phpseclib3\Crypt\Rijndael; +use phpseclib3\Crypt\RSA; +use phpseclib3\Crypt\TripleDES; // Used to do Diffie-Hellman key exchange and DSA/RSA signature verification. +use phpseclib3\Crypt\Twofish; +use phpseclib3\Exception\ConnectionClosedException; +use phpseclib3\Exception\InsufficientSetupException; +use phpseclib3\Exception\NoSupportedAlgorithmsException; +use phpseclib3\Exception\UnableToConnectException; +use phpseclib3\Exception\UnsupportedAlgorithmException; +use phpseclib3\Exception\UnsupportedCurveException; +use phpseclib3\Math\BigInteger; +use phpseclib3\System\SSH\Agent; /** * Pure-PHP implementation of SSHv2. * - * @package SSH2 * @author Jim Wigginton - * @access public */ class SSH2 { /**#@+ - * Execution Bitmap Masks + * Compression Types * - * @see \phpseclib\Net\SSH2::bitmap - * @access private */ + /** + * No compression + */ + const NET_SSH2_COMPRESSION_NONE = 1; + /** + * zlib compression + */ + const NET_SSH2_COMPRESSION_ZLIB = 2; + /** + * zlib@openssh.com + */ + const NET_SSH2_COMPRESSION_ZLIB_AT_OPENSSH = 3; + /**#@-*/ + + // Execution Bitmap Masks const MASK_CONSTRUCTOR = 0x00000001; const MASK_CONNECTED = 0x00000002; const MASK_LOGIN_REQ = 0x00000004; const MASK_LOGIN = 0x00000008; const MASK_SHELL = 0x00000010; const MASK_WINDOW_ADJUST = 0x00000020; - /**#@-*/ - /**#@+ + /* * Channel constants * * RFC4254 refers not to client and server channels but rather to sender and recipient channels. we don't refer * to them in that way because RFC4254 toggles the meaning. the client sends a SSH_MSG_CHANNEL_OPEN message with * a sender channel and the server sends a SSH_MSG_CHANNEL_OPEN_CONFIRMATION in response, with a sender and a - * recepient channel. at first glance, you might conclude that SSH_MSG_CHANNEL_OPEN_CONFIRMATION's sender channel - * would be the same thing as SSH_MSG_CHANNEL_OPEN's sender channel, but it's not, per this snipet: + * recipient channel. at first glance, you might conclude that SSH_MSG_CHANNEL_OPEN_CONFIRMATION's sender channel + * would be the same thing as SSH_MSG_CHANNEL_OPEN's sender channel, but it's not, per this snippet: * The 'recipient channel' is the channel number given in the original * open request, and 'sender channel' is the channel number allocated by * the other side. * - * @see \phpseclib\Net\SSH2::_send_channel_packet() - * @see \phpseclib\Net\SSH2::_get_channel_packet() - * @access private - */ + * @see \phpseclib3\Net\SSH2::send_channel_packet() + * @see \phpseclib3\Net\SSH2::get_channel_packet() + */ const CHANNEL_EXEC = 1; // PuTTy uses 0x100 const CHANNEL_SHELL = 2; const CHANNEL_SUBSYSTEM = 3; const CHANNEL_AGENT_FORWARD = 4; const CHANNEL_KEEP_ALIVE = 5; - /**#@-*/ - /**#@+ - * @access public - * @see \phpseclib\Net\SSH2::getLog() - */ /** * Returns the message numbers + * + * @see \phpseclib3\Net\SSH2::getLog() */ const LOG_SIMPLE = 1; /** * Returns the message content + * + * @see \phpseclib3\Net\SSH2::getLog() */ const LOG_COMPLEX = 2; /** @@ -127,22 +145,27 @@ class SSH2 * Dumps the content real-time to a file */ const LOG_REALTIME_FILE = 4; + /** + * Outputs the message numbers real-time + */ + const LOG_SIMPLE_REALTIME = 5; /** * Make sure that the log never gets larger than this + * + * @see \phpseclib3\Net\SSH2::getLog() */ const LOG_MAX_SIZE = 1048576; // 1024 * 1024 - /**#@-*/ - /**#@+ - * @access public - * @see \phpseclib\Net\SSH2::read() - */ /** * Returns when a string matching $expect exactly is found + * + * @see \phpseclib3\Net\SSH2::read() */ const READ_SIMPLE = 1; /** * Returns when a string matching the regular expression $expect is found + * + * @see \phpseclib3\Net\SSH2::read() */ const READ_REGEX = 2; /** @@ -150,25 +173,24 @@ class SSH2 * * Some data packets may only contain a single character so it may be necessary * to call read() multiple times when using this option + * + * @see \phpseclib3\Net\SSH2::read() */ const READ_NEXT = 3; - /**#@-*/ /** * The SSH identifier * * @var string - * @access private */ - var $identifier; + private $identifier; /** * The Socket Object * - * @var object - * @access private + * @var resource|closed-resource|null */ - var $fsock; + public $fsock; /** * Execution Bitmap @@ -177,163 +199,145 @@ class SSH2 * if a requisite function has been successfully executed. If not, an error should be thrown. * * @var int - * @access private */ - var $bitmap = 0; + protected $bitmap = 0; /** * Error information * * @see self::getErrors() * @see self::getLastError() - * @var string - * @access private + * @var array */ - var $errors = array(); + private $errors = []; /** * Server Identifier * * @see self::getServerIdentification() - * @var array|false - * @access private + * @var string|false */ - var $server_identifier = false; + protected $server_identifier = false; /** * Key Exchange Algorithms * * @see self::getKexAlgorithims() * @var array|false - * @access private */ - var $kex_algorithms = false; + private $kex_algorithms = false; /** * Key Exchange Algorithm * * @see self::getMethodsNegotiated() * @var string|false - * @access private */ - var $kex_algorithm = false; + private $kex_algorithm = false; /** * Minimum Diffie-Hellman Group Bit Size in RFC 4419 Key Exchange Methods * * @see self::_key_exchange() * @var int - * @access private */ - var $kex_dh_group_size_min = 1536; + private $kex_dh_group_size_min = 1536; /** * Preferred Diffie-Hellman Group Bit Size in RFC 4419 Key Exchange Methods * * @see self::_key_exchange() * @var int - * @access private */ - var $kex_dh_group_size_preferred = 2048; + private $kex_dh_group_size_preferred = 2048; /** * Maximum Diffie-Hellman Group Bit Size in RFC 4419 Key Exchange Methods * * @see self::_key_exchange() * @var int - * @access private */ - var $kex_dh_group_size_max = 4096; + private $kex_dh_group_size_max = 4096; /** * Server Host Key Algorithms * * @see self::getServerHostKeyAlgorithms() * @var array|false - * @access private */ - var $server_host_key_algorithms = false; + private $server_host_key_algorithms = false; /** * Encryption Algorithms: Client to Server * * @see self::getEncryptionAlgorithmsClient2Server() * @var array|false - * @access private */ - var $encryption_algorithms_client_to_server = false; + private $encryption_algorithms_client_to_server = false; /** * Encryption Algorithms: Server to Client * * @see self::getEncryptionAlgorithmsServer2Client() * @var array|false - * @access private */ - var $encryption_algorithms_server_to_client = false; + private $encryption_algorithms_server_to_client = false; /** * MAC Algorithms: Client to Server * * @see self::getMACAlgorithmsClient2Server() * @var array|false - * @access private */ - var $mac_algorithms_client_to_server = false; + private $mac_algorithms_client_to_server = false; /** * MAC Algorithms: Server to Client * * @see self::getMACAlgorithmsServer2Client() * @var array|false - * @access private */ - var $mac_algorithms_server_to_client = false; + private $mac_algorithms_server_to_client = false; /** * Compression Algorithms: Client to Server * * @see self::getCompressionAlgorithmsClient2Server() * @var array|false - * @access private */ - var $compression_algorithms_client_to_server = false; + private $compression_algorithms_client_to_server = false; /** * Compression Algorithms: Server to Client * * @see self::getCompressionAlgorithmsServer2Client() * @var array|false - * @access private */ - var $compression_algorithms_server_to_client = false; + private $compression_algorithms_server_to_client = false; /** * Languages: Server to Client * * @see self::getLanguagesServer2Client() * @var array|false - * @access private */ - var $languages_server_to_client = false; + private $languages_server_to_client = false; /** * Languages: Client to Server * * @see self::getLanguagesClient2Server() * @var array|false - * @access private */ - var $languages_client_to_server = false; + private $languages_client_to_server = false; /** * Preferred Algorithms * * @see self::setPreferredAlgorithms() * @var array - * @access private */ - var $preferred = array(); + private $preferred = []; /** * Block Size for Server to Client Encryption @@ -348,9 +352,8 @@ class SSH2 * @see self::__construct() * @see self::_send_binary_packet() * @var int - * @access private */ - var $encrypt_block_size = 8; + private $encrypt_block_size = 8; /** * Block Size for Client to Server Encryption @@ -358,45 +361,134 @@ class SSH2 * @see self::__construct() * @see self::_get_binary_packet() * @var int - * @access private */ - var $decrypt_block_size = 8; + private $decrypt_block_size = 8; /** * Server to Client Encryption Object * * @see self::_get_binary_packet() + * @var SymmetricKey|false + */ + private $decrypt = false; + + /** + * Decryption Algorithm Name + * + * @var string|null + */ + private $decryptName; + + /** + * Decryption Invocation Counter + * + * Used by GCM + * + * @var string|null + */ + private $decryptInvocationCounter; + + /** + * Fixed Part of Nonce + * + * Used by GCM + * + * @var string|null + */ + private $decryptFixedPart; + + /** + * Server to Client Length Encryption Object + * + * @see self::_get_binary_packet() * @var object - * @access private */ - var $decrypt = false; + private $lengthDecrypt = false; /** * Client to Server Encryption Object * * @see self::_send_binary_packet() + * @var SymmetricKey|false + */ + private $encrypt = false; + + /** + * Encryption Algorithm Name + * + * @var string|null + */ + private $encryptName; + + /** + * Encryption Invocation Counter + * + * Used by GCM + * + * @var string|null + */ + private $encryptInvocationCounter; + + /** + * Fixed Part of Nonce + * + * Used by GCM + * + * @var string|null + */ + private $encryptFixedPart; + + /** + * Client to Server Length Encryption Object + * + * @see self::_send_binary_packet() * @var object - * @access private */ - var $encrypt = false; + private $lengthEncrypt = false; /** * Client to Server HMAC Object * * @see self::_send_binary_packet() * @var object - * @access private */ - var $hmac_create = false; + private $hmac_create = false; + + /** + * Client to Server HMAC Name + * + * @var string|false + */ + private $hmac_create_name; + + /** + * Client to Server ETM + * + * @var int|false + */ + private $hmac_create_etm; /** * Server to Client HMAC Object * * @see self::_get_binary_packet() * @var object - * @access private */ - var $hmac_check = false; + private $hmac_check = false; + + /** + * Server to Client HMAC Name + * + * @var string|false + */ + private $hmac_check_name; + + /** + * Server to Client ETM + * + * @var int|false + */ + private $hmac_check_etm; /** * Size of server to client HMAC @@ -407,18 +499,16 @@ class SSH2 * * @see self::_get_binary_packet() * @var int - * @access private */ - var $hmac_size = false; + private $hmac_size = false; /** * Server Public Host Key * * @see self::getServerPublicHostKey() * @var string - * @access private */ - var $server_public_host_key; + private $server_public_host_key; /** * Session identifier @@ -431,9 +521,8 @@ class SSH2 * * @see self::_key_exchange() * @var string - * @access private */ - var $session_id = false; + private $session_id = false; /** * Exchange hash @@ -442,9 +531,8 @@ class SSH2 * * @see self::_key_exchange() * @var string - * @access private */ - var $exchange_hash = false; + private $exchange_hash = false; /** * Message Numbers @@ -453,7 +541,7 @@ class SSH2 * @var array * @access private */ - var $message_numbers = array(); + private $message_numbers = []; /** * Disconnection Message 'reason codes' defined in RFC4253 @@ -462,7 +550,7 @@ class SSH2 * @var array * @access private */ - var $disconnect_reasons = array(); + private $disconnect_reasons = []; /** * SSH_MSG_CHANNEL_OPEN_FAILURE 'reason codes', defined in RFC4254 @@ -471,7 +559,7 @@ class SSH2 * @var array * @access private */ - var $channel_open_failure_reasons = array(); + private $channel_open_failure_reasons = []; /** * Terminal Modes @@ -481,7 +569,7 @@ class SSH2 * @var array * @access private */ - var $terminal_modes = array(); + private $terminal_modes = []; /** * SSH_MSG_CHANNEL_EXTENDED_DATA's data_type_codes @@ -491,7 +579,7 @@ class SSH2 * @var array * @access private */ - var $channel_extended_data_type_codes = array(); + private $channel_extended_data_type_codes = []; /** * Send Sequence Number @@ -500,9 +588,8 @@ class SSH2 * * @see self::_send_binary_packet() * @var int - * @access private */ - var $send_seq_no = 0; + private $send_seq_no = 0; /** * Get Sequence Number @@ -511,21 +598,19 @@ class SSH2 * * @see self::_get_binary_packet() * @var int - * @access private */ - var $get_seq_no = 0; + private $get_seq_no = 0; /** * Server Channels * * Maps client channels to server channels * - * @see self::_get_channel_packet() + * @see self::get_channel_packet() * @see self::exec() * @var array - * @access private */ - var $server_channels = array(); + protected $server_channels = []; /** * Channel Buffers @@ -533,52 +618,47 @@ class SSH2 * If a client requests a packet from one channel but receives two packets from another those packets should * be placed in a buffer * - * @see self::_get_channel_packet() + * @see self::get_channel_packet() * @see self::exec() * @var array - * @access private */ - var $channel_buffers = array(); + private $channel_buffers = []; /** * Channel Status * * Contains the type of the last sent message * - * @see self::_get_channel_packet() + * @see self::get_channel_packet() * @var array - * @access private */ - var $channel_status = array(); + protected $channel_status = []; /** * Packet Size * * Maximum packet size indexed by channel * - * @see self::_send_channel_packet() + * @see self::send_channel_packet() * @var array - * @access private */ - var $packet_size_client_to_server = array(); + private $packet_size_client_to_server = []; /** * Message Number Log * * @see self::getLog() * @var array - * @access private */ - var $message_number_log = array(); + private $message_number_log = []; /** * Message Log * * @see self::getLog() * @var array - * @access private */ - var $message_log = array(); + private $message_log = []; /** * The Window Size @@ -586,11 +666,10 @@ class SSH2 * Bytes the other party can send before it must wait for the window to be adjusted (0x7FFFFFFF = 2GB) * * @var int - * @see self::_send_channel_packet() + * @see self::send_channel_packet() * @see self::exec() - * @access private */ - var $window_size = 0x7FFFFFFF; + protected $window_size = 0x7FFFFFFF; /** * What we resize the window to @@ -602,31 +681,28 @@ class SSH2 * @var int * @see self::_send_channel_packet() * @see self::exec() - * @access private */ - var $window_resize = 0x40000000; + private $window_resize = 0x40000000; /** * Window size, server to client * * Window size indexed by channel * - * @see self::_send_channel_packet() + * @see self::send_channel_packet() * @var array - * @access private */ - var $window_size_server_to_client = array(); + protected $window_size_server_to_client = []; /** * Window size, client to server * * Window size indexed by channel * - * @see self::_get_channel_packet() + * @see self::get_channel_packet() * @var array - * @access private */ - var $window_size_client_to_server = array(); + private $window_size_client_to_server = []; /** * Server signature @@ -635,9 +711,8 @@ class SSH2 * * @see self::getServerPublicHostKey() * @var string - * @access private */ - var $signature = ''; + private $signature = ''; /** * Server signature format @@ -646,18 +721,16 @@ class SSH2 * * @see self::getServerPublicHostKey() * @var string - * @access private */ - var $signature_format = ''; + private $signature_format = ''; /** * Interactive Buffer * * @see self::read() - * @var array - * @access private + * @var string */ - var $interactiveBuffer = ''; + private $interactiveBuffer = ''; /** * Current log size @@ -667,143 +740,127 @@ class SSH2 * @see self::_send_binary_packet() * @see self::_get_binary_packet() * @var int - * @access private */ - var $log_size; + private $log_size; /** * Timeout * * @see self::setTimeout() - * @access private */ - var $timeout; + protected $timeout; /** * Current Timeout * - * @see self::_get_channel_packet() - * @access private + * @see self::get_channel_packet() */ - var $curTimeout; + protected $curTimeout; /** * Keep Alive Interval * * @see self::setKeepAlive() - * @access private */ - var $keepAlive; + private $keepAlive; /** * Real-time log file pointer * * @see self::_append_log() - * @var resource - * @access private + * @var resource|closed-resource */ - var $realtime_log_file; + private $realtime_log_file; /** * Real-time log file size * * @see self::_append_log() * @var int - * @access private */ - var $realtime_log_size; + private $realtime_log_size; /** * Has the signature been validated? * * @see self::getServerPublicHostKey() * @var bool - * @access private */ - var $signature_validated = false; + private $signature_validated = false; /** * Real-time log file wrap boolean * * @see self::_append_log() - * @access private + * @var bool */ - var $realtime_log_wrap; + private $realtime_log_wrap; /** * Flag to suppress stderr from output * * @see self::enableQuietMode() - * @access private */ - var $quiet_mode = false; + private $quiet_mode = false; /** * Time of first network activity * - * @var int - * @access private + * @var float */ - var $last_packet; + private $last_packet; /** * Exit status returned from ssh if any * * @var int - * @access private */ - var $exit_status; + private $exit_status; /** * Flag to request a PTY when using exec() * * @var bool * @see self::enablePTY() - * @access private */ - var $request_pty = false; + private $request_pty = false; /** * Flag set while exec() is running when using enablePTY() * * @var bool - * @access private */ - var $in_request_pty_exec = false; + private $in_request_pty_exec = false; /** * Flag set after startSubsystem() is called * * @var bool - * @access private */ - var $in_subsystem; + private $in_subsystem; /** * Contents of stdError * * @var string - * @access private */ - var $stdErrorLog; + private $stdErrorLog; /** * The Last Interactive Response * * @see self::_keyboard_interactive_process() * @var string - * @access private */ - var $last_interactive_response = ''; + private $last_interactive_response = ''; /** * Keyboard Interactive Request / Responses * * @see self::_keyboard_interactive_process() * @var array - * @access private */ - var $keyboard_requests_responses = array(); + private $keyboard_requests_responses = []; /** * Banner Message @@ -814,45 +871,40 @@ class SSH2 * @see self::_filter() * @see self::getBannerMessage() * @var string - * @access private */ - var $banner_message = ''; + private $banner_message = ''; /** * Did read() timeout or return normally? * * @see self::isTimeout() * @var bool - * @access private */ - var $is_timeout = false; + private $is_timeout = false; /** * Log Boundary * * @see self::_format_log() * @var string - * @access private */ - var $log_boundary = ':'; + private $log_boundary = ':'; /** * Log Long Width * * @see self::_format_log() * @var int - * @access private */ - var $log_long_width = 65; + private $log_long_width = 65; /** * Log Short Width * * @see self::_format_log() * @var int - * @access private */ - var $log_short_width = 16; + private $log_short_width = 16; /** * Hostname @@ -860,9 +912,8 @@ class SSH2 * @see self::__construct() * @see self::_connect() * @var string - * @access private */ - var $host; + private $host; /** * Port Number @@ -870,9 +921,8 @@ class SSH2 * @see self::__construct() * @see self::_connect() * @var int - * @access private */ - var $port; + private $port; /** * Number of columns for terminal window size @@ -881,9 +931,8 @@ class SSH2 * @see self::setWindowColumns() * @see self::setWindowSize() * @var int - * @access private */ - var $windowColumns = 80; + private $windowColumns = 80; /** * Number of columns for terminal window size @@ -892,9 +941,8 @@ class SSH2 * @see self::setWindowRows() * @see self::setWindowSize() * @var int - * @access private */ - var $windowRows = 24; + private $windowRows = 24; /** * Crypto Engine @@ -902,73 +950,136 @@ class SSH2 * @see self::setCryptoEngine() * @see self::_key_exchange() * @var int - * @access private */ - var $crypto_engine = false; + private static $crypto_engine = false; /** * A System_SSH_Agent for use in the SSH2 Agent Forwarding scenario * - * @var System_SSH_Agent - * @access private + * @var Agent */ - var $agent; + private $agent; + + /** + * Connection storage to replicates ssh2 extension functionality: + * {@link http://php.net/manual/en/wrappers.ssh2.php#refsect1-wrappers.ssh2-examples} + * + * @var array> + */ + private static $connections; /** * Send the identification string first? * * @var bool - * @access private */ - var $send_id_string_first = true; + private $send_id_string_first = true; /** * Send the key exchange initiation packet first? * * @var bool - * @access private */ - var $send_kex_first = true; + private $send_kex_first = true; /** * Some versions of OpenSSH incorrectly calculate the key size * * @var bool - * @access private */ - var $bad_key_size_fix = false; + private $bad_key_size_fix = false; /** * Should we try to re-connect to re-establish keys? * * @var bool - * @access private */ - var $retry_connect = false; + private $retry_connect = false; /** * Binary Packet Buffer * * @var string|false - * @access private */ - var $binary_packet_buffer = false; + private $binary_packet_buffer = false; /** * Preferred Signature Format * * @var string|false - * @access private */ - var $preferred_signature_format = false; + protected $preferred_signature_format = false; /** * Authentication Credentials * * @var array - * @access private */ - var $auth = array(); + protected $auth = []; + + /** + * Terminal + * + * @var string + */ + private $term = 'vt100'; + + /** + * The authentication methods that may productively continue authentication. + * + * @see https://tools.ietf.org/html/rfc4252#section-5.1 + * @var array|null + */ + private $auth_methods_to_continue = null; + + /** + * Compression method + * + * @var int + */ + private $compress = self::NET_SSH2_COMPRESSION_NONE; + + /** + * Decompression method + * + * @var int + */ + private $decompress = self::NET_SSH2_COMPRESSION_NONE; + + /** + * Compression context + * + * @var resource|false|null + */ + private $compress_context; + + /** + * Decompression context + * + * @var resource|object + */ + private $decompress_context; + + /** + * Regenerate Compression Context + * + * @var bool + */ + private $regenerate_compression_context = false; + + /** + * Regenerate Decompression Context + * + * @var bool + */ + private $regenerate_decompression_context = false; + + /** + * Smart multi-factor authentication flag + * + * @var bool + */ + private $smartMFA = true; /** * Default Constructor. @@ -979,12 +1090,10 @@ class SSH2 * @param int $port * @param int $timeout * @see self::login() - * @return \phpseclib\Net\SSH2 - * @access public */ - function __construct($host, $port = 22, $timeout = 10) + public function __construct($host, $port = 22, $timeout = 10) { - $this->message_numbers = array( + $this->message_numbers = [ 1 => 'NET_SSH2_MSG_DISCONNECT', 2 => 'NET_SSH2_MSG_IGNORE', 3 => 'NET_SSH2_MSG_UNIMPLEMENTED', @@ -1014,8 +1123,8 @@ function __construct($host, $port = 22, $timeout = 10) 98 => 'NET_SSH2_MSG_CHANNEL_REQUEST', 99 => 'NET_SSH2_MSG_CHANNEL_SUCCESS', 100 => 'NET_SSH2_MSG_CHANNEL_FAILURE' - ); - $this->disconnect_reasons = array( + ]; + $this->disconnect_reasons = [ 1 => 'NET_SSH2_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT', 2 => 'NET_SSH2_DISCONNECT_PROTOCOL_ERROR', 3 => 'NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED', @@ -1031,44 +1140,52 @@ function __construct($host, $port = 22, $timeout = 10) 13 => 'NET_SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER', 14 => 'NET_SSH2_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE', 15 => 'NET_SSH2_DISCONNECT_ILLEGAL_USER_NAME' - ); - $this->channel_open_failure_reasons = array( + ]; + $this->channel_open_failure_reasons = [ 1 => 'NET_SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED' - ); - $this->terminal_modes = array( + ]; + $this->terminal_modes = [ 0 => 'NET_SSH2_TTY_OP_END' - ); - $this->channel_extended_data_type_codes = array( + ]; + $this->channel_extended_data_type_codes = [ 1 => 'NET_SSH2_EXTENDED_DATA_STDERR' - ); + ]; - $this->_define_array( + $this->define_array( $this->message_numbers, $this->disconnect_reasons, $this->channel_open_failure_reasons, $this->terminal_modes, $this->channel_extended_data_type_codes, - array(60 => 'NET_SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ'), - array(60 => 'NET_SSH2_MSG_USERAUTH_PK_OK'), - array(60 => 'NET_SSH2_MSG_USERAUTH_INFO_REQUEST', - 61 => 'NET_SSH2_MSG_USERAUTH_INFO_RESPONSE'), + [60 => 'NET_SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ'], + [60 => 'NET_SSH2_MSG_USERAUTH_PK_OK'], + [60 => 'NET_SSH2_MSG_USERAUTH_INFO_REQUEST', + 61 => 'NET_SSH2_MSG_USERAUTH_INFO_RESPONSE'], // RFC 4419 - diffie-hellman-group-exchange-sha{1,256} - array(30 => 'NET_SSH2_MSG_KEXDH_GEX_REQUEST_OLD', + [30 => 'NET_SSH2_MSG_KEXDH_GEX_REQUEST_OLD', 31 => 'NET_SSH2_MSG_KEXDH_GEX_GROUP', 32 => 'NET_SSH2_MSG_KEXDH_GEX_INIT', 33 => 'NET_SSH2_MSG_KEXDH_GEX_REPLY', - 34 => 'NET_SSH2_MSG_KEXDH_GEX_REQUEST'), + 34 => 'NET_SSH2_MSG_KEXDH_GEX_REQUEST'], // RFC 5656 - Elliptic Curves (for curve25519-sha256@libssh.org) - array(30 => 'NET_SSH2_MSG_KEX_ECDH_INIT', - 31 => 'NET_SSH2_MSG_KEX_ECDH_REPLY') + [30 => 'NET_SSH2_MSG_KEX_ECDH_INIT', + 31 => 'NET_SSH2_MSG_KEX_ECDH_REPLY'] ); + /** + * Typehint is required due to a bug in Psalm: https://github.com/vimeo/psalm/issues/7508 + * @var \WeakReference|SSH2 + */ + self::$connections[$this->getResourceId()] = class_exists('WeakReference') + ? \WeakReference::create($this) + : $this; + if (is_resource($host)) { $this->fsock = $host; return; } - if (is_string($host)) { + if (Strings::is_stringable($host)) { $this->host = $host; $this->port = $port; $this->timeout = $timeout; @@ -1079,14 +1196,13 @@ function __construct($host, $port = 22, $timeout = 10) * Set Crypto Engine Mode * * Possible $engine values: - * CRYPT_MODE_INTERNAL, CRYPT_MODE_MCRYPT + * OpenSSL, mcrypt, Eval, PHP * * @param int $engine - * @access public */ - function setCryptoEngine($engine) + public static function setCryptoEngine($engine) { - $this->crypto_engine = $engine; + self::$crypto_engine = $engine; } /** @@ -1096,9 +1212,8 @@ function setCryptoEngine($engine) * both sides MUST send an identification string". It does not say which side sends it first. In * theory it shouldn't matter but it is a fact of life that some SSH servers are simply buggy * - * @access public */ - function sendIdentificationStringFirst() + public function sendIdentificationStringFirst() { $this->send_id_string_first = true; } @@ -1110,9 +1225,8 @@ function sendIdentificationStringFirst() * both sides MUST send an identification string". It does not say which side sends it first. In * theory it shouldn't matter but it is a fact of life that some SSH servers are simply buggy * - * @access public */ - function sendIdentificationStringLast() + public function sendIdentificationStringLast() { $this->send_id_string_first = false; } @@ -1124,9 +1238,8 @@ function sendIdentificationStringLast() * sending the [SSH_MSG_KEXINIT] packet". It does not say which side sends it first. In theory * it shouldn't matter but it is a fact of life that some SSH servers are simply buggy * - * @access public */ - function sendKEXINITFirst() + public function sendKEXINITFirst() { $this->send_kex_first = true; } @@ -1138,9 +1251,8 @@ function sendKEXINITFirst() * sending the [SSH_MSG_KEXINIT] packet". It does not say which side sends it first. In theory * it shouldn't matter but it is a fact of life that some SSH servers are simply buggy * - * @access public */ - function sendKEXINITLast() + public function sendKEXINITLast() { $this->send_kex_first = false; } @@ -1148,13 +1260,13 @@ function sendKEXINITLast() /** * Connect to an SSHv2 server * - * @return bool - * @access private + * @throws \UnexpectedValueException on receipt of unexpected packets + * @throws \RuntimeException on other errors */ - function _connect() + private function connect() { if ($this->bitmap & self::MASK_CONSTRUCTOR) { - return false; + return; } $this->bitmap |= self::MASK_CONSTRUCTOR; @@ -1171,21 +1283,19 @@ function _connect() $this->fsock = @fsockopen($this->host, $this->port, $errno, $errstr, $this->curTimeout == 0 ? 100000 : $this->curTimeout); if (!$this->fsock) { $host = $this->host . ':' . $this->port; - user_error(rtrim("Cannot connect to $host. Error $errno. $errstr")); - return false; + throw new UnableToConnectException(rtrim("Cannot connect to $host. Error $errno. $errstr")); } $elapsed = microtime(true) - $start; if ($this->curTimeout) { - $this->curTimeout-= $elapsed; + $this->curTimeout -= $elapsed; if ($this->curTimeout < 0) { - $this->is_timeout = true; - return false; + throw new \RuntimeException('Connection timed out whilst attempting to open socket connection'); } } } - $this->identifier = $this->_generate_identifier(); + $this->identifier = $this->generate_identifier(); if ($this->send_id_string_first) { fputs($this->fsock, $this->identifier . "\r\n"); @@ -1204,33 +1314,29 @@ function _connect() while (true) { if ($this->curTimeout) { if ($this->curTimeout < 0) { - $this->is_timeout = true; - return false; + throw new \RuntimeException('Connection timed out whilst receiving server identification string'); } - $read = array($this->fsock); + $read = [$this->fsock]; $write = $except = null; $start = microtime(true); - $sec = floor($this->curTimeout); - $usec = 1000000 * ($this->curTimeout - $sec); - // on windows this returns a "Warning: Invalid CRT parameters detected" error - // the !count() is done as a workaround for - if (!@stream_select($read, $write, $except, $sec, $usec) && !count($read)) { - $this->is_timeout = true; - return false; + $sec = (int) floor($this->curTimeout); + $usec = (int) (1000000 * ($this->curTimeout - $sec)); + if (@stream_select($read, $write, $except, $sec, $usec) === false) { + throw new \RuntimeException('Connection timed out whilst receiving server identification string'); } $elapsed = microtime(true) - $start; - $this->curTimeout-= $elapsed; + $this->curTimeout -= $elapsed; } $temp = stream_get_line($this->fsock, 255, "\n"); + if ($temp === false) { + throw new \RuntimeException('Error reading from socket'); + } if (strlen($temp) == 255) { continue; } - if ($temp === false) { - return false; - } - $line.= "$temp\n"; + $line .= "$temp\n"; // quoting RFC4253, "Implementers who wish to maintain // compatibility with older, undocumented versions of this protocol may @@ -1245,20 +1351,19 @@ function _connect() break; } - $data.= $line; + $data .= $line; } if (feof($this->fsock)) { $this->bitmap = 0; - user_error('Connection closed by server'); - return false; + throw new ConnectionClosedException('Connection closed by server'); } $extra = $matches[1]; if (defined('NET_SSH2_LOGGING')) { - $this->_append_log('<-', $matches[0]); - $this->_append_log('->', $this->identifier . "\r\n"); + $this->append_log('<-', $matches[0]); + $this->append_log('->', $this->identifier . "\r\n"); } $this->server_identifier = trim($temp, "\r\n"); @@ -1267,8 +1372,8 @@ function _connect() } if (version_compare($matches[3], '1.99', '<')) { - user_error("Cannot connect to SSH $matches[3] servers"); - return false; + $this->bitmap = 0; + throw new UnableToConnectException("Cannot connect to SSH $matches[3] servers"); } if (!$this->send_id_string_first) { @@ -1276,28 +1381,21 @@ function _connect() } if (!$this->send_kex_first) { - $response = $this->_get_binary_packet(); - if ($response === false) { - $this->bitmap = 0; - user_error('Connection closed by server'); - return false; - } + $response = $this->get_binary_packet(); - if (!strlen($response) || ord($response[0]) != NET_SSH2_MSG_KEXINIT) { - user_error('Expected SSH_MSG_KEXINIT'); - return false; + if (is_bool($response) || !strlen($response) || ord($response[0]) != NET_SSH2_MSG_KEXINIT) { + $this->bitmap = 0; + throw new \UnexpectedValueException('Expected SSH_MSG_KEXINIT'); } - if (!$this->_key_exchange($response)) { - return false; - } + $this->key_exchange($response); } - if ($this->send_kex_first && !$this->_key_exchange()) { - return false; + if ($this->send_kex_first) { + $this->key_exchange(); } - $this->bitmap|= self::MASK_CONNECTED; + $this->bitmap |= self::MASK_CONNECTED; return true; } @@ -1307,15 +1405,14 @@ function _connect() * * You should overwrite this method in your own class if you want to use another identifier * - * @access protected * @return string */ - function _generate_identifier() + private function generate_identifier() { - $identifier = 'SSH-2.0-phpseclib_2.0'; + $identifier = 'SSH-2.0-phpseclib_3.0'; - $ext = array(); - if (function_exists('sodium_crypto_box_publickey_from_secretkey')) { + $ext = []; + if (extension_loaded('sodium')) { $ext[] = 'libsodium'; } @@ -1341,37 +1438,41 @@ function _generate_identifier() /** * Key Exchange * - * @param string $kexinit_payload_server optional - * @access private + * @return bool + * @param string|bool $kexinit_payload_server optional + * @throws \UnexpectedValueException on receipt of unexpected packets + * @throws \RuntimeException on other errors + * @throws \phpseclib3\Exception\NoSupportedAlgorithmsException when none of the algorithms phpseclib has loaded are compatible */ - function _key_exchange($kexinit_payload_server = false) + private function key_exchange($kexinit_payload_server = false) { $preferred = $this->preferred; + $send_kex = true; $kex_algorithms = isset($preferred['kex']) ? $preferred['kex'] : - $this->getSupportedKEXAlgorithms(); + SSH2::getSupportedKEXAlgorithms(); $server_host_key_algorithms = isset($preferred['hostkey']) ? $preferred['hostkey'] : - $this->getSupportedHostKeyAlgorithms(); + SSH2::getSupportedHostKeyAlgorithms(); $s2c_encryption_algorithms = isset($preferred['server_to_client']['crypt']) ? $preferred['server_to_client']['crypt'] : - $this->getSupportedEncryptionAlgorithms(); + SSH2::getSupportedEncryptionAlgorithms(); $c2s_encryption_algorithms = isset($preferred['client_to_server']['crypt']) ? $preferred['client_to_server']['crypt'] : - $this->getSupportedEncryptionAlgorithms(); + SSH2::getSupportedEncryptionAlgorithms(); $s2c_mac_algorithms = isset($preferred['server_to_client']['mac']) ? $preferred['server_to_client']['mac'] : - $this->getSupportedMACAlgorithms(); + SSH2::getSupportedMACAlgorithms(); $c2s_mac_algorithms = isset($preferred['client_to_server']['mac']) ? $preferred['client_to_server']['mac'] : - $this->getSupportedMACAlgorithms(); + SSH2::getSupportedMACAlgorithms(); $s2c_compression_algorithms = isset($preferred['server_to_client']['comp']) ? $preferred['server_to_client']['comp'] : - $this->getSupportedCompressionAlgorithms(); + SSH2::getSupportedCompressionAlgorithms(); $c2s_compression_algorithms = isset($preferred['client_to_server']['comp']) ? $preferred['client_to_server']['comp'] : - $this->getSupportedCompressionAlgorithms(); + SSH2::getSupportedCompressionAlgorithms(); // some SSH servers have buggy implementations of some of the above algorithms switch (true) { @@ -1380,209 +1481,174 @@ function _key_exchange($kexinit_payload_server = false) if (!isset($preferred['server_to_client']['mac'])) { $s2c_mac_algorithms = array_values(array_diff( $s2c_mac_algorithms, - array('hmac-sha1-96', 'hmac-md5-96') + ['hmac-sha1-96', 'hmac-md5-96'] )); } if (!isset($preferred['client_to_server']['mac'])) { $c2s_mac_algorithms = array_values(array_diff( $c2s_mac_algorithms, - array('hmac-sha1-96', 'hmac-md5-96') + ['hmac-sha1-96', 'hmac-md5-96'] )); } } - $str_kex_algorithms = implode(',', $kex_algorithms); - $str_server_host_key_algorithms = implode(',', $server_host_key_algorithms); - $encryption_algorithms_server_to_client = implode(',', $s2c_encryption_algorithms); - $encryption_algorithms_client_to_server = implode(',', $c2s_encryption_algorithms); - $mac_algorithms_server_to_client = implode(',', $s2c_mac_algorithms); - $mac_algorithms_client_to_server = implode(',', $c2s_mac_algorithms); - $compression_algorithms_server_to_client = implode(',', $s2c_compression_algorithms); - $compression_algorithms_client_to_server = implode(',', $c2s_compression_algorithms); - $client_cookie = Random::string(16); - $kexinit_payload_client = pack( - 'Ca*Na*Na*Na*Na*Na*Na*Na*Na*Na*Na*CN', - NET_SSH2_MSG_KEXINIT, - $client_cookie, - strlen($str_kex_algorithms), - $str_kex_algorithms, - strlen($str_server_host_key_algorithms), - $str_server_host_key_algorithms, - strlen($encryption_algorithms_client_to_server), - $encryption_algorithms_client_to_server, - strlen($encryption_algorithms_server_to_client), - $encryption_algorithms_server_to_client, - strlen($mac_algorithms_client_to_server), - $mac_algorithms_client_to_server, - strlen($mac_algorithms_server_to_client), - $mac_algorithms_server_to_client, - strlen($compression_algorithms_client_to_server), - $compression_algorithms_client_to_server, - strlen($compression_algorithms_server_to_client), - $compression_algorithms_server_to_client, - 0, - '', - 0, - '', - 0, - 0 + $kexinit_payload_client = pack('Ca*', NET_SSH2_MSG_KEXINIT, $client_cookie); + $kexinit_payload_client .= Strings::packSSH2( + 'L10bN', + $kex_algorithms, + $server_host_key_algorithms, + $c2s_encryption_algorithms, + $s2c_encryption_algorithms, + $c2s_mac_algorithms, + $s2c_mac_algorithms, + $c2s_compression_algorithms, + $s2c_compression_algorithms, + [], // language, client to server + [], // language, server to client + false, // first_kex_packet_follows + 0 // reserved for future extension ); - if ($this->send_kex_first) { - if (!$this->_send_binary_packet($kexinit_payload_client)) { - return false; - } + if ($kexinit_payload_server === false) { + $this->send_binary_packet($kexinit_payload_client); - $kexinit_payload_server = $this->_get_binary_packet(); - if ($kexinit_payload_server === false) { - $this->bitmap = 0; - user_error('Connection closed by server'); - return false; - } + $kexinit_payload_server = $this->get_binary_packet(); - if (!strlen($kexinit_payload_server) || ord($kexinit_payload_server[0]) != NET_SSH2_MSG_KEXINIT) { - user_error('Expected SSH_MSG_KEXINIT'); - return false; + if ( + is_bool($kexinit_payload_server) + || !strlen($kexinit_payload_server) + || ord($kexinit_payload_server[0]) != NET_SSH2_MSG_KEXINIT + ) { + $this->disconnect_helper(NET_SSH2_DISCONNECT_PROTOCOL_ERROR); + throw new \UnexpectedValueException('Expected SSH_MSG_KEXINIT'); } - } - $response = $kexinit_payload_server; - $this->_string_shift($response, 1); // skip past the message number (it should be SSH_MSG_KEXINIT) - $server_cookie = $this->_string_shift($response, 16); - - if (strlen($response) < 4) { - return false; - } - $temp = unpack('Nlength', $this->_string_shift($response, 4)); - $this->kex_algorithms = explode(',', $this->_string_shift($response, $temp['length'])); - - if (strlen($response) < 4) { - return false; - } - $temp = unpack('Nlength', $this->_string_shift($response, 4)); - $this->server_host_key_algorithms = explode(',', $this->_string_shift($response, $temp['length'])); - - if (strlen($response) < 4) { - return false; - } - $temp = unpack('Nlength', $this->_string_shift($response, 4)); - $this->encryption_algorithms_client_to_server = explode(',', $this->_string_shift($response, $temp['length'])); - - if (strlen($response) < 4) { - return false; + $send_kex = false; } - $temp = unpack('Nlength', $this->_string_shift($response, 4)); - $this->encryption_algorithms_server_to_client = explode(',', $this->_string_shift($response, $temp['length'])); - if (strlen($response) < 4) { - return false; - } - $temp = unpack('Nlength', $this->_string_shift($response, 4)); - $this->mac_algorithms_client_to_server = explode(',', $this->_string_shift($response, $temp['length'])); - - if (strlen($response) < 4) { - return false; - } - $temp = unpack('Nlength', $this->_string_shift($response, 4)); - $this->mac_algorithms_server_to_client = explode(',', $this->_string_shift($response, $temp['length'])); - - if (strlen($response) < 4) { - return false; - } - $temp = unpack('Nlength', $this->_string_shift($response, 4)); - $this->compression_algorithms_client_to_server = explode(',', $this->_string_shift($response, $temp['length'])); - - if (strlen($response) < 4) { - return false; - } - $temp = unpack('Nlength', $this->_string_shift($response, 4)); - $this->compression_algorithms_server_to_client = explode(',', $this->_string_shift($response, $temp['length'])); - - if (strlen($response) < 4) { - return false; - } - $temp = unpack('Nlength', $this->_string_shift($response, 4)); - $this->languages_client_to_server = explode(',', $this->_string_shift($response, $temp['length'])); - - if (strlen($response) < 4) { - return false; - } - $temp = unpack('Nlength', $this->_string_shift($response, 4)); - $this->languages_server_to_client = explode(',', $this->_string_shift($response, $temp['length'])); - - if (!strlen($response)) { - return false; - } - extract(unpack('Cfirst_kex_packet_follows', $this->_string_shift($response, 1))); - $first_kex_packet_follows = $first_kex_packet_follows != 0; - - if (!$this->send_kex_first && !$this->_send_binary_packet($kexinit_payload_client)) { - return false; + $response = $kexinit_payload_server; + Strings::shift($response, 1); // skip past the message number (it should be SSH_MSG_KEXINIT) + $server_cookie = Strings::shift($response, 16); + + list( + $this->kex_algorithms, + $this->server_host_key_algorithms, + $this->encryption_algorithms_client_to_server, + $this->encryption_algorithms_server_to_client, + $this->mac_algorithms_client_to_server, + $this->mac_algorithms_server_to_client, + $this->compression_algorithms_client_to_server, + $this->compression_algorithms_server_to_client, + $this->languages_client_to_server, + $this->languages_server_to_client, + $first_kex_packet_follows + ) = Strings::unpackSSH2('L10C', $response); + + if ($send_kex) { + $this->send_binary_packet($kexinit_payload_client); } // we need to decide upon the symmetric encryption algorithms before we do the diffie-hellman key exchange + // we don't initialize any crypto-objects, yet - we do that, later. for now, we need the lengths to make the // diffie-hellman key exchange as fast as possible - $decrypt = $this->_array_intersect_first($s2c_encryption_algorithms, $this->encryption_algorithms_server_to_client); - $decryptKeyLength = $this->_encryption_algorithm_to_key_size($decrypt); + $decrypt = self::array_intersect_first($s2c_encryption_algorithms, $this->encryption_algorithms_server_to_client); + $decryptKeyLength = $this->encryption_algorithm_to_key_size($decrypt); if ($decryptKeyLength === null) { - user_error('No compatible server to client encryption algorithms found'); - return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); + $this->disconnect_helper(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); + throw new NoSupportedAlgorithmsException('No compatible server to client encryption algorithms found'); } - $encrypt = $this->_array_intersect_first($c2s_encryption_algorithms, $this->encryption_algorithms_client_to_server); - $encryptKeyLength = $this->_encryption_algorithm_to_key_size($encrypt); + $encrypt = self::array_intersect_first($c2s_encryption_algorithms, $this->encryption_algorithms_client_to_server); + $encryptKeyLength = $this->encryption_algorithm_to_key_size($encrypt); if ($encryptKeyLength === null) { - user_error('No compatible client to server encryption algorithms found'); - return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); + $this->disconnect_helper(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); + throw new NoSupportedAlgorithmsException('No compatible client to server encryption algorithms found'); } // through diffie-hellman key exchange a symmetric key is obtained - $this->kex_algorithm = $kex_algorithm = $this->_array_intersect_first($kex_algorithms, $this->kex_algorithms); - if ($kex_algorithm === false) { - user_error('No compatible key exchange algorithms found'); - return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); + $this->kex_algorithm = self::array_intersect_first($kex_algorithms, $this->kex_algorithms); + if ($this->kex_algorithm === false) { + $this->disconnect_helper(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); + throw new NoSupportedAlgorithmsException('No compatible key exchange algorithms found'); } - $server_host_key_algorithm = $this->_array_intersect_first($server_host_key_algorithms, $this->server_host_key_algorithms); + $server_host_key_algorithm = self::array_intersect_first($server_host_key_algorithms, $this->server_host_key_algorithms); if ($server_host_key_algorithm === false) { - user_error('No compatible server host key algorithms found'); - return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); + $this->disconnect_helper(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); + throw new NoSupportedAlgorithmsException('No compatible server host key algorithms found'); } - $mac_algorithm_in = $this->_array_intersect_first($s2c_mac_algorithms, $this->mac_algorithms_server_to_client); - if ($mac_algorithm_in === false) { - user_error('No compatible server to client message authentication algorithms found'); - return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); + $mac_algorithm_out = self::array_intersect_first($c2s_mac_algorithms, $this->mac_algorithms_client_to_server); + if ($mac_algorithm_out === false) { + $this->disconnect_helper(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); + throw new NoSupportedAlgorithmsException('No compatible client to server message authentication algorithms found'); } - $compression_algorithm_out = $this->_array_intersect_first($c2s_compression_algorithms, $this->compression_algorithms_client_to_server); - if ($compression_algorithm_out === false) { - user_error('No compatible client to server compression algorithms found'); - return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); + $mac_algorithm_in = self::array_intersect_first($s2c_mac_algorithms, $this->mac_algorithms_server_to_client); + if ($mac_algorithm_in === false) { + $this->disconnect_helper(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); + throw new NoSupportedAlgorithmsException('No compatible server to client message authentication algorithms found'); } - //$this->decompress = $compression_algorithm_out == 'zlib'; - $compression_algorithm_in = $this->_array_intersect_first($s2c_compression_algorithms, $this->compression_algorithms_client_to_server); + $compression_map = [ + 'none' => self::NET_SSH2_COMPRESSION_NONE, + 'zlib' => self::NET_SSH2_COMPRESSION_ZLIB, + 'zlib@openssh.com' => self::NET_SSH2_COMPRESSION_ZLIB_AT_OPENSSH + ]; + + $compression_algorithm_in = self::array_intersect_first($s2c_compression_algorithms, $this->compression_algorithms_server_to_client); if ($compression_algorithm_in === false) { - user_error('No compatible server to client compression algorithms found'); - return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); + $this->disconnect_helper(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); + throw new NoSupportedAlgorithmsException('No compatible server to client compression algorithms found'); + } + $this->decompress = $compression_map[$compression_algorithm_in]; + + $compression_algorithm_out = self::array_intersect_first($c2s_compression_algorithms, $this->compression_algorithms_client_to_server); + if ($compression_algorithm_out === false) { + $this->disconnect_helper(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); + throw new NoSupportedAlgorithmsException('No compatible client to server compression algorithms found'); + } + $this->compress = $compression_map[$compression_algorithm_out]; + + switch ($this->kex_algorithm) { + case 'diffie-hellman-group15-sha512': + case 'diffie-hellman-group16-sha512': + case 'diffie-hellman-group17-sha512': + case 'diffie-hellman-group18-sha512': + case 'ecdh-sha2-nistp521': + $kexHash = new Hash('sha512'); + break; + case 'ecdh-sha2-nistp384': + $kexHash = new Hash('sha384'); + break; + case 'diffie-hellman-group-exchange-sha256': + case 'diffie-hellman-group14-sha256': + case 'ecdh-sha2-nistp256': + case 'curve25519-sha256@libssh.org': + case 'curve25519-sha256': + $kexHash = new Hash('sha256'); + break; + default: + $kexHash = new Hash('sha1'); } - //$this->compress = $compression_algorithm_in == 'zlib'; // Only relevant in diffie-hellman-group-exchange-sha{1,256}, otherwise empty. + $exchange_hash_rfc4419 = ''; - if ($kex_algorithm === 'curve25519-sha256@libssh.org') { - $x = Random::string(32); - $eBytes = sodium_crypto_box_publickey_from_secretkey($x); + if (strpos($this->kex_algorithm, 'curve25519-sha256') === 0 || strpos($this->kex_algorithm, 'ecdh-sha2-nistp') === 0) { + $curve = strpos($this->kex_algorithm, 'curve25519-sha256') === 0 ? + 'Curve25519' : + substr($this->kex_algorithm, 10); + $ourPrivate = EC::createKey($curve); + $ourPublicBytes = $ourPrivate->getPublicKey()->getEncodedCoordinates(); $clientKexInitMessage = 'NET_SSH2_MSG_KEX_ECDH_INIT'; $serverKexReplyMessage = 'NET_SSH2_MSG_KEX_ECDH_REPLY'; - $kexHash = new Hash('sha256'); } else { - if (strpos($kex_algorithm, 'diffie-hellman-group-exchange') === 0) { + if (strpos($this->kex_algorithm, 'diffie-hellman-group-exchange') === 0) { $dh_group_sizes_packed = pack( 'NNN', $this->kex_dh_group_size_min, @@ -1594,210 +1660,103 @@ function _key_exchange($kexinit_payload_server = false) NET_SSH2_MSG_KEXDH_GEX_REQUEST, $dh_group_sizes_packed ); - if (!$this->_send_binary_packet($packet)) { - return false; - } - $this->_updateLogHistory('UNKNOWN (34)', 'NET_SSH2_MSG_KEXDH_GEX_REQUEST'); + $this->send_binary_packet($packet); + $this->updateLogHistory('UNKNOWN (34)', 'NET_SSH2_MSG_KEXDH_GEX_REQUEST'); - $response = $this->_get_binary_packet(); - if ($response === false) { - $this->bitmap = 0; - user_error('Connection closed by server'); - return false; - } - extract(unpack('Ctype', $this->_string_shift($response, 1))); - if ($type != NET_SSH2_MSG_KEXDH_GEX_GROUP) { - user_error('Expected SSH_MSG_KEX_DH_GEX_GROUP'); - return false; - } - $this->_updateLogHistory('NET_SSH2_MSG_KEXDH_REPLY', 'NET_SSH2_MSG_KEXDH_GEX_GROUP'); + $response = $this->get_binary_packet(); - if (strlen($response) < 4) { - return false; + list($type, $primeBytes, $gBytes) = Strings::unpackSSH2('Css', $response); + if ($type != NET_SSH2_MSG_KEXDH_GEX_GROUP) { + $this->disconnect_helper(NET_SSH2_DISCONNECT_PROTOCOL_ERROR); + throw new \UnexpectedValueException('Expected SSH_MSG_KEX_DH_GEX_GROUP'); } - extract(unpack('NprimeLength', $this->_string_shift($response, 4))); - $primeBytes = $this->_string_shift($response, $primeLength); + $this->updateLogHistory('NET_SSH2_MSG_KEXDH_REPLY', 'NET_SSH2_MSG_KEXDH_GEX_GROUP'); $prime = new BigInteger($primeBytes, -256); - - if (strlen($response) < 4) { - return false; - } - extract(unpack('NgLength', $this->_string_shift($response, 4))); - $gBytes = $this->_string_shift($response, $gLength); $g = new BigInteger($gBytes, -256); - $exchange_hash_rfc4419 = pack( - 'a*Na*Na*', - $dh_group_sizes_packed, - $primeLength, + $exchange_hash_rfc4419 = $dh_group_sizes_packed . Strings::packSSH2( + 'ss', $primeBytes, - $gLength, $gBytes ); + $params = DH::createParameters($prime, $g); $clientKexInitMessage = 'NET_SSH2_MSG_KEXDH_GEX_INIT'; $serverKexReplyMessage = 'NET_SSH2_MSG_KEXDH_GEX_REPLY'; } else { - switch ($kex_algorithm) { - // see http://tools.ietf.org/html/rfc2409#section-6.2 and - // http://tools.ietf.org/html/rfc2412, appendex E - case 'diffie-hellman-group1-sha1': - $prime = 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' . - '020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' . - '4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' . - 'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF'; - break; - // see http://tools.ietf.org/html/rfc3526#section-3 - case 'diffie-hellman-group14-sha1': - $prime = 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' . - '020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' . - '4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' . - 'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF05' . - '98DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB' . - '9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B' . - 'E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718' . - '3995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF'; - break; - } - // For both diffie-hellman-group1-sha1 and diffie-hellman-group14-sha1 - // the generator field element is 2 (decimal) and the hash function is sha1. - $g = new BigInteger(2); - $prime = new BigInteger($prime, 16); + $params = DH::createParameters($this->kex_algorithm); $clientKexInitMessage = 'NET_SSH2_MSG_KEXDH_INIT'; $serverKexReplyMessage = 'NET_SSH2_MSG_KEXDH_REPLY'; } - switch ($kex_algorithm) { - case 'diffie-hellman-group-exchange-sha256': - $kexHash = new Hash('sha256'); - break; - default: - $kexHash = new Hash('sha1'); - } - - /* To increase the speed of the key exchange, both client and server may - reduce the size of their private exponents. It should be at least - twice as long as the key material that is generated from the shared - secret. For more details, see the paper by van Oorschot and Wiener - [VAN-OORSCHOT]. + $keyLength = min($kexHash->getLengthInBytes(), max($encryptKeyLength, $decryptKeyLength)); - -- http://tools.ietf.org/html/rfc4419#section-6.2 */ - $one = new BigInteger(1); - $keyLength = min($kexHash->getLength(), max($encryptKeyLength, $decryptKeyLength)); - $max = $one->bitwise_leftShift(16 * $keyLength); // 2 * 8 * $keyLength - $max = $max->subtract($one); + $ourPrivate = DH::createKey($params, 16 * $keyLength); // 2 * 8 * $keyLength + $ourPublic = $ourPrivate->getPublicKey()->toBigInteger(); + $ourPublicBytes = $ourPublic->toBytes(true); + } - $x = $one->random($one, $max); - $e = $g->modPow($x, $prime); + $data = pack('CNa*', constant($clientKexInitMessage), strlen($ourPublicBytes), $ourPublicBytes); - $eBytes = $e->toBytes(true); - } - $data = pack('CNa*', constant($clientKexInitMessage), strlen($eBytes), $eBytes); + $this->send_binary_packet($data); - if (!$this->_send_binary_packet($data)) { - $this->bitmap = 0; - user_error('Connection closed by server'); - return false; - } switch ($clientKexInitMessage) { case 'NET_SSH2_MSG_KEX_ECDH_INIT': - $this->_updateLogHistory('NET_SSH2_MSG_KEXDH_INIT', 'NET_SSH2_MSG_KEX_ECDH_INIT'); + $this->updateLogHistory('NET_SSH2_MSG_KEXDH_INIT', 'NET_SSH2_MSG_KEX_ECDH_INIT'); break; case 'NET_SSH2_MSG_KEXDH_GEX_INIT': - $this->_updateLogHistory('UNKNOWN (32)', 'NET_SSH2_MSG_KEXDH_GEX_INIT'); + $this->updateLogHistory('UNKNOWN (32)', 'NET_SSH2_MSG_KEXDH_GEX_INIT'); } - $response = $this->_get_binary_packet(); - if ($response === false) { - $this->bitmap = 0; - user_error('Connection closed by server'); - return false; - } - if (!strlen($response)) { - return false; - } - extract(unpack('Ctype', $this->_string_shift($response, 1))); + $response = $this->get_binary_packet(); + + list( + $type, + $server_public_host_key, + $theirPublicBytes, + $this->signature + ) = Strings::unpackSSH2('Csss', $response); if ($type != constant($serverKexReplyMessage)) { - user_error("Expected $serverKexReplyMessage"); - return false; + $this->disconnect_helper(NET_SSH2_DISCONNECT_PROTOCOL_ERROR); + throw new \UnexpectedValueException("Expected $serverKexReplyMessage"); } switch ($serverKexReplyMessage) { case 'NET_SSH2_MSG_KEX_ECDH_REPLY': - $this->_updateLogHistory('NET_SSH2_MSG_KEXDH_REPLY', 'NET_SSH2_MSG_KEX_ECDH_REPLY'); + $this->updateLogHistory('NET_SSH2_MSG_KEXDH_REPLY', 'NET_SSH2_MSG_KEX_ECDH_REPLY'); break; case 'NET_SSH2_MSG_KEXDH_GEX_REPLY': - $this->_updateLogHistory('UNKNOWN (33)', 'NET_SSH2_MSG_KEXDH_GEX_REPLY'); - } - - if (strlen($response) < 4) { - return false; + $this->updateLogHistory('UNKNOWN (33)', 'NET_SSH2_MSG_KEXDH_GEX_REPLY'); } - $temp = unpack('Nlength', $this->_string_shift($response, 4)); - $this->server_public_host_key = $server_public_host_key = $this->_string_shift($response, $temp['length']); - - if (strlen($server_public_host_key) < 4) { - return false; - } - $temp = unpack('Nlength', $this->_string_shift($server_public_host_key, 4)); - $public_key_format = $this->_string_shift($server_public_host_key, $temp['length']); - - if (strlen($response) < 4) { - return false; - } - $temp = unpack('Nlength', $this->_string_shift($response, 4)); - $fBytes = $this->_string_shift($response, $temp['length']); - - if (strlen($response) < 4) { - return false; - } - $temp = unpack('Nlength', $this->_string_shift($response, 4)); - $this->signature = $this->_string_shift($response, $temp['length']); + $this->server_public_host_key = $server_public_host_key; + list($public_key_format) = Strings::unpackSSH2('s', $server_public_host_key); if (strlen($this->signature) < 4) { - return false; + throw new \LengthException('The signature needs at least four bytes'); } - $temp = unpack('Nlength', $this->_string_shift($this->signature, 4)); - $this->signature_format = $this->_string_shift($this->signature, $temp['length']); + $temp = unpack('Nlength', substr($this->signature, 0, 4)); + $this->signature_format = substr($this->signature, 4, $temp['length']); - if ($kex_algorithm === 'curve25519-sha256@libssh.org') { - if (strlen($fBytes) !== 32) { - user_error('Received curve25519 public key of invalid length.'); - return false; - } - $key = new BigInteger(sodium_crypto_scalarmult($x, $fBytes), 256); - // sodium_compat doesn't emulate sodium_memzero - // also, with v1 of libsodium API the extension identifies itself as - // libsodium whereas v2 of the libsodium API (what PHP 7.2+ includes) - // identifies itself as sodium. sodium_compat uses the v1 API to - // emulate the v2 API if it's the v1 API that's available - if (extension_loaded('sodium') || extension_loaded('libsodium')) { - sodium_memzero($x); - } - } else { - $f = new BigInteger($fBytes, -256); - $key = $f->modPow($x, $prime); + $keyBytes = DH::computeSecret($ourPrivate, $theirPublicBytes); + if (($keyBytes & "\xFF\x80") === "\x00\x00") { + $keyBytes = substr($keyBytes, 1); + } elseif (($keyBytes[0] & "\x80") === "\x80") { + $keyBytes = "\0$keyBytes"; } - $keyBytes = $key->toBytes(true); - $this->exchange_hash = pack( - 'Na*Na*Na*Na*Na*a*Na*Na*Na*', - strlen($this->identifier), + $this->exchange_hash = Strings::packSSH2( + 's5', $this->identifier, - strlen($this->server_identifier), $this->server_identifier, - strlen($kexinit_payload_client), $kexinit_payload_client, - strlen($kexinit_payload_server), $kexinit_payload_server, - strlen($this->server_public_host_key), - $this->server_public_host_key, - $exchange_hash_rfc4419, - strlen($eBytes), - $eBytes, - strlen($fBytes), - $fBytes, - strlen($keyBytes), + $this->server_public_host_key + ); + $this->exchange_hash .= $exchange_hash_rfc4419; + $this->exchange_hash .= Strings::packSSH2( + 's3', + $ourPublicBytes, + $theirPublicBytes, $keyBytes ); @@ -1808,113 +1767,131 @@ function _key_exchange($kexinit_payload_server = false) } switch ($server_host_key_algorithm) { - case 'ssh-dss': - $expected_key_format = 'ssh-dss'; - break; - //case 'rsa-sha2-256': - //case 'rsa-sha2-512': + case 'rsa-sha2-256': + case 'rsa-sha2-512': //case 'ssh-rsa': - default: $expected_key_format = 'ssh-rsa'; + break; + default: + $expected_key_format = $server_host_key_algorithm; } - if ($public_key_format != $expected_key_format || $this->signature_format != $server_host_key_algorithm) { switch (true) { case $this->signature_format == $server_host_key_algorithm: case $server_host_key_algorithm != 'rsa-sha2-256' && $server_host_key_algorithm != 'rsa-sha2-512': case $this->signature_format != 'ssh-rsa': - user_error('Server Host Key Algorithm Mismatch'); - return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); + $this->disconnect_helper(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE); + throw new \RuntimeException('Server Host Key Algorithm Mismatch (' . $this->signature_format . ' vs ' . $server_host_key_algorithm . ')'); } } - $packet = pack( - 'C', - NET_SSH2_MSG_NEWKEYS - ); + $packet = pack('C', NET_SSH2_MSG_NEWKEYS); + $this->send_binary_packet($packet); - if (!$this->_send_binary_packet($packet)) { - return false; - } - - $response = $this->_get_binary_packet(); + $response = $this->get_binary_packet(); if ($response === false) { - $this->bitmap = 0; - user_error('Connection closed by server'); - return false; - } - - if (!strlen($response)) { - return false; + $this->disconnect_helper(NET_SSH2_DISCONNECT_CONNECTION_LOST); + throw new ConnectionClosedException('Connection closed by server'); } - extract(unpack('Ctype', $this->_string_shift($response, 1))); + list($type) = Strings::unpackSSH2('C', $response); if ($type != NET_SSH2_MSG_NEWKEYS) { - user_error('Expected SSH_MSG_NEWKEYS'); - return false; + $this->disconnect_helper(NET_SSH2_DISCONNECT_PROTOCOL_ERROR); + throw new \UnexpectedValueException('Expected SSH_MSG_NEWKEYS'); } $keyBytes = pack('Na*', strlen($keyBytes), $keyBytes); - $this->encrypt = $this->_encryption_algorithm_to_crypt_instance($encrypt); + $this->encrypt = self::encryption_algorithm_to_crypt_instance($encrypt); if ($this->encrypt) { - if ($this->crypto_engine) { - $this->encrypt->setPreferredEngine($this->crypto_engine); + if (self::$crypto_engine) { + $this->encrypt->setPreferredEngine(self::$crypto_engine); } - if ($this->encrypt->block_size) { - $this->encrypt_block_size = $this->encrypt->block_size; + if ($this->encrypt->getBlockLengthInBytes()) { + $this->encrypt_block_size = $this->encrypt->getBlockLengthInBytes(); } - $this->encrypt->enableContinuousBuffer(); $this->encrypt->disablePadding(); - if ($this->encrypt->getBlockLength()) { - $this->encrypt_block_size = $this->encrypt->getBlockLength() >> 3; + if ($this->encrypt->usesIV()) { + $iv = $kexHash->hash($keyBytes . $this->exchange_hash . 'A' . $this->session_id); + while ($this->encrypt_block_size > strlen($iv)) { + $iv .= $kexHash->hash($keyBytes . $this->exchange_hash . $iv); + } + $this->encrypt->setIV(substr($iv, 0, $this->encrypt_block_size)); } - $iv = $kexHash->hash($keyBytes . $this->exchange_hash . 'A' . $this->session_id); - while ($this->encrypt_block_size > strlen($iv)) { - $iv.= $kexHash->hash($keyBytes . $this->exchange_hash . $iv); + switch ($encrypt) { + case 'aes128-gcm@openssh.com': + case 'aes256-gcm@openssh.com': + $nonce = $kexHash->hash($keyBytes . $this->exchange_hash . 'A' . $this->session_id); + $this->encryptFixedPart = substr($nonce, 0, 4); + $this->encryptInvocationCounter = substr($nonce, 4, 8); + // fall-through + case 'chacha20-poly1305@openssh.com': + break; + default: + $this->encrypt->enableContinuousBuffer(); } - $this->encrypt->setIV(substr($iv, 0, $this->encrypt_block_size)); $key = $kexHash->hash($keyBytes . $this->exchange_hash . 'C' . $this->session_id); while ($encryptKeyLength > strlen($key)) { - $key.= $kexHash->hash($keyBytes . $this->exchange_hash . $key); + $key .= $kexHash->hash($keyBytes . $this->exchange_hash . $key); + } + switch ($encrypt) { + case 'chacha20-poly1305@openssh.com': + $encryptKeyLength = 32; + $this->lengthEncrypt = self::encryption_algorithm_to_crypt_instance($encrypt); + $this->lengthEncrypt->setKey(substr($key, 32, 32)); } $this->encrypt->setKey(substr($key, 0, $encryptKeyLength)); - - $this->encrypt->name = $decrypt; + $this->encryptName = $encrypt; } - $this->decrypt = $this->_encryption_algorithm_to_crypt_instance($decrypt); + $this->decrypt = self::encryption_algorithm_to_crypt_instance($decrypt); if ($this->decrypt) { - if ($this->crypto_engine) { - $this->decrypt->setPreferredEngine($this->crypto_engine); + if (self::$crypto_engine) { + $this->decrypt->setPreferredEngine(self::$crypto_engine); } - if ($this->decrypt->block_size) { - $this->decrypt_block_size = $this->decrypt->block_size; + if ($this->decrypt->getBlockLengthInBytes()) { + $this->decrypt_block_size = $this->decrypt->getBlockLengthInBytes(); } - $this->decrypt->enableContinuousBuffer(); $this->decrypt->disablePadding(); - if ($this->decrypt->getBlockLength()) { - $this->decrypt_block_size = $this->decrypt->getBlockLength() >> 3; + if ($this->decrypt->usesIV()) { + $iv = $kexHash->hash($keyBytes . $this->exchange_hash . 'B' . $this->session_id); + while ($this->decrypt_block_size > strlen($iv)) { + $iv .= $kexHash->hash($keyBytes . $this->exchange_hash . $iv); + } + $this->decrypt->setIV(substr($iv, 0, $this->decrypt_block_size)); } - $iv = $kexHash->hash($keyBytes . $this->exchange_hash . 'B' . $this->session_id); - while ($this->decrypt_block_size > strlen($iv)) { - $iv.= $kexHash->hash($keyBytes . $this->exchange_hash . $iv); + switch ($decrypt) { + case 'aes128-gcm@openssh.com': + case 'aes256-gcm@openssh.com': + // see https://tools.ietf.org/html/rfc5647#section-7.1 + $nonce = $kexHash->hash($keyBytes . $this->exchange_hash . 'B' . $this->session_id); + $this->decryptFixedPart = substr($nonce, 0, 4); + $this->decryptInvocationCounter = substr($nonce, 4, 8); + // fall-through + case 'chacha20-poly1305@openssh.com': + break; + default: + $this->decrypt->enableContinuousBuffer(); } - $this->decrypt->setIV(substr($iv, 0, $this->decrypt_block_size)); $key = $kexHash->hash($keyBytes . $this->exchange_hash . 'D' . $this->session_id); while ($decryptKeyLength > strlen($key)) { - $key.= $kexHash->hash($keyBytes . $this->exchange_hash . $key); + $key .= $kexHash->hash($keyBytes . $this->exchange_hash . $key); + } + switch ($decrypt) { + case 'chacha20-poly1305@openssh.com': + $decryptKeyLength = 32; + $this->lengthDecrypt = self::encryption_algorithm_to_crypt_instance($decrypt); + $this->lengthDecrypt->setKey(substr($key, 32, 32)); } $this->decrypt->setKey(substr($key, 0, $decryptKeyLength)); - - $this->decrypt->name = $decrypt; + $this->decryptName = $decrypt; } /* The "arcfour128" algorithm is the RC4 cipher, as described in @@ -1931,77 +1908,47 @@ function _key_exchange($kexinit_payload_server = false) $this->decrypt->decrypt(str_repeat("\0", 1536)); } - $mac_algorithm_out = $this->_array_intersect_first($c2s_mac_algorithms, $this->mac_algorithms_client_to_server); - if ($mac_algorithm_out === false) { - user_error('No compatible client to server message authentication algorithms found'); - return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); + if (!$this->encrypt->usesNonce()) { + list($this->hmac_create, $createKeyLength) = self::mac_algorithm_to_hash_instance($mac_algorithm_out); + } else { + $this->hmac_create = new \stdClass(); + $this->hmac_create_name = $mac_algorithm_out; + //$mac_algorithm_out = 'none'; + $createKeyLength = 0; } - $createKeyLength = 0; // ie. $mac_algorithm == 'none' - switch ($mac_algorithm_out) { - case 'hmac-sha2-256': - $this->hmac_create = new Hash('sha256'); - $createKeyLength = 32; - break; - case 'hmac-sha1': - $this->hmac_create = new Hash('sha1'); - $createKeyLength = 20; - break; - case 'hmac-sha1-96': - $this->hmac_create = new Hash('sha1-96'); - $createKeyLength = 20; - break; - case 'hmac-md5': - $this->hmac_create = new Hash('md5'); - $createKeyLength = 16; - break; - case 'hmac-md5-96': - $this->hmac_create = new Hash('md5-96'); - $createKeyLength = 16; + if ($this->hmac_create instanceof Hash) { + $key = $kexHash->hash($keyBytes . $this->exchange_hash . 'E' . $this->session_id); + while ($createKeyLength > strlen($key)) { + $key .= $kexHash->hash($keyBytes . $this->exchange_hash . $key); + } + $this->hmac_create->setKey(substr($key, 0, $createKeyLength)); + $this->hmac_create_name = $mac_algorithm_out; + $this->hmac_create_etm = preg_match('#-etm@openssh\.com$#', $mac_algorithm_out); } - $this->hmac_create->name = $mac_algorithm_out; - $checkKeyLength = 0; - $this->hmac_size = 0; - switch ($mac_algorithm_in) { - case 'hmac-sha2-256': - $this->hmac_check = new Hash('sha256'); - $checkKeyLength = 32; - $this->hmac_size = 32; - break; - case 'hmac-sha1': - $this->hmac_check = new Hash('sha1'); - $checkKeyLength = 20; - $this->hmac_size = 20; - break; - case 'hmac-sha1-96': - $this->hmac_check = new Hash('sha1-96'); - $checkKeyLength = 20; - $this->hmac_size = 12; - break; - case 'hmac-md5': - $this->hmac_check = new Hash('md5'); - $checkKeyLength = 16; - $this->hmac_size = 16; - break; - case 'hmac-md5-96': - $this->hmac_check = new Hash('md5-96'); - $checkKeyLength = 16; - $this->hmac_size = 12; + if (!$this->decrypt->usesNonce()) { + list($this->hmac_check, $checkKeyLength) = self::mac_algorithm_to_hash_instance($mac_algorithm_in); + $this->hmac_size = $this->hmac_check->getLengthInBytes(); + } else { + $this->hmac_check = new \stdClass(); + $this->hmac_check_name = $mac_algorithm_in; + //$mac_algorithm_in = 'none'; + $checkKeyLength = 0; + $this->hmac_size = 0; } - $this->hmac_check->name = $mac_algorithm_in; - $key = $kexHash->hash($keyBytes . $this->exchange_hash . 'E' . $this->session_id); - while ($createKeyLength > strlen($key)) { - $key.= $kexHash->hash($keyBytes . $this->exchange_hash . $key); + if ($this->hmac_check instanceof Hash) { + $key = $kexHash->hash($keyBytes . $this->exchange_hash . 'F' . $this->session_id); + while ($checkKeyLength > strlen($key)) { + $key .= $kexHash->hash($keyBytes . $this->exchange_hash . $key); + } + $this->hmac_check->setKey(substr($key, 0, $checkKeyLength)); + $this->hmac_check_name = $mac_algorithm_in; + $this->hmac_check_etm = preg_match('#-etm@openssh\.com$#', $mac_algorithm_in); } - $this->hmac_create->setKey(substr($key, 0, $createKeyLength)); - $key = $kexHash->hash($keyBytes . $this->exchange_hash . 'F' . $this->session_id); - while ($checkKeyLength > strlen($key)) { - $key.= $kexHash->hash($keyBytes . $this->exchange_hash . $key); - } - $this->hmac_check->setKey(substr($key, 0, $checkKeyLength)); + $this->regenerate_compression_context = $this->regenerate_decompression_context = true; return true; } @@ -2011,17 +1958,17 @@ function _key_exchange($kexinit_payload_server = false) * * @param string $algorithm Name of the encryption algorithm * @return int|null Number of bytes as an integer or null for unknown - * @access private */ - function _encryption_algorithm_to_key_size($algorithm) + private function encryption_algorithm_to_key_size($algorithm) { - if ($this->bad_key_size_fix && $this->_bad_algorithm_candidate($algorithm)) { + if ($this->bad_key_size_fix && self::bad_algorithm_candidate($algorithm)) { return 16; } switch ($algorithm) { case 'none': return 0; + case 'aes128-gcm@openssh.com': case 'aes128-cbc': case 'aes128-ctr': case 'arcfour': @@ -2038,6 +1985,7 @@ function _encryption_algorithm_to_key_size($algorithm) case 'twofish192-cbc': case 'twofish192-ctr': return 24; + case 'aes256-gcm@openssh.com': case 'aes256-cbc': case 'aes256-ctr': case 'arcfour256': @@ -2045,64 +1993,103 @@ function _encryption_algorithm_to_key_size($algorithm) case 'twofish256-cbc': case 'twofish256-ctr': return 32; + case 'chacha20-poly1305@openssh.com': + return 64; } return null; } /** * Maps an encryption algorithm name to an instance of a subclass of - * \phpseclib\Crypt\Base. + * \phpseclib3\Crypt\Common\SymmetricKey. * * @param string $algorithm Name of the encryption algorithm - * @return mixed Instance of \phpseclib\Crypt\Base or null for unknown - * @access private + * @return SymmetricKey|null */ - function _encryption_algorithm_to_crypt_instance($algorithm) + private static function encryption_algorithm_to_crypt_instance($algorithm) { switch ($algorithm) { case '3des-cbc': - return new TripleDES(); + return new TripleDES('cbc'); case '3des-ctr': - return new TripleDES(Base::MODE_CTR); + return new TripleDES('ctr'); case 'aes256-cbc': case 'aes192-cbc': case 'aes128-cbc': - return new Rijndael(); + return new Rijndael('cbc'); case 'aes256-ctr': case 'aes192-ctr': case 'aes128-ctr': - return new Rijndael(Base::MODE_CTR); + return new Rijndael('ctr'); case 'blowfish-cbc': - return new Blowfish(); + return new Blowfish('cbc'); case 'blowfish-ctr': - return new Blowfish(Base::MODE_CTR); + return new Blowfish('ctr'); case 'twofish128-cbc': case 'twofish192-cbc': case 'twofish256-cbc': case 'twofish-cbc': - return new Twofish(); + return new Twofish('cbc'); case 'twofish128-ctr': case 'twofish192-ctr': case 'twofish256-ctr': - return new Twofish(Base::MODE_CTR); + return new Twofish('ctr'); case 'arcfour': case 'arcfour128': case 'arcfour256': return new RC4(); + case 'aes128-gcm@openssh.com': + case 'aes256-gcm@openssh.com': + return new Rijndael('gcm'); + case 'chacha20-poly1305@openssh.com': + return new ChaCha20(); } return null; } /** + * Maps an encryption algorithm name to an instance of a subclass of + * \phpseclib3\Crypt\Hash. + * + * @param string $algorithm Name of the encryption algorithm + * @return array{Hash, int}|null + */ + private static function mac_algorithm_to_hash_instance($algorithm) + { + switch ($algorithm) { + case 'umac-64@openssh.com': + case 'umac-64-etm@openssh.com': + return [new Hash('umac-64'), 16]; + case 'umac-128@openssh.com': + case 'umac-128-etm@openssh.com': + return [new Hash('umac-128'), 16]; + case 'hmac-sha2-512': + case 'hmac-sha2-512-etm@openssh.com': + return [new Hash('sha512'), 64]; + case 'hmac-sha2-256': + case 'hmac-sha2-256-etm@openssh.com': + return [new Hash('sha256'), 32]; + case 'hmac-sha1': + case 'hmac-sha1-etm@openssh.com': + return [new Hash('sha1'), 20]; + case 'hmac-sha1-96': + return [new Hash('sha1-96'), 20]; + case 'hmac-md5': + return [new Hash('md5'), 16]; + case 'hmac-md5-96': + return [new Hash('md5-96'), 16]; + } + } + + /* * Tests whether or not proposed algorithm has a potential for issues * * @link https://www.chiark.greenend.org.uk/~sgtatham/putty/wishlist/ssh2-aesctr-openssh.html * @link https://bugzilla.mindrot.org/show_bug.cgi?id=1291 * @param string $algorithm Name of the encryption algorithm * @return bool - * @access private */ - function _bad_algorithm_candidate($algorithm) + private static function bad_algorithm_candidate($algorithm) { switch ($algorithm) { case 'arcfour256': @@ -2117,55 +2104,118 @@ function _bad_algorithm_candidate($algorithm) /** * Login * - * The $password parameter can be a plaintext password, a \phpseclib\Crypt\RSA object or an array + * The $password parameter can be a plaintext password, a \phpseclib3\Crypt\RSA|EC|DSA object, a \phpseclib3\System\SSH\Agent object or an array * * @param string $username + * @param string|AsymmetricKey|array[]|Agent|null ...$args * @return bool * @see self::_login() - * @access public */ - function login($username) + public function login($username, ...$args) { - $args = func_get_args(); - $this->auth[] = $args; + $this->auth[] = func_get_args(); // try logging with 'none' as an authentication method first since that's what // PuTTY does - if (substr($this->server_identifier, 0, 13) != 'SSH-2.0-CoreFTP') { - if ($this->_login($username)) { + if (substr($this->server_identifier, 0, 15) != 'SSH-2.0-CoreFTP' && $this->auth_methods_to_continue === null) { + if ($this->sublogin($username)) { return true; } - if (count($args) == 1) { + if (!count($args)) { return false; } } - return call_user_func_array(array(&$this, '_login'), $args); + return $this->sublogin($username, ...$args); } /** * Login Helper * * @param string $username + * @param string ...$args * @return bool * @see self::_login_helper() - * @access private */ - function _login($username) + protected function sublogin($username, ...$args) { if (!($this->bitmap & self::MASK_CONSTRUCTOR)) { - if (!$this->_connect()) { - return false; - } + $this->connect(); } - $args = array_slice(func_get_args(), 1); if (empty($args)) { - return $this->_login_helper($username); + return $this->login_helper($username); } foreach ($args as $arg) { - if ($this->_login_helper($username, $arg)) { - return true; + switch (true) { + case $arg instanceof PublicKey: + throw new \UnexpectedValueException('A PublicKey object was passed to the login method instead of a PrivateKey object'); + case $arg instanceof PrivateKey: + case $arg instanceof Agent: + case is_array($arg): + case Strings::is_stringable($arg): + break; + default: + throw new \UnexpectedValueException('$password needs to either be an instance of \phpseclib3\Crypt\Common\PrivateKey, \System\SSH\Agent, an array or a string'); + } + } + + while (count($args)) { + if (!$this->auth_methods_to_continue || !$this->smartMFA) { + $newargs = $args; + $args = []; + } else { + $newargs = []; + foreach ($this->auth_methods_to_continue as $method) { + switch ($method) { + case 'publickey': + foreach ($args as $key => $arg) { + if ($arg instanceof PrivateKey || $arg instanceof Agent) { + $newargs[] = $arg; + unset($args[$key]); + break; + } + } + break; + case 'keyboard-interactive': + $hasArray = $hasString = false; + foreach ($args as $arg) { + if ($hasArray || is_array($arg)) { + $hasArray = true; + break; + } + if ($hasString || Strings::is_stringable($arg)) { + $hasString = true; + break; + } + } + if ($hasArray && $hasString) { + foreach ($args as $key => $arg) { + if (is_array($arg)) { + $newargs[] = $arg; + break 2; + } + } + } + // fall-through + case 'password': + foreach ($args as $key => $arg) { + $newargs[] = $arg; + unset($args[$key]); + break; + } + } + } + } + + if (!count($newargs)) { + return false; + } + + foreach ($newargs as $arg) { + if ($this->login_helper($username, $arg)) { + return true; + } } } return false; @@ -2174,69 +2224,59 @@ function _login($username) /** * Login Helper * + * {@internal It might be worthwhile, at some point, to protect against {@link http://tools.ietf.org/html/rfc4251#section-9.3.9 traffic analysis} + * by sending dummy SSH_MSG_IGNORE messages.} + * * @param string $username - * @param string $password + * @param string|AsymmetricKey|array[]|Agent|null ...$args * @return bool - * @access private - * @internal It might be worthwhile, at some point, to protect against {@link http://tools.ietf.org/html/rfc4251#section-9.3.9 traffic analysis} - * by sending dummy SSH_MSG_IGNORE messages. + * @throws \UnexpectedValueException on receipt of unexpected packets + * @throws \RuntimeException on other errors */ - function _login_helper($username, $password = null) + private function login_helper($username, $password = null) { if (!($this->bitmap & self::MASK_CONNECTED)) { return false; } if (!($this->bitmap & self::MASK_LOGIN_REQ)) { - $packet = pack( - 'CNa*', - NET_SSH2_MSG_SERVICE_REQUEST, - strlen('ssh-userauth'), - 'ssh-userauth' - ); - - if (!$this->_send_binary_packet($packet)) { - return false; - } + $packet = Strings::packSSH2('Cs', NET_SSH2_MSG_SERVICE_REQUEST, 'ssh-userauth'); + $this->send_binary_packet($packet); - $response = $this->_get_binary_packet(); - if ($response === false) { + try { + $response = $this->get_binary_packet(); + } catch (\Exception $e) { if ($this->retry_connect) { $this->retry_connect = false; - if (!$this->_connect()) { - return false; - } - return $this->_login_helper($username, $password); + $this->connect(); + return $this->login_helper($username, $password); } - $this->bitmap = 0; - user_error('Connection closed by server'); - return false; - } - - if (strlen($response) < 4) { - return false; + $this->disconnect_helper(NET_SSH2_DISCONNECT_CONNECTION_LOST); + throw new ConnectionClosedException('Connection closed by server'); } - extract(unpack('Ctype', $this->_string_shift($response, 1))); - if ($type != NET_SSH2_MSG_SERVICE_ACCEPT) { - user_error('Expected SSH_MSG_SERVICE_ACCEPT'); - return false; + list($type, $service) = Strings::unpackSSH2('Cs', $response); + if ($type != NET_SSH2_MSG_SERVICE_ACCEPT || $service != 'ssh-userauth') { + $this->disconnect_helper(NET_SSH2_DISCONNECT_PROTOCOL_ERROR); + throw new \UnexpectedValueException('Expected SSH_MSG_SERVICE_ACCEPT'); } $this->bitmap |= self::MASK_LOGIN_REQ; } if (strlen($this->last_interactive_response)) { - return !is_string($password) && !is_array($password) ? false : $this->_keyboard_interactive_process($password); + return !Strings::is_stringable($password) && !is_array($password) ? false : $this->keyboard_interactive_process($password); } - if ($password instanceof RSA) { - return $this->_privatekey_login($username, $password); - } elseif ($password instanceof Agent) { - return $this->_ssh_agent_login($username, $password); + if ($password instanceof PrivateKey) { + return $this->privatekey_login($username, $password); + } + + if ($password instanceof Agent) { + return $this->ssh_agent_login($username, $password); } if (is_array($password)) { - if ($this->_keyboard_interactive_login($username, $password)) { + if ($this->keyboard_interactive_login($username, $password)) { $this->bitmap |= self::MASK_LOGIN; return true; } @@ -2244,54 +2284,39 @@ function _login_helper($username, $password = null) } if (!isset($password)) { - $packet = pack( - 'CNa*Na*Na*', + $packet = Strings::packSSH2( + 'Cs3', NET_SSH2_MSG_USERAUTH_REQUEST, - strlen($username), $username, - strlen('ssh-connection'), 'ssh-connection', - strlen('none'), 'none' ); - if (!$this->_send_binary_packet($packet)) { - return false; - } - - $response = $this->_get_binary_packet(); - if ($response === false) { - $this->bitmap = 0; - user_error('Connection closed by server'); - return false; - } + $this->send_binary_packet($packet); - if (!strlen($response)) { - return false; - } - extract(unpack('Ctype', $this->_string_shift($response, 1))); + $response = $this->get_binary_packet(); + list($type) = Strings::unpackSSH2('C', $response); switch ($type) { case NET_SSH2_MSG_USERAUTH_SUCCESS: $this->bitmap |= self::MASK_LOGIN; return true; - //case NET_SSH2_MSG_USERAUTH_FAILURE: + case NET_SSH2_MSG_USERAUTH_FAILURE: + list($auth_methods) = Strings::unpackSSH2('L', $response); + $this->auth_methods_to_continue = $auth_methods; + // fall-through default: return false; } } - $packet = pack( - 'CNa*Na*Na*CNa*', + $packet = Strings::packSSH2( + 'Cs3bs', NET_SSH2_MSG_USERAUTH_REQUEST, - strlen($username), $username, - strlen('ssh-connection'), 'ssh-connection', - strlen('password'), 'password', - 0, - strlen($password), + false, $password ); @@ -2299,62 +2324,39 @@ function _login_helper($username, $password = null) if (!defined('NET_SSH2_LOGGING')) { $logged = null; } else { - $logged = pack( - 'CNa*Na*Na*CNa*', + $logged = Strings::packSSH2( + 'Cs3bs', NET_SSH2_MSG_USERAUTH_REQUEST, - strlen('username'), - 'username', - strlen('ssh-connection'), + $username, 'ssh-connection', - strlen('password'), 'password', - 0, - strlen('password'), + false, 'password' ); } - if (!$this->_send_binary_packet($packet, $logged)) { - return false; - } + $this->send_binary_packet($packet, $logged); - $response = $this->_get_binary_packet(); + $response = $this->get_binary_packet(); if ($response === false) { - $this->bitmap = 0; - user_error('Connection closed by server'); return false; } - - if (!strlen($response)) { - return false; - } - extract(unpack('Ctype', $this->_string_shift($response, 1))); - + list($type) = Strings::unpackSSH2('C', $response); switch ($type) { case NET_SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ: // in theory, the password can be changed - $this->_updateLogHistory('UNKNOWN (60)', 'NET_SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ'); - if (strlen($response) < 4) { - return false; - } - extract(unpack('Nlength', $this->_string_shift($response, 4))); - $this->errors[] = 'SSH_MSG_USERAUTH_PASSWD_CHANGEREQ: ' . $this->_string_shift($response, $length); - return $this->_disconnect(NET_SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER); + $this->updateLogHistory('UNKNOWN (60)', 'NET_SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ'); + + list($message) = Strings::unpackSSH2('s', $response); + $this->errors[] = 'SSH_MSG_USERAUTH_PASSWD_CHANGEREQ: ' . $message; + + return $this->disconnect_helper(NET_SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER); case NET_SSH2_MSG_USERAUTH_FAILURE: // can we use keyboard-interactive authentication? if not then either the login is bad or the server employees // multi-factor authentication - if (strlen($response) < 4) { - return false; - } - extract(unpack('Nlength', $this->_string_shift($response, 4))); - $auth_methods = explode(',', $this->_string_shift($response, $length)); - if (!strlen($response)) { - return false; - } - extract(unpack('Cpartial_success', $this->_string_shift($response, 1))); - $partial_success = $partial_success != 0; - + list($auth_methods, $partial_success) = Strings::unpackSSH2('Lb', $response); + $this->auth_methods_to_continue = $auth_methods; if (!$partial_success && in_array('keyboard-interactive', $auth_methods)) { - if ($this->_keyboard_interactive_login($username, $password)) { + if ($this->keyboard_interactive_login($username, $password)) { $this->bitmap |= self::MASK_LOGIN; return true; } @@ -2375,81 +2377,49 @@ function _login_helper($username, $password = null) * See {@link http://tools.ietf.org/html/rfc4256 RFC4256} for details. This is not a full-featured keyboard-interactive authenticator. * * @param string $username - * @param string $password + * @param string|array $password * @return bool - * @access private */ - function _keyboard_interactive_login($username, $password) + private function keyboard_interactive_login($username, $password) { - $packet = pack( - 'CNa*Na*Na*Na*Na*', + $packet = Strings::packSSH2( + 'Cs5', NET_SSH2_MSG_USERAUTH_REQUEST, - strlen($username), $username, - strlen('ssh-connection'), 'ssh-connection', - strlen('keyboard-interactive'), 'keyboard-interactive', - 0, - '', - 0, - '' + '', // language tag + '' // submethods ); + $this->send_binary_packet($packet); - if (!$this->_send_binary_packet($packet)) { - return false; - } - - return $this->_keyboard_interactive_process($password); + return $this->keyboard_interactive_process($password); } /** * Handle the keyboard-interactive requests / responses. * + * @param string|array ...$responses * @return bool - * @access private + * @throws \RuntimeException on connection error */ - function _keyboard_interactive_process() + private function keyboard_interactive_process(...$responses) { - $responses = func_get_args(); - if (strlen($this->last_interactive_response)) { $response = $this->last_interactive_response; } else { - $orig = $response = $this->_get_binary_packet(); - if ($response === false) { - $this->bitmap = 0; - user_error('Connection closed by server'); - return false; - } - } - - if (!strlen($response)) { - return false; + $orig = $response = $this->get_binary_packet(); } - extract(unpack('Ctype', $this->_string_shift($response, 1))); + list($type) = Strings::unpackSSH2('C', $response); switch ($type) { case NET_SSH2_MSG_USERAUTH_INFO_REQUEST: - if (strlen($response) < 4) { - return false; - } - extract(unpack('Nlength', $this->_string_shift($response, 4))); - $this->_string_shift($response, $length); // name; may be empty - if (strlen($response) < 4) { - return false; - } - extract(unpack('Nlength', $this->_string_shift($response, 4))); - $this->_string_shift($response, $length); // instruction; may be empty - if (strlen($response) < 4) { - return false; - } - extract(unpack('Nlength', $this->_string_shift($response, 4))); - $this->_string_shift($response, $length); // language tag; may be empty - if (strlen($response) < 4) { - return false; - } - extract(unpack('Nnum_prompts', $this->_string_shift($response, 4))); + list( + , // name; may be empty + , // instruction; may be empty + , // language tag; may be empty + $num_prompts + ) = Strings::unpackSSH2('s3N', $response); for ($i = 0; $i < count($responses); $i++) { if (is_array($responses[$i])) { @@ -2463,13 +2433,10 @@ function _keyboard_interactive_process() if (isset($this->keyboard_requests_responses)) { for ($i = 0; $i < $num_prompts; $i++) { - if (strlen($response) < 4) { - return false; - } - extract(unpack('Nlength', $this->_string_shift($response, 4))); - // prompt - ie. "Password: "; must not be empty - $prompt = $this->_string_shift($response, $length); - //$echo = $this->_string_shift($response) != chr(0); + list( + $prompt, // prompt - ie. "Password: "; must not be empty + // echo + ) = Strings::unpackSSH2('sC', $response); foreach ($this->keyboard_requests_responses as $key => $value) { if (substr($prompt, 0, strlen($key)) == $key) { $responses[] = $value; @@ -2483,7 +2450,7 @@ function _keyboard_interactive_process() if (strlen($this->last_interactive_response)) { $this->last_interactive_response = ''; } else { - $this->_updateLogHistory('UNKNOWN (60)', 'NET_SSH2_MSG_USERAUTH_INFO_REQUEST'); + $this->updateLogHistory('UNKNOWN (60)', 'NET_SSH2_MSG_USERAUTH_INFO_REQUEST'); } if (!count($responses) && $num_prompts) { @@ -2498,15 +2465,13 @@ function _keyboard_interactive_process() // see http://tools.ietf.org/html/rfc4256#section-3.4 $packet = $logged = pack('CN', NET_SSH2_MSG_USERAUTH_INFO_RESPONSE, count($responses)); for ($i = 0; $i < count($responses); $i++) { - $packet.= pack('Na*', strlen($responses[$i]), $responses[$i]); - $logged.= pack('Na*', strlen('dummy-answer'), 'dummy-answer'); + $packet .= Strings::packSSH2('s', $responses[$i]); + $logged .= Strings::packSSH2('s', 'dummy-answer'); } - if (!$this->_send_binary_packet($packet, $logged)) { - return false; - } + $this->send_binary_packet($packet, $logged); - $this->_updateLogHistory('UNKNOWN (61)', 'NET_SSH2_MSG_USERAUTH_INFO_RESPONSE'); + $this->updateLogHistory('UNKNOWN (61)', 'NET_SSH2_MSG_USERAUTH_INFO_RESPONSE'); /* After receiving the response, the server MUST send either an @@ -2515,10 +2480,12 @@ function _keyboard_interactive_process() */ // maybe phpseclib should force close the connection after x request / responses? unless something like that is done // there could be an infinite loop of request / responses. - return $this->_keyboard_interactive_process(); + return $this->keyboard_interactive_process(); case NET_SSH2_MSG_USERAUTH_SUCCESS: return true; case NET_SSH2_MSG_USERAUTH_FAILURE: + list($auth_methods) = Strings::unpackSSH2('L', $response); + $this->auth_methods_to_continue = $auth_methods; return false; } @@ -2529,16 +2496,15 @@ function _keyboard_interactive_process() * Login with an ssh-agent provided key * * @param string $username - * @param \phpseclib\System\SSH\Agent $agent + * @param \phpseclib3\System\SSH\Agent $agent * @return bool - * @access private */ - function _ssh_agent_login($username, $agent) + private function ssh_agent_login($username, Agent $agent) { $this->agent = $agent; $keys = $agent->requestIdentities(); foreach ($keys as $key) { - if ($this->_privatekey_login($username, $key)) { + if ($this->privatekey_login($username, $key)) { return true; } } @@ -2549,134 +2515,135 @@ function _ssh_agent_login($username, $agent) /** * Login with an RSA private key * + * {@internal It might be worthwhile, at some point, to protect against {@link http://tools.ietf.org/html/rfc4251#section-9.3.9 traffic analysis} + * by sending dummy SSH_MSG_IGNORE messages.} + * * @param string $username - * @param \phpseclib\Crypt\RSA $privatekey + * @param \phpseclib3\Crypt\Common\PrivateKey $privatekey * @return bool - * @access private - * @internal It might be worthwhile, at some point, to protect against {@link http://tools.ietf.org/html/rfc4251#section-9.3.9 traffic analysis} - * by sending dummy SSH_MSG_IGNORE messages. + * @throws \RuntimeException on connection error */ - function _privatekey_login($username, $privatekey) + private function privatekey_login($username, PrivateKey $privatekey) { - // see http://tools.ietf.org/html/rfc4253#page-15 - $publickey = $privatekey->getPublicKey(RSA::PUBLIC_FORMAT_RAW); - if ($publickey === false) { - return false; - } - - $publickey = array( - 'e' => $publickey['e']->toBytes(true), - 'n' => $publickey['n']->toBytes(true) - ); - $publickey = pack( - 'Na*Na*Na*', - strlen('ssh-rsa'), - 'ssh-rsa', - strlen($publickey['e']), - $publickey['e'], - strlen($publickey['n']), - $publickey['n'] - ); + $publickey = $privatekey->getPublicKey(); - switch ($this->signature_format) { - case 'rsa-sha2-512': - $hash = 'sha512'; - $signatureType = 'rsa-sha2-512'; - break; - case 'rsa-sha2-256': - $hash = 'sha256'; - $signatureType = 'rsa-sha2-256'; - break; - //case 'ssh-rsa': - default: - $hash = 'sha1'; - $signatureType = 'ssh-rsa'; + if ($publickey instanceof RSA) { + $privatekey = $privatekey->withPadding(RSA::SIGNATURE_PKCS1); + $algos = ['rsa-sha2-256', 'rsa-sha2-512', 'ssh-rsa']; + if (isset($this->preferred['hostkey'])) { + $algos = array_intersect($this->preferred['hostkey'], $algos); + } + $algo = self::array_intersect_first($algos, $this->server_host_key_algorithms); + switch ($algo) { + case 'rsa-sha2-512': + $hash = 'sha512'; + $signatureType = 'rsa-sha2-512'; + break; + case 'rsa-sha2-256': + $hash = 'sha256'; + $signatureType = 'rsa-sha2-256'; + break; + //case 'ssh-rsa': + default: + $hash = 'sha1'; + $signatureType = 'ssh-rsa'; + } + } elseif ($publickey instanceof EC) { + $privatekey = $privatekey->withSignatureFormat('SSH2'); + $curveName = $privatekey->getCurve(); + switch ($curveName) { + case 'Ed25519': + $hash = 'sha512'; + $signatureType = 'ssh-ed25519'; + break; + case 'secp256r1': // nistp256 + $hash = 'sha256'; + $signatureType = 'ecdsa-sha2-nistp256'; + break; + case 'secp384r1': // nistp384 + $hash = 'sha384'; + $signatureType = 'ecdsa-sha2-nistp384'; + break; + case 'secp521r1': // nistp521 + $hash = 'sha512'; + $signatureType = 'ecdsa-sha2-nistp521'; + break; + default: + if (is_array($curveName)) { + throw new UnsupportedCurveException('Specified Curves are not supported by SSH2'); + } + throw new UnsupportedCurveException('Named Curve of ' . $curveName . ' is not supported by phpseclib3\'s SSH2 implementation'); + } + } elseif ($publickey instanceof DSA) { + $privatekey = $privatekey->withSignatureFormat('SSH2'); + $hash = 'sha1'; + $signatureType = 'ssh-dss'; + } else { + throw new UnsupportedAlgorithmException('Please use either an RSA key, an EC one or a DSA key'); } - $part1 = pack( - 'CNa*Na*Na*', + $publickeyStr = $publickey->toString('OpenSSH', ['binary' => true]); + + $part1 = Strings::packSSH2( + 'Csss', NET_SSH2_MSG_USERAUTH_REQUEST, - strlen($username), $username, - strlen('ssh-connection'), 'ssh-connection', - strlen('publickey'), 'publickey' ); - $part2 = pack('Na*Na*', strlen($signatureType), $signatureType, strlen($publickey), $publickey); + $part2 = Strings::packSSH2('ss', $signatureType, $publickeyStr); $packet = $part1 . chr(0) . $part2; - if (!$this->_send_binary_packet($packet)) { - return false; - } - - $response = $this->_get_binary_packet(); - if ($response === false) { - $this->bitmap = 0; - user_error('Connection closed by server'); - return false; - } + $this->send_binary_packet($packet); - if (!strlen($response)) { - return false; - } - extract(unpack('Ctype', $this->_string_shift($response, 1))); + $response = $this->get_binary_packet(); + list($type) = Strings::unpackSSH2('C', $response); switch ($type) { case NET_SSH2_MSG_USERAUTH_FAILURE: - if (strlen($response) < 4) { - return false; - } - extract(unpack('Nlength', $this->_string_shift($response, 4))); - $this->errors[] = 'SSH_MSG_USERAUTH_FAILURE: ' . $this->_string_shift($response, $length); + list($auth_methods) = Strings::unpackSSH2('L', $response); + $this->auth_methods_to_continue = $auth_methods; + $this->errors[] = 'SSH_MSG_USERAUTH_FAILURE'; return false; case NET_SSH2_MSG_USERAUTH_PK_OK: // we'll just take it on faith that the public key blob and the public key algorithm name are as // they should be - $this->_updateLogHistory('UNKNOWN (60)', 'NET_SSH2_MSG_USERAUTH_PK_OK'); + $this->updateLogHistory('UNKNOWN (60)', 'NET_SSH2_MSG_USERAUTH_PK_OK'); break; case NET_SSH2_MSG_USERAUTH_SUCCESS: $this->bitmap |= self::MASK_LOGIN; return true; default: - user_error('Unexpected response to publickey authentication pt 1'); - return $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION); + $this->disconnect_helper(NET_SSH2_DISCONNECT_BY_APPLICATION); + throw new ConnectionClosedException('Unexpected response to publickey authentication pt 1'); } $packet = $part1 . chr(1) . $part2; - $privatekey->setSignatureMode(RSA::SIGNATURE_PKCS1); - $privatekey->setHash($hash); - $signature = $privatekey->sign(pack('Na*a*', strlen($this->session_id), $this->session_id, $packet)); - $signature = pack('Na*Na*', strlen($signatureType), $signatureType, strlen($signature), $signature); - $packet.= pack('Na*', strlen($signature), $signature); - - if (!$this->_send_binary_packet($packet)) { - return false; + $privatekey = $privatekey->withHash($hash); + $signature = $privatekey->sign(Strings::packSSH2('s', $this->session_id) . $packet); + if ($publickey instanceof RSA) { + $signature = Strings::packSSH2('ss', $signatureType, $signature); } + $packet .= Strings::packSSH2('s', $signature); - $response = $this->_get_binary_packet(); - if ($response === false) { - $this->bitmap = 0; - user_error('Connection closed by server'); - return false; - } + $this->send_binary_packet($packet); - if (!strlen($response)) { - return false; - } - extract(unpack('Ctype', $this->_string_shift($response, 1))); + $response = $this->get_binary_packet(); + list($type) = Strings::unpackSSH2('C', $response); switch ($type) { case NET_SSH2_MSG_USERAUTH_FAILURE: // either the login is bad or the server employs multi-factor authentication + list($auth_methods) = Strings::unpackSSH2('L', $response); + $this->auth_methods_to_continue = $auth_methods; return false; case NET_SSH2_MSG_USERAUTH_SUCCESS: $this->bitmap |= self::MASK_LOGIN; return true; } - user_error('Unexpected response to publickey authentication pt 2'); - return $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION); + $this->disconnect_helper(NET_SSH2_DISCONNECT_BY_APPLICATION); + throw new ConnectionClosedException('Unexpected response to publickey authentication pt 2'); } /** @@ -2686,9 +2653,8 @@ function _privatekey_login($username, $privatekey) * Setting $timeout to false or 0 will mean there is no timeout. * * @param mixed $timeout - * @access public */ - function setTimeout($timeout) + public function setTimeout($timeout) { $this->timeout = $this->curTimeout = $timeout; } @@ -2699,9 +2665,8 @@ function setTimeout($timeout) * Sends an SSH2_MSG_IGNORE message every x seconds, if x is a positive non-zero number. * * @param int $interval - * @access public */ - function setKeepAlive($interval) + public function setKeepAlive($interval) { $this->keepAlive = $interval; } @@ -2709,9 +2674,8 @@ function setKeepAlive($interval) /** * Get the output from stdError * - * @access public */ - function getStdError() + public function getStdError() { return $this->stdErrorLog; } @@ -2719,15 +2683,15 @@ function getStdError() /** * Execute Command * - * If $callback is set to false then \phpseclib\Net\SSH2::_get_channel_packet(self::CHANNEL_EXEC) will need to be called manually. + * If $callback is set to false then \phpseclib3\Net\SSH2::get_channel_packet(self::CHANNEL_EXEC) will need to be called manually. * In all likelihood, this is not a feature you want to be taking advantage of. * * @param string $command - * @param Callback $callback - * @return string - * @access public + * @return string|bool + * @psalm-return ($callback is callable ? bool : string|bool) + * @throws \RuntimeException on connection error */ - function exec($command, $callback = null) + public function exec($command, callable $callback = null) { $this->curTimeout = $this->timeout; $this->is_timeout = false; @@ -2738,8 +2702,7 @@ function exec($command, $callback = null) } if ($this->in_request_pty_exec) { - user_error('If you want to run multiple exec()\'s you will need to disable (and re-enable if appropriate) a PTY for each one.'); - return false; + throw new \RuntimeException('If you want to run multiple exec()\'s you will need to disable (and re-enable if appropriate) a PTY for each one.'); } // RFC4254 defines the (client) window size as "bytes the other party can send before it must wait for the window to @@ -2751,75 +2714,49 @@ function exec($command, $callback = null) // uses 0x4000, that's what will be used here, as well. $packet_size = 0x4000; - $packet = pack( - 'CNa*N3', + $packet = Strings::packSSH2( + 'CsN3', NET_SSH2_MSG_CHANNEL_OPEN, - strlen('session'), 'session', self::CHANNEL_EXEC, $this->window_size_server_to_client[self::CHANNEL_EXEC], $packet_size ); - - if (!$this->_send_binary_packet($packet)) { - return false; - } + $this->send_binary_packet($packet); $this->channel_status[self::CHANNEL_EXEC] = NET_SSH2_MSG_CHANNEL_OPEN; - $response = $this->_get_channel_packet(self::CHANNEL_EXEC); - if ($response === false) { - return false; - } + $this->get_channel_packet(self::CHANNEL_EXEC); if ($this->request_pty === true) { $terminal_modes = pack('C', NET_SSH2_TTY_OP_END); - $packet = pack( - 'CNNa*CNa*N5a*', + $packet = Strings::packSSH2( + 'CNsCsN4s', NET_SSH2_MSG_CHANNEL_REQUEST, $this->server_channels[self::CHANNEL_EXEC], - strlen('pty-req'), 'pty-req', 1, - strlen('vt100'), - 'vt100', + $this->term, $this->windowColumns, $this->windowRows, 0, 0, - strlen($terminal_modes), $terminal_modes ); - if (!$this->_send_binary_packet($packet)) { - return false; - } - - $response = $this->_get_binary_packet(); - if ($response === false) { - $this->bitmap = 0; - user_error('Connection closed by server'); - return false; - } + $this->send_binary_packet($packet); - if (!strlen($response)) { - return false; + $this->channel_status[self::CHANNEL_EXEC] = NET_SSH2_MSG_CHANNEL_REQUEST; + if (!$this->get_channel_packet(self::CHANNEL_EXEC)) { + $this->disconnect_helper(NET_SSH2_DISCONNECT_BY_APPLICATION); + throw new \RuntimeException('Unable to request pseudo-terminal'); } - list(, $type) = unpack('C', $this->_string_shift($response, 1)); - switch ($type) { - case NET_SSH2_MSG_CHANNEL_SUCCESS: - break; - case NET_SSH2_MSG_CHANNEL_FAILURE: - default: - user_error('Unable to request pseudo-terminal'); - return $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION); - } $this->in_request_pty_exec = true; } // sending a pty-req SSH_MSG_CHANNEL_REQUEST message is unnecessary and, in fact, in most cases, slows things - // down. the one place where it might be desirable is if you're doing something like \phpseclib\Net\SSH2::exec('ping localhost &'). + // down. the one place where it might be desirable is if you're doing something like \phpseclib3\Net\SSH2::exec('ping localhost &'). // with a pty-req SSH_MSG_CHANNEL_REQUEST, exec() will return immediately and the ping process will then // then immediately terminate. without such a request exec() will loop indefinitely. the ping process won't end but // neither will your script. @@ -2827,24 +2764,19 @@ function exec($command, $callback = null) // although, in theory, the size of SSH_MSG_CHANNEL_REQUEST could exceed the maximum packet size established by // SSH_MSG_CHANNEL_OPEN_CONFIRMATION, RFC4254#section-5.1 states that the "maximum packet size" refers to the // "maximum size of an individual data packet". ie. SSH_MSG_CHANNEL_DATA. RFC4254#section-5.2 corroborates. - $packet = pack( - 'CNNa*CNa*', + $packet = Strings::packSSH2( + 'CNsCs', NET_SSH2_MSG_CHANNEL_REQUEST, $this->server_channels[self::CHANNEL_EXEC], - strlen('exec'), 'exec', 1, - strlen($command), $command ); - if (!$this->_send_binary_packet($packet)) { - return false; - } + $this->send_binary_packet($packet); $this->channel_status[self::CHANNEL_EXEC] = NET_SSH2_MSG_CHANNEL_REQUEST; - $response = $this->_get_channel_packet(self::CHANNEL_EXEC); - if ($response === false) { + if (!$this->get_channel_packet(self::CHANNEL_EXEC)) { return false; } @@ -2856,7 +2788,7 @@ function exec($command, $callback = null) $output = ''; while (true) { - $temp = $this->_get_channel_packet(self::CHANNEL_EXEC); + $temp = $this->get_channel_packet(self::CHANNEL_EXEC); switch (true) { case $temp === true: return is_callable($callback) ? true : $output; @@ -2864,12 +2796,12 @@ function exec($command, $callback = null) return false; default: if (is_callable($callback)) { - if (call_user_func($callback, $temp) === true) { - $this->_close_channel(self::CHANNEL_EXEC); + if ($callback($temp) === true) { + $this->close_channel(self::CHANNEL_EXEC); return true; } } else { - $output.= $temp; + $output .= $temp; } } } @@ -2881,9 +2813,10 @@ function exec($command, $callback = null) * @see self::read() * @see self::write() * @return bool - * @access private + * @throws \UnexpectedValueException on receipt of unexpected packets + * @throws \RuntimeException on other errors */ - function _initShell() + private function initShell() { if ($this->in_request_pty_exec === true) { return true; @@ -2892,62 +2825,59 @@ function _initShell() $this->window_size_server_to_client[self::CHANNEL_SHELL] = $this->window_size; $packet_size = 0x4000; - $packet = pack( - 'CNa*N3', + $packet = Strings::packSSH2( + 'CsN3', NET_SSH2_MSG_CHANNEL_OPEN, - strlen('session'), 'session', self::CHANNEL_SHELL, $this->window_size_server_to_client[self::CHANNEL_SHELL], $packet_size ); - if (!$this->_send_binary_packet($packet)) { - return false; - } + $this->send_binary_packet($packet); $this->channel_status[self::CHANNEL_SHELL] = NET_SSH2_MSG_CHANNEL_OPEN; - $response = $this->_get_channel_packet(self::CHANNEL_SHELL); - if ($response === false) { - return false; - } + $this->get_channel_packet(self::CHANNEL_SHELL); $terminal_modes = pack('C', NET_SSH2_TTY_OP_END); - $packet = pack( - 'CNNa*CNa*N5a*', + $packet = Strings::packSSH2( + 'CNsbsN4s', NET_SSH2_MSG_CHANNEL_REQUEST, $this->server_channels[self::CHANNEL_SHELL], - strlen('pty-req'), 'pty-req', - 1, - strlen('vt100'), - 'vt100', + true, // want reply + $this->term, $this->windowColumns, $this->windowRows, 0, 0, - strlen($terminal_modes), $terminal_modes ); - if (!$this->_send_binary_packet($packet)) { - return false; + $this->send_binary_packet($packet); + + $this->channel_status[self::CHANNEL_SHELL] = NET_SSH2_MSG_CHANNEL_REQUEST; + + if (!$this->get_channel_packet(self::CHANNEL_SHELL)) { + throw new \RuntimeException('Unable to request pty'); } - $packet = pack( - 'CNNa*C', + $packet = Strings::packSSH2( + 'CNsb', NET_SSH2_MSG_CHANNEL_REQUEST, $this->server_channels[self::CHANNEL_SHELL], - strlen('shell'), 'shell', - 1 + true // want reply ); - if (!$this->_send_binary_packet($packet)) { - return false; + $this->send_binary_packet($packet); + + $response = $this->get_channel_packet(self::CHANNEL_SHELL); + if ($response === false) { + throw new \RuntimeException('Unable to request shell'); } - $this->channel_status[self::CHANNEL_SHELL] = NET_SSH2_MSG_IGNORE; + $this->channel_status[self::CHANNEL_SHELL] = NET_SSH2_MSG_CHANNEL_DATA; $this->bitmap |= self::MASK_SHELL; @@ -2960,9 +2890,8 @@ function _initShell() * @see self::read() * @see self::write() * @return int - * @access public */ - function _get_interactive_channel() + private function get_interactive_channel() { switch (true) { case $this->in_subsystem: @@ -2978,9 +2907,8 @@ function _get_interactive_channel() * Return an available open channel * * @return int - * @access public */ - function _get_open_channel() + private function get_open_channel() { $channel = self::CHANNEL_EXEC; do { @@ -2992,6 +2920,39 @@ function _get_open_channel() return false; } + /** + * Request agent forwarding of remote server + * + * @return bool + */ + public function requestAgentForwarding() + { + $request_channel = $this->get_open_channel(); + if ($request_channel === false) { + return false; + } + + $packet = Strings::packSSH2( + 'CNsC', + NET_SSH2_MSG_CHANNEL_REQUEST, + $this->server_channels[$request_channel], + 'auth-agent-req@openssh.com', + 1 + ); + + $this->channel_status[$request_channel] = NET_SSH2_MSG_CHANNEL_REQUEST; + + $this->send_binary_packet($packet); + + if (!$this->get_channel_packet($request_channel)) { + return false; + } + + $this->channel_status[$request_channel] = NET_SSH2_MSG_CHANNEL_OPEN; + + return true; + } + /** * Returns the output of an interactive shell * @@ -3001,28 +2962,26 @@ function _get_open_channel() * @see self::write() * @param string $expect * @param int $mode - * @return string|bool - * @access public + * @return string|bool|null + * @throws \RuntimeException on connection error */ - function read($expect = '', $mode = self::READ_SIMPLE) + public function read($expect = '', $mode = self::READ_SIMPLE) { $this->curTimeout = $this->timeout; $this->is_timeout = false; if (!$this->isAuthenticated()) { - user_error('Operation disallowed prior to login()'); - return false; + throw new InsufficientSetupException('Operation disallowed prior to login()'); } - if (!($this->bitmap & self::MASK_SHELL) && !$this->_initShell()) { - user_error('Unable to initiate an interactive shell session'); - return false; + if (!($this->bitmap & self::MASK_SHELL) && !$this->initShell()) { + throw new \RuntimeException('Unable to initiate an interactive shell session'); } - $channel = $this->_get_interactive_channel(); + $channel = $this->get_interactive_channel(); if ($mode == self::READ_NEXT) { - return $this->_get_channel_packet($channel); + return $this->get_channel_packet($channel); } $match = $expect; @@ -3033,39 +2992,37 @@ function read($expect = '', $mode = self::READ_SIMPLE) } $pos = strlen($match) ? strpos($this->interactiveBuffer, $match) : false; if ($pos !== false) { - return $this->_string_shift($this->interactiveBuffer, $pos + strlen($match)); + return Strings::shift($this->interactiveBuffer, $pos + strlen($match)); } - $response = $this->_get_channel_packet($channel); - if (is_bool($response)) { + $response = $this->get_channel_packet($channel); + if ($response === true) { $this->in_request_pty_exec = false; - return $response ? $this->_string_shift($this->interactiveBuffer, strlen($this->interactiveBuffer)) : false; + return Strings::shift($this->interactiveBuffer, strlen($this->interactiveBuffer)); } - $this->interactiveBuffer.= $response; + $this->interactiveBuffer .= $response; } } /** * Inputs a command into an interactive shell. * - * @see self::read() + * @see SSH2::read() * @param string $cmd - * @return bool - * @access public + * @return void + * @throws \RuntimeException on connection error */ - function write($cmd) + public function write($cmd) { if (!$this->isAuthenticated()) { - user_error('Operation disallowed prior to login()'); - return false; + throw new InsufficientSetupException('Operation disallowed prior to login()'); } - if (!($this->bitmap & self::MASK_SHELL) && !$this->_initShell()) { - user_error('Unable to initiate an interactive shell session'); - return false; + if (!($this->bitmap & self::MASK_SHELL) && !$this->initShell()) { + throw new \RuntimeException('Unable to initiate an interactive shell session'); } - return $this->_send_channel_packet($this->_get_interactive_channel(), $cmd); + $this->send_channel_packet($this->get_interactive_channel(), $cmd); } /** @@ -3080,52 +3037,39 @@ function write($cmd) * @see self::stopSubsystem() * @param string $subsystem * @return bool - * @access public */ - function startSubsystem($subsystem) + public function startSubsystem($subsystem) { $this->window_size_server_to_client[self::CHANNEL_SUBSYSTEM] = $this->window_size; - $packet = pack( - 'CNa*N3', + $packet = Strings::packSSH2( + 'CsN3', NET_SSH2_MSG_CHANNEL_OPEN, - strlen('session'), 'session', self::CHANNEL_SUBSYSTEM, $this->window_size, 0x4000 ); - if (!$this->_send_binary_packet($packet)) { - return false; - } + $this->send_binary_packet($packet); $this->channel_status[self::CHANNEL_SUBSYSTEM] = NET_SSH2_MSG_CHANNEL_OPEN; - $response = $this->_get_channel_packet(self::CHANNEL_SUBSYSTEM); - if ($response === false) { - return false; - } + $this->get_channel_packet(self::CHANNEL_SUBSYSTEM); - $packet = pack( - 'CNNa*CNa*', + $packet = Strings::packSSH2( + 'CNsCs', NET_SSH2_MSG_CHANNEL_REQUEST, $this->server_channels[self::CHANNEL_SUBSYSTEM], - strlen('subsystem'), 'subsystem', 1, - strlen($subsystem), $subsystem ); - if (!$this->_send_binary_packet($packet)) { - return false; - } + $this->send_binary_packet($packet); $this->channel_status[self::CHANNEL_SUBSYSTEM] = NET_SSH2_MSG_CHANNEL_REQUEST; - $response = $this->_get_channel_packet(self::CHANNEL_SUBSYSTEM); - - if ($response === false) { + if (!$this->get_channel_packet(self::CHANNEL_SUBSYSTEM)) { return false; } @@ -3142,12 +3086,11 @@ function startSubsystem($subsystem) * * @see self::startSubsystem() * @return bool - * @access public */ - function stopSubsystem() + public function stopSubsystem() { $this->in_subsystem = false; - $this->_close_channel(self::CHANNEL_SUBSYSTEM); + $this->close_channel(self::CHANNEL_SUBSYSTEM); return true; } @@ -3156,11 +3099,10 @@ function stopSubsystem() * * If read() timed out you might want to just close the channel and have it auto-restart on the next read() call * - * @access public */ - function reset() + public function reset() { - $this->_close_channel($this->_get_interactive_channel()); + $this->close_channel($this->get_interactive_channel()); } /** @@ -3168,9 +3110,8 @@ function reset() * * Did exec() or read() return because they timed out or because they encountered the end? * - * @access public */ - function isTimeout() + public function isTimeout() { return $this->is_timeout; } @@ -3178,14 +3119,14 @@ function isTimeout() /** * Disconnect * - * @access public */ - function disconnect() + public function disconnect() { - $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION); + $this->disconnect_helper(NET_SSH2_DISCONNECT_BY_APPLICATION); if (isset($this->realtime_log_file) && is_resource($this->realtime_log_file)) { fclose($this->realtime_log_file); } + unset(self::$connections[$this->getResourceId()]); } /** @@ -3194,9 +3135,8 @@ function disconnect() * Will be called, automatically, if you're supporting just PHP5. If you're supporting PHP4, you'll need to call * disconnect(). * - * @access public */ - function __destruct() + public function __destruct() { $this->disconnect(); } @@ -3205,9 +3145,8 @@ function __destruct() * Is the connection still active? * * @return bool - * @access public */ - function isConnected() + public function isConnected() { return (bool) ($this->bitmap & self::MASK_CONNECTED); } @@ -3216,9 +3155,8 @@ function isConnected() * Have you successfully been logged in? * * @return bool - * @access public */ - function isAuthenticated() + public function isAuthenticated() { return (bool) ($this->bitmap & self::MASK_LOGIN); } @@ -3229,59 +3167,53 @@ function isAuthenticated() * Inspired by http://php.net/manual/en/mysqli.ping.php * * @return bool - * @access public */ - function ping() + public function ping() { if (!$this->isAuthenticated()) { if (!empty($this->auth)) { - return $this->_reconnect(); + return $this->reconnect(); } return false; } $this->window_size_server_to_client[self::CHANNEL_KEEP_ALIVE] = $this->window_size; $packet_size = 0x4000; - $packet = pack( - 'CNa*N3', + $packet = Strings::packSSH2( + 'CsN3', NET_SSH2_MSG_CHANNEL_OPEN, - strlen('session'), 'session', self::CHANNEL_KEEP_ALIVE, $this->window_size_server_to_client[self::CHANNEL_KEEP_ALIVE], $packet_size ); - if (!@$this->_send_binary_packet($packet)) { - return $this->_reconnect(); - } + try { + $this->send_binary_packet($packet); - $this->channel_status[self::CHANNEL_KEEP_ALIVE] = NET_SSH2_MSG_CHANNEL_OPEN; + $this->channel_status[self::CHANNEL_KEEP_ALIVE] = NET_SSH2_MSG_CHANNEL_OPEN; - $response = @$this->_get_channel_packet(self::CHANNEL_KEEP_ALIVE); - if ($response !== false) { - $this->_close_channel(self::CHANNEL_KEEP_ALIVE); - return true; + $response = $this->get_channel_packet(self::CHANNEL_KEEP_ALIVE); + } catch (\RuntimeException $e) { + return $this->reconnect(); } - return $this->_reconnect(); + $this->close_channel(self::CHANNEL_KEEP_ALIVE); + return true; } /** * In situ reconnect method * * @return boolean - * @access private */ - function _reconnect() + private function reconnect() { - $this->_reset_connection(NET_SSH2_DISCONNECT_CONNECTION_LOST); + $this->reset_connection(NET_SSH2_DISCONNECT_CONNECTION_LOST); $this->retry_connect = true; - if (!$this->_connect()) { - return false; - } + $this->connect(); foreach ($this->auth as $auth) { - $result = call_user_func_array(array(&$this, 'login'), $auth); + $result = $this->login(...$auth); } return $result; } @@ -3290,11 +3222,10 @@ function _reconnect() * Resets a connection for re-use * * @param int $reason - * @access private */ - function _reset_connection($reason) + protected function reset_connection($reason) { - $this->_disconnect($reason); + $this->disconnect_helper($reason); $this->decrypt = $this->encrypt = false; $this->decrypt_block_size = $this->encrypt_block_size = 8; $this->hmac_check = $this->hmac_create = false; @@ -3310,22 +3241,25 @@ function _reset_connection($reason) * See '6. Binary Packet Protocol' of rfc4253 for more info. * * @see self::_send_binary_packet() - * @return string - * @access private + * @param bool $skip_channel_filter + * @return bool|string */ - function _get_binary_packet($skip_channel_filter = false) + private function get_binary_packet($skip_channel_filter = false) { if ($skip_channel_filter) { - $read = array($this->fsock); + if (!is_resource($this->fsock)) { + throw new \InvalidArgumentException('fsock is not a resource.'); + } + $read = [$this->fsock]; $write = $except = null; - if ($this->curTimeout <= 0) { + if (!$this->curTimeout) { if ($this->keepAlive <= 0) { @stream_select($read, $write, $except, null); } else { - if (!@stream_select($read, $write, $except, $this->keepAlive) && !count($read)) { - $this->_send_binary_packet(pack('CN', NET_SSH2_MSG_IGNORE, 0)); - return $this->_get_binary_packet(true); + if (!@stream_select($read, $write, $except, $this->keepAlive)) { + $this->send_binary_packet(pack('CN', NET_SSH2_MSG_IGNORE, 0)); + return $this->get_binary_packet(true); } } } else { @@ -3334,111 +3268,209 @@ function _get_binary_packet($skip_channel_filter = false) return true; } - $read = array($this->fsock); - $write = $except = null; - $start = microtime(true); if ($this->keepAlive > 0 && $this->keepAlive < $this->curTimeout) { - if (!@stream_select($read, $write, $except, $this->keepAlive) && !count($read)) { - $this->_send_binary_packet(pack('CN', NET_SSH2_MSG_IGNORE, 0)); + if (!@stream_select($read, $write, $except, $this->keepAlive)) { + $this->send_binary_packet(pack('CN', NET_SSH2_MSG_IGNORE, 0)); $elapsed = microtime(true) - $start; - $this->curTimeout-= $elapsed; - return $this->_get_binary_packet(true); + $this->curTimeout -= $elapsed; + return $this->get_binary_packet(true); } $elapsed = microtime(true) - $start; - $this->curTimeout-= $elapsed; + $this->curTimeout -= $elapsed; } - $sec = floor($this->curTimeout); - $usec = 1000000 * ($this->curTimeout - $sec); + $sec = (int) floor($this->curTimeout); + $usec = (int) (1000000 * ($this->curTimeout - $sec)); - // on windows this returns a "Warning: Invalid CRT parameters detected" error - if (!@stream_select($read, $write, $except, $sec, $usec) && !count($read)) { + // this can return a "stream_select(): unable to select [4]: Interrupted system call" error + if (!@stream_select($read, $write, $except, $sec, $usec)) { $this->is_timeout = true; return true; } $elapsed = microtime(true) - $start; - $this->curTimeout-= $elapsed; + $this->curTimeout -= $elapsed; } } if (!is_resource($this->fsock) || feof($this->fsock)) { $this->bitmap = 0; - user_error('Connection closed prematurely'); - return false; + $str = 'Connection closed (by server) prematurely'; + if (isset($elapsed)) { + $str .= ' ' . $elapsed . 's'; + } + throw new ConnectionClosedException($str); } $start = microtime(true); $raw = stream_get_contents($this->fsock, $this->decrypt_block_size); if (!strlen($raw)) { - return ''; + $this->bitmap = 0; + throw new ConnectionClosedException('No data received from server'); } - if ($this->decrypt !== false) { - $raw = $this->decrypt->decrypt($raw); - } - if ($raw === false) { - user_error('Unable to decrypt content'); - return false; + if ($this->decrypt) { + switch ($this->decryptName) { + case 'aes128-gcm@openssh.com': + case 'aes256-gcm@openssh.com': + $this->decrypt->setNonce( + $this->decryptFixedPart . + $this->decryptInvocationCounter + ); + Strings::increment_str($this->decryptInvocationCounter); + $this->decrypt->setAAD($temp = Strings::shift($raw, 4)); + extract(unpack('Npacket_length', $temp)); + /** + * @var integer $packet_length + */ + + $raw .= $this->read_remaining_bytes($packet_length - $this->decrypt_block_size + 4); + $stop = microtime(true); + $tag = stream_get_contents($this->fsock, $this->decrypt_block_size); + $this->decrypt->setTag($tag); + $raw = $this->decrypt->decrypt($raw); + $raw = $temp . $raw; + $remaining_length = 0; + break; + case 'chacha20-poly1305@openssh.com': + // This should be impossible, but we are checking anyway to narrow the type for Psalm. + if (!($this->decrypt instanceof ChaCha20)) { + throw new \LogicException('$this->decrypt is not a ' . ChaCha20::class); + } + + $nonce = pack('N2', 0, $this->get_seq_no); + + $this->lengthDecrypt->setNonce($nonce); + $temp = $this->lengthDecrypt->decrypt($aad = Strings::shift($raw, 4)); + extract(unpack('Npacket_length', $temp)); + /** + * @var integer $packet_length + */ + + $raw .= $this->read_remaining_bytes($packet_length - $this->decrypt_block_size + 4); + $stop = microtime(true); + $tag = stream_get_contents($this->fsock, 16); + + $this->decrypt->setNonce($nonce); + $this->decrypt->setCounter(0); + // this is the same approach that's implemented in Salsa20::createPoly1305Key() + // but we don't want to use the same AEAD construction that RFC8439 describes + // for ChaCha20-Poly1305 so we won't rely on it (see Salsa20::poly1305()) + $this->decrypt->setPoly1305Key( + $this->decrypt->encrypt(str_repeat("\0", 32)) + ); + $this->decrypt->setAAD($aad); + $this->decrypt->setCounter(1); + $this->decrypt->setTag($tag); + $raw = $this->decrypt->decrypt($raw); + $raw = $temp . $raw; + $remaining_length = 0; + break; + default: + if (!$this->hmac_check instanceof Hash || !$this->hmac_check_etm) { + $raw = $this->decrypt->decrypt($raw); + break; + } + extract(unpack('Npacket_length', $temp = Strings::shift($raw, 4))); + /** + * @var integer $packet_length + */ + $raw .= $this->read_remaining_bytes($packet_length - $this->decrypt_block_size + 4); + $stop = microtime(true); + $encrypted = $temp . $raw; + $raw = $temp . $this->decrypt->decrypt($raw); + $remaining_length = 0; + } } if (strlen($raw) < 5) { - return false; + $this->bitmap = 0; + throw new \RuntimeException('Plaintext is too short'); } - extract(unpack('Npacket_length/Cpadding_length', $this->_string_shift($raw, 5))); + extract(unpack('Npacket_length/Cpadding_length', Strings::shift($raw, 5))); + /** + * @var integer $packet_length + * @var integer $padding_length + */ - $remaining_length = $packet_length + 4 - $this->decrypt_block_size; - - // quoting , - // "implementations SHOULD check that the packet length is reasonable" - // PuTTY uses 0x9000 as the actual max packet size and so to shall we - if ($remaining_length < -$this->decrypt_block_size || $remaining_length > 0x9000 || $remaining_length % $this->decrypt_block_size != 0) { - if (!$this->bad_key_size_fix && $this->_bad_algorithm_candidate($this->decrypt->name) && !($this->bitmap & SSH2::MASK_LOGIN)) { - $this->bad_key_size_fix = true; - $this->_reset_connection(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); - return false; - } - user_error('Invalid size'); - return false; + if (!isset($remaining_length)) { + $remaining_length = $packet_length + 4 - $this->decrypt_block_size; } - $buffer = ''; - while ($remaining_length > 0) { - $temp = stream_get_contents($this->fsock, $remaining_length); - if ($temp === false || feof($this->fsock)) { - $this->bitmap = 0; - user_error('Error reading from socket'); - return false; - } - $buffer.= $temp; - $remaining_length-= strlen($temp); - } + $buffer = $this->read_remaining_bytes($remaining_length); - $stop = microtime(true); + if (!isset($stop)) { + $stop = microtime(true); + } if (strlen($buffer)) { - $raw.= $this->decrypt !== false ? $this->decrypt->decrypt($buffer) : $buffer; + $raw .= $this->decrypt ? $this->decrypt->decrypt($buffer) : $buffer; } - $payload = $this->_string_shift($raw, $packet_length - $padding_length - 1); - $padding = $this->_string_shift($raw, $padding_length); // should leave $raw empty + $payload = Strings::shift($raw, $packet_length - $padding_length - 1); + $padding = Strings::shift($raw, $padding_length); // should leave $raw empty - if ($this->hmac_check !== false) { + if ($this->hmac_check instanceof Hash) { $hmac = stream_get_contents($this->fsock, $this->hmac_size); if ($hmac === false || strlen($hmac) != $this->hmac_size) { - $this->bitmap = 0; - user_error('Error reading socket'); - return false; - } elseif ($hmac != $this->hmac_check->hash(pack('NNCa*', $this->get_seq_no, $packet_length, $padding_length, $payload . $padding))) { - user_error('Invalid HMAC'); - return false; + $this->disconnect_helper(NET_SSH2_DISCONNECT_MAC_ERROR); + throw new \RuntimeException('Error reading socket'); + } + + $reconstructed = !$this->hmac_check_etm ? + pack('NCa*', $packet_length, $padding_length, $payload . $padding) : + $encrypted; + if (($this->hmac_check->getHash() & "\xFF\xFF\xFF\xFF") == 'umac') { + $this->hmac_check->setNonce("\0\0\0\0" . pack('N', $this->get_seq_no)); + if ($hmac != $this->hmac_check->hash($reconstructed)) { + $this->disconnect_helper(NET_SSH2_DISCONNECT_MAC_ERROR); + throw new \RuntimeException('Invalid UMAC'); + } + } else { + if ($hmac != $this->hmac_check->hash(pack('Na*', $this->get_seq_no, $reconstructed))) { + $this->disconnect_helper(NET_SSH2_DISCONNECT_MAC_ERROR); + throw new \RuntimeException('Invalid HMAC'); + } } } - //if ($this->decompress) { - // $payload = gzinflate(substr($payload, 2)); - //} + switch ($this->decompress) { + case self::NET_SSH2_COMPRESSION_ZLIB_AT_OPENSSH: + if (!$this->isAuthenticated()) { + break; + } + // fall-through + case self::NET_SSH2_COMPRESSION_ZLIB: + if ($this->regenerate_decompression_context) { + $this->regenerate_decompression_context = false; + + $cmf = ord($payload[0]); + $cm = $cmf & 0x0F; + if ($cm != 8) { // deflate + user_error("Only CM = 8 ('deflate') is supported ($cm)"); + } + $cinfo = ($cmf & 0xF0) >> 4; + if ($cinfo > 7) { + user_error("CINFO above 7 is not allowed ($cinfo)"); + } + $windowSize = 1 << ($cinfo + 8); + + $flg = ord($payload[1]); + //$fcheck = $flg && 0x0F; + if ((($cmf << 8) | $flg) % 31) { + user_error('fcheck failed'); + } + $fdict = boolval($flg & 0x20); + $flevel = ($flg & 0xC0) >> 6; + + $this->decompress_context = inflate_init(ZLIB_ENCODING_RAW, ['window' => $cinfo + 8]); + $payload = substr($payload, 2); + } + if ($this->decompress_context) { + $payload = inflate_add($this->decompress_context, $payload, ZLIB_PARTIAL_FLUSH); + } + } $this->get_seq_no++; @@ -3447,11 +3479,67 @@ function _get_binary_packet($skip_channel_filter = false) $message_number = isset($this->message_numbers[ord($payload[0])]) ? $this->message_numbers[ord($payload[0])] : 'UNKNOWN (' . ord($payload[0]) . ')'; $message_number = '<- ' . $message_number . ' (since last: ' . round($current - $this->last_packet, 4) . ', network: ' . round($stop - $start, 4) . 's)'; - $this->_append_log($message_number, $payload); + $this->append_log($message_number, $payload); $this->last_packet = $current; } - return $this->_filter($payload, $skip_channel_filter); + return $this->filter($payload, $skip_channel_filter); + } + + /** + * Read Remaining Bytes + * + * @see self::get_binary_packet() + * @param int $remaining_length + * @return string + */ + private function read_remaining_bytes($remaining_length) + { + if (!$remaining_length) { + return ''; + } + + $adjustLength = false; + if ($this->decrypt) { + switch (true) { + case $this->decryptName == 'aes128-gcm@openssh.com': + case $this->decryptName == 'aes256-gcm@openssh.com': + case $this->decryptName == 'chacha20-poly1305@openssh.com': + case $this->hmac_check instanceof Hash && $this->hmac_check_etm: + $remaining_length += $this->decrypt_block_size - 4; + $adjustLength = true; + } + } + + // quoting , + // "implementations SHOULD check that the packet length is reasonable" + // PuTTY uses 0x9000 as the actual max packet size and so to shall we + // don't do this when GCM mode is used since GCM mode doesn't encrypt the length + if ($remaining_length < -$this->decrypt_block_size || $remaining_length > 0x9000 || $remaining_length % $this->decrypt_block_size != 0) { + if (!$this->bad_key_size_fix && self::bad_algorithm_candidate($this->decrypt ? $this->decryptName : '') && !($this->bitmap & SSH2::MASK_LOGIN)) { + $this->bad_key_size_fix = true; + $this->reset_connection(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); + return false; + } + throw new \RuntimeException('Invalid size'); + } + + if ($adjustLength) { + $remaining_length -= $this->decrypt_block_size - 4; + } + + $buffer = ''; + while ($remaining_length > 0) { + $temp = stream_get_contents($this->fsock, $remaining_length); + if ($temp === false || feof($this->fsock)) { + $this->disconnect_helper(NET_SSH2_DISCONNECT_CONNECTION_LOST); + throw new \RuntimeException('Error reading from socket'); + } + $buffer .= $temp; + $remaining_length -= strlen($temp); + } + + return $buffer; } /** @@ -3460,68 +3548,62 @@ function _get_binary_packet($skip_channel_filter = false) * Because some binary packets need to be ignored... * * @see self::_get_binary_packet() - * @return string - * @access private + * @param string $payload + * @param bool $skip_channel_filter + * @return string|bool */ - function _filter($payload, $skip_channel_filter) + private function filter($payload, $skip_channel_filter) { switch (ord($payload[0])) { case NET_SSH2_MSG_DISCONNECT: - $this->_string_shift($payload, 1); - if (strlen($payload) < 8) { - return false; - } - extract(unpack('Nreason_code/Nlength', $this->_string_shift($payload, 8))); - $this->errors[] = 'SSH_MSG_DISCONNECT: ' . $this->disconnect_reasons[$reason_code] . "\r\n" . $this->_string_shift($payload, $length); + Strings::shift($payload, 1); + list($reason_code, $message) = Strings::unpackSSH2('Ns', $payload); + $this->errors[] = 'SSH_MSG_DISCONNECT: ' . $this->disconnect_reasons[$reason_code] . "\r\n$message"; $this->bitmap = 0; return false; case NET_SSH2_MSG_IGNORE: - $payload = $this->_get_binary_packet($skip_channel_filter); + $payload = $this->get_binary_packet($skip_channel_filter); break; case NET_SSH2_MSG_DEBUG: - $this->_string_shift($payload, 2); - if (strlen($payload) < 4) { - return false; - } - extract(unpack('Nlength', $this->_string_shift($payload, 4))); - $this->errors[] = 'SSH_MSG_DEBUG: ' . $this->_string_shift($payload, $length); - $payload = $this->_get_binary_packet($skip_channel_filter); + Strings::shift($payload, 2); // second byte is "always_display" + list($message) = Strings::unpackSSH2('s', $payload); + $this->errors[] = "SSH_MSG_DEBUG: $message"; + $payload = $this->get_binary_packet($skip_channel_filter); break; case NET_SSH2_MSG_UNIMPLEMENTED: return false; case NET_SSH2_MSG_KEXINIT: if ($this->session_id !== false) { - $this->send_kex_first = false; - if (!$this->_key_exchange($payload)) { + if (!$this->key_exchange($payload)) { $this->bitmap = 0; return false; } - $payload = $this->_get_binary_packet($skip_channel_filter); + $payload = $this->get_binary_packet($skip_channel_filter); } } // see http://tools.ietf.org/html/rfc4252#section-5.4; only called when the encryption has been activated and when we haven't already logged in - if (($this->bitmap & self::MASK_CONNECTED) && !$this->isAuthenticated() && ord($payload[0]) == NET_SSH2_MSG_USERAUTH_BANNER) { - $this->_string_shift($payload, 1); - if (strlen($payload) < 4) { - return false; - } - extract(unpack('Nlength', $this->_string_shift($payload, 4))); - $this->banner_message = $this->_string_shift($payload, $length); - $payload = $this->_get_binary_packet(); + if (($this->bitmap & self::MASK_CONNECTED) && !$this->isAuthenticated() && !is_bool($payload) && ord($payload[0]) == NET_SSH2_MSG_USERAUTH_BANNER) { + Strings::shift($payload, 1); + list($this->banner_message) = Strings::unpackSSH2('s', $payload); + $payload = $this->get_binary_packet(); } // only called when we've already logged in if (($this->bitmap & self::MASK_CONNECTED) && $this->isAuthenticated()) { + if (is_bool($payload)) { + return $payload; + } + switch (ord($payload[0])) { case NET_SSH2_MSG_CHANNEL_REQUEST: if (strlen($payload) == 31) { extract(unpack('cpacket_type/Nchannel/Nlength', $payload)); if (substr($payload, 9, $length) == 'keepalive@openssh.com' && isset($this->server_channels[$channel])) { if (ord(substr($payload, 9 + $length))) { // want reply - $this->_send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_SUCCESS, $this->server_channels[$channel])); + $this->send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_SUCCESS, $this->server_channels[$channel])); } - $payload = $this->_get_binary_packet($skip_channel_filter); + $payload = $this->get_binary_packet($skip_channel_filter); } } break; @@ -3531,45 +3613,36 @@ function _filter($payload, $skip_channel_filter) case NET_SSH2_MSG_CHANNEL_EOF: if (!$skip_channel_filter && !empty($this->server_channels)) { $this->binary_packet_buffer = $payload; - $this->_get_channel_packet(true); - $payload = $this->_get_binary_packet(); + $this->get_channel_packet(true); + $payload = $this->get_binary_packet(); } break; case NET_SSH2_MSG_GLOBAL_REQUEST: // see http://tools.ietf.org/html/rfc4254#section-4 - if (strlen($payload) < 4) { - return false; - } - extract(unpack('Nlength', $this->_string_shift($payload, 4))); - $this->errors[] = 'SSH_MSG_GLOBAL_REQUEST: ' . $this->_string_shift($payload, $length); - - if (!$this->_send_binary_packet(pack('C', NET_SSH2_MSG_REQUEST_FAILURE))) { - return $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION); + Strings::shift($payload, 1); + list($request_name) = Strings::unpackSSH2('s', $payload); + $this->errors[] = "SSH_MSG_GLOBAL_REQUEST: $request_name"; + + try { + $this->send_binary_packet(pack('C', NET_SSH2_MSG_REQUEST_FAILURE)); + } catch (\RuntimeException $e) { + return $this->disconnect_helper(NET_SSH2_DISCONNECT_BY_APPLICATION); } - $payload = $this->_get_binary_packet($skip_channel_filter); + $payload = $this->get_binary_packet($skip_channel_filter); break; case NET_SSH2_MSG_CHANNEL_OPEN: // see http://tools.ietf.org/html/rfc4254#section-5.1 - $this->_string_shift($payload, 1); - if (strlen($payload) < 4) { - return false; - } - extract(unpack('Nlength', $this->_string_shift($payload, 4))); - $data = $this->_string_shift($payload, $length); - if (strlen($payload) < 4) { - return false; - } - extract(unpack('Nserver_channel', $this->_string_shift($payload, 4))); + Strings::shift($payload, 1); + list($data, $server_channel) = Strings::unpackSSH2('sN', $payload); switch ($data) { case 'auth-agent': case 'auth-agent@openssh.com': if (isset($this->agent)) { $new_channel = self::CHANNEL_AGENT_FORWARD; - if (strlen($payload) < 8) { - return false; - } - extract(unpack('Nremote_window_size', $this->_string_shift($payload, 4))); - extract(unpack('Nremote_maximum_packet_size', $this->_string_shift($payload, 4))); + list( + $remote_window_size, + $remote_maximum_packet_size + ) = Strings::unpackSSH2('NN', $payload); $this->packet_size_client_to_server[$new_channel] = $remote_window_size; $this->window_size_server_to_client[$new_channel] = $remote_maximum_packet_size; @@ -3588,39 +3661,35 @@ function _filter($payload, $skip_channel_filter) $this->server_channels[$new_channel] = $server_channel; $this->channel_status[$new_channel] = NET_SSH2_MSG_CHANNEL_OPEN_CONFIRMATION; - if (!$this->_send_binary_packet($packet)) { - return false; - } + $this->send_binary_packet($packet); } break; default: - $packet = pack( - 'CN3a*Na*', - NET_SSH2_MSG_REQUEST_FAILURE, + $packet = Strings::packSSH2( + 'CN2ss', + NET_SSH2_MSG_CHANNEL_OPEN_FAILURE, $server_channel, NET_SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED, - 0, - '', - 0, - '' + '', // description + '' // language tag ); - if (!$this->_send_binary_packet($packet)) { - return $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION); + try { + $this->send_binary_packet($packet); + } catch (\RuntimeException $e) { + return $this->disconnect_helper(NET_SSH2_DISCONNECT_BY_APPLICATION); } } - $payload = $this->_get_binary_packet($skip_channel_filter); + + $payload = $this->get_binary_packet($skip_channel_filter); break; case NET_SSH2_MSG_CHANNEL_WINDOW_ADJUST: - $this->_string_shift($payload, 1); - if (strlen($payload) < 8) { - return false; - } - extract(unpack('Nchannel', $this->_string_shift($payload, 4))); - extract(unpack('Nwindow_size', $this->_string_shift($payload, 4))); - $this->window_size_client_to_server[$channel]+= $window_size; + Strings::shift($payload, 1); + list($channel, $window_size) = Strings::unpackSSH2('NN', $payload); + + $this->window_size_client_to_server[$channel] += $window_size; - $payload = ($this->bitmap & self::MASK_WINDOW_ADJUST) ? true : $this->_get_binary_packet($skip_channel_filter); + $payload = ($this->bitmap & self::MASK_WINDOW_ADJUST) ? true : $this->get_binary_packet($skip_channel_filter); } } @@ -3632,9 +3701,8 @@ function _filter($payload, $skip_channel_filter) * * Suppress stderr from output * - * @access public */ - function enableQuietMode() + public function enableQuietMode() { $this->quiet_mode = true; } @@ -3644,9 +3712,8 @@ function enableQuietMode() * * Show stderr in output * - * @access public */ - function disableQuietMode() + public function disableQuietMode() { $this->quiet_mode = false; } @@ -3656,10 +3723,9 @@ function disableQuietMode() * * @see self::enableQuietMode() * @see self::disableQuietMode() - * @access public * @return bool */ - function isQuietModeEnabled() + public function isQuietModeEnabled() { return $this->quiet_mode; } @@ -3667,9 +3733,8 @@ function isQuietModeEnabled() /** * Enable request-pty when using exec() * - * @access public */ - function enablePTY() + public function enablePTY() { $this->request_pty = true; } @@ -3677,12 +3742,11 @@ function enablePTY() /** * Disable request-pty when using exec() * - * @access public */ - function disablePTY() + public function disablePTY() { if ($this->in_request_pty_exec) { - $this->_close_channel(self::CHANNEL_EXEC); + $this->close_channel(self::CHANNEL_EXEC); $this->in_request_pty_exec = false; } $this->request_pty = false; @@ -3693,10 +3757,9 @@ function disablePTY() * * @see self::enablePTY() * @see self::disablePTY() - * @access public * @return bool */ - function isPTYEnabled() + public function isPTYEnabled() { return $this->request_pty; } @@ -3704,17 +3767,39 @@ function isPTYEnabled() /** * Gets channel data * - * Returns the data as a string if it's available and false if not. + * Returns the data as a string. bool(true) is returned if: + * + * - the server closes the channel + * - if the connection times out + * - if the channel status is CHANNEL_OPEN and the response was CHANNEL_OPEN_CONFIRMATION + * - if the channel status is CHANNEL_REQUEST and the response was CHANNEL_SUCCESS + * + * bool(false) is returned if: + * + * - if the channel status is CHANNEL_REQUEST and the response was CHANNEL_FAILURE * * @param int $client_channel * @param bool $skip_extended - * @return mixed|bool - * @access private + * @return mixed + * @throws \RuntimeException on connection error */ - function _get_channel_packet($client_channel, $skip_extended = false) + protected function get_channel_packet($client_channel, $skip_extended = false) { if (!empty($this->channel_buffers[$client_channel])) { - return array_shift($this->channel_buffers[$client_channel]); + switch ($this->channel_status[$client_channel]) { + case NET_SSH2_MSG_CHANNEL_REQUEST: + foreach ($this->channel_buffers[$client_channel] as $i => $packet) { + switch (ord($packet[0])) { + case NET_SSH2_MSG_CHANNEL_SUCCESS: + case NET_SSH2_MSG_CHANNEL_FAILURE: + unset($this->channel_buffers[$client_channel][$i]); + return substr($packet, 1); + } + } + break; + default: + return substr(array_shift($this->channel_buffers[$client_channel]), 1); + } } while (true) { @@ -3722,116 +3807,83 @@ function _get_channel_packet($client_channel, $skip_extended = false) $response = $this->binary_packet_buffer; $this->binary_packet_buffer = false; } else { - $response = $this->_get_binary_packet(true); + $response = $this->get_binary_packet(true); if ($response === true && $this->is_timeout) { if ($client_channel == self::CHANNEL_EXEC && !$this->request_pty) { - $this->_close_channel($client_channel); + $this->close_channel($client_channel); } return true; } if ($response === false) { - $this->bitmap = 0; - user_error('Connection closed by server'); - return false; + $this->disconnect_helper(NET_SSH2_DISCONNECT_CONNECTION_LOST); + throw new ConnectionClosedException('Connection closed by server'); } } if ($client_channel == -1 && $response === true) { return true; } - if (!strlen($response)) { - return false; - } - extract(unpack('Ctype', $this->_string_shift($response, 1))); - - if (strlen($response) < 4) { - return false; - } - if ($type == NET_SSH2_MSG_CHANNEL_OPEN) { - extract(unpack('Nlength', $this->_string_shift($response, 4))); - } else { - extract(unpack('Nchannel', $this->_string_shift($response, 4))); - } + list($type, $channel) = Strings::unpackSSH2('CN', $response); // will not be setup yet on incoming channel open request if (isset($channel) && isset($this->channel_status[$channel]) && isset($this->window_size_server_to_client[$channel])) { - $this->window_size_server_to_client[$channel]-= strlen($response); + $this->window_size_server_to_client[$channel] -= strlen($response); // resize the window, if appropriate if ($this->window_size_server_to_client[$channel] < 0) { // PuTTY does something more analogous to the following: //if ($this->window_size_server_to_client[$channel] < 0x3FFFFFFF) { $packet = pack('CNN', NET_SSH2_MSG_CHANNEL_WINDOW_ADJUST, $this->server_channels[$channel], $this->window_resize); - if (!$this->_send_binary_packet($packet)) { - return false; - } - $this->window_size_server_to_client[$channel]+= $this->window_resize; + $this->send_binary_packet($packet); + $this->window_size_server_to_client[$channel] += $this->window_resize; } switch ($type) { case NET_SSH2_MSG_CHANNEL_EXTENDED_DATA: /* if ($client_channel == self::CHANNEL_EXEC) { - $this->_send_channel_packet($client_channel, chr(0)); + $this->send_channel_packet($client_channel, chr(0)); } */ // currently, there's only one possible value for $data_type_code: NET_SSH2_EXTENDED_DATA_STDERR - if (strlen($response) < 8) { - return false; - } - extract(unpack('Ndata_type_code/Nlength', $this->_string_shift($response, 8))); - $data = $this->_string_shift($response, $length); - $this->stdErrorLog.= $data; + list($data_type_code, $data) = Strings::unpackSSH2('Ns', $response); + $this->stdErrorLog .= $data; if ($skip_extended || $this->quiet_mode) { continue 2; } if ($client_channel == $channel && $this->channel_status[$channel] == NET_SSH2_MSG_CHANNEL_DATA) { return $data; } - if (!isset($this->channel_buffers[$channel])) { - $this->channel_buffers[$channel] = array(); - } - $this->channel_buffers[$channel][] = $data; + $this->channel_buffers[$channel][] = chr($type) . $data; continue 2; case NET_SSH2_MSG_CHANNEL_REQUEST: if ($this->channel_status[$channel] == NET_SSH2_MSG_CHANNEL_CLOSE) { continue 2; } - if (strlen($response) < 4) { - return false; - } - extract(unpack('Nlength', $this->_string_shift($response, 4))); - $value = $this->_string_shift($response, $length); + list($value) = Strings::unpackSSH2('s', $response); switch ($value) { case 'exit-signal': - $this->_string_shift($response, 1); - if (strlen($response) < 4) { - return false; - } - extract(unpack('Nlength', $this->_string_shift($response, 4))); - $this->errors[] = 'SSH_MSG_CHANNEL_REQUEST (exit-signal): ' . $this->_string_shift($response, $length); - $this->_string_shift($response, 1); - if (strlen($response) < 4) { - return false; - } - extract(unpack('Nlength', $this->_string_shift($response, 4))); - if ($length) { - $this->errors[count($this->errors)].= "\r\n" . $this->_string_shift($response, $length); + list( + , // FALSE + $signal_name, + , // core dumped + $error_message + ) = Strings::unpackSSH2('bsbs', $response); + + $this->errors[] = "SSH_MSG_CHANNEL_REQUEST (exit-signal): $signal_name"; + if (strlen($error_message)) { + $this->errors[count($this->errors) - 1] .= "\r\n$error_message"; } - $this->_send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_EOF, $this->server_channels[$client_channel])); - $this->_send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_CLOSE, $this->server_channels[$channel])); + $this->send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_EOF, $this->server_channels[$client_channel])); + $this->send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_CLOSE, $this->server_channels[$channel])); $this->channel_status[$channel] = NET_SSH2_MSG_CHANNEL_EOF; continue 3; case 'exit-status': - if (strlen($response) < 5) { - return false; - } - extract(unpack('Cfalse/Nexit_status', $this->_string_shift($response, 5))); - $this->exit_status = $exit_status; + list(, $this->exit_status) = Strings::unpackSSH2('CN', $response); // "The client MAY ignore these messages." // -- http://tools.ietf.org/html/rfc4254#section-6.10 @@ -3848,42 +3900,29 @@ function _get_channel_packet($client_channel, $skip_extended = false) case NET_SSH2_MSG_CHANNEL_OPEN: switch ($type) { case NET_SSH2_MSG_CHANNEL_OPEN_CONFIRMATION: - if (strlen($response) < 4) { - return false; - } - extract(unpack('Nserver_channel', $this->_string_shift($response, 4))); - $this->server_channels[$channel] = $server_channel; - if (strlen($response) < 4) { - return false; - } - extract(unpack('Nwindow_size', $this->_string_shift($response, 4))); + list( + $this->server_channels[$channel], + $window_size, + $this->packet_size_client_to_server[$channel] + ) = Strings::unpackSSH2('NNN', $response); + if ($window_size < 0) { - $window_size&= 0x7FFFFFFF; - $window_size+= 0x80000000; + $window_size &= 0x7FFFFFFF; + $window_size += 0x80000000; } $this->window_size_client_to_server[$channel] = $window_size; - if (strlen($response) < 4) { - return false; - } - $temp = unpack('Npacket_size_client_to_server', $this->_string_shift($response, 4)); - $this->packet_size_client_to_server[$channel] = $temp['packet_size_client_to_server']; - $result = $client_channel == $channel ? true : $this->_get_channel_packet($client_channel, $skip_extended); - $this->_on_channel_open(); + $result = $client_channel == $channel ? true : $this->get_channel_packet($client_channel, $skip_extended); + $this->on_channel_open(); return $result; - //case NET_SSH2_MSG_CHANNEL_OPEN_FAILURE: + case NET_SSH2_MSG_CHANNEL_OPEN_FAILURE: + $this->disconnect_helper(NET_SSH2_DISCONNECT_BY_APPLICATION); + throw new \RuntimeException('Unable to open channel'); default: - user_error('Unable to open channel'); - return $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION); - } - break; - case NET_SSH2_MSG_IGNORE: - switch ($type) { - case NET_SSH2_MSG_CHANNEL_SUCCESS: - //$this->channel_status[$channel] = NET_SSH2_MSG_CHANNEL_DATA; - continue 3; - case NET_SSH2_MSG_CHANNEL_FAILURE: - user_error('Error opening channel'); - return $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION); + if ($client_channel == $channel) { + $this->disconnect_helper(NET_SSH2_DISCONNECT_BY_APPLICATION); + throw new \RuntimeException('Unexpected response to open request'); + } + return $this->get_channel_packet($client_channel, $skip_extended); } break; case NET_SSH2_MSG_CHANNEL_REQUEST: @@ -3892,12 +3931,16 @@ function _get_channel_packet($client_channel, $skip_extended = false) return true; case NET_SSH2_MSG_CHANNEL_FAILURE: return false; + case NET_SSH2_MSG_CHANNEL_DATA: + list($data) = Strings::unpackSSH2('s', $response); + $this->channel_buffers[$channel][] = chr($type) . $data; + return $this->get_channel_packet($client_channel, $skip_extended); default: - user_error('Unable to fulfill channel request'); - return $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION); + $this->disconnect_helper(NET_SSH2_DISCONNECT_BY_APPLICATION); + throw new \RuntimeException('Unable to fulfill channel request'); } case NET_SSH2_MSG_CHANNEL_CLOSE: - return $type == NET_SSH2_MSG_CHANNEL_CLOSE ? true : $this->_get_channel_packet($client_channel, $skip_extended); + return $type == NET_SSH2_MSG_CHANNEL_CLOSE ? true : $this->get_channel_packet($client_channel, $skip_extended); } } @@ -3905,29 +3948,21 @@ function _get_channel_packet($client_channel, $skip_extended = false) switch ($type) { case NET_SSH2_MSG_CHANNEL_DATA: - //if ($this->channel_status[$channel] == NET_SSH2_MSG_IGNORE) { - // $this->channel_status[$channel] = NET_SSH2_MSG_CHANNEL_DATA; - //} - /* if ($channel == self::CHANNEL_EXEC) { // SCP requires null packets, such as this, be sent. further, in the case of the ssh.com SSH server // this actually seems to make things twice as fast. more to the point, the message right after // SSH_MSG_CHANNEL_DATA (usually SSH_MSG_IGNORE) won't block for as long as it would have otherwise. // in OpenSSH it slows things down but only by a couple thousandths of a second. - $this->_send_channel_packet($channel, chr(0)); + $this->send_channel_packet($channel, chr(0)); } */ - if (strlen($response) < 4) { - return false; - } - extract(unpack('Nlength', $this->_string_shift($response, 4))); - $data = $this->_string_shift($response, $length); + list($data) = Strings::unpackSSH2('s', $response); if ($channel == self::CHANNEL_AGENT_FORWARD) { - $agent_response = $this->agent->_forward_data($data); + $agent_response = $this->agent->forwardData($data); if (!is_bool($agent_response)) { - $this->_send_channel_packet($channel, $agent_response); + $this->send_channel_packet($channel, $agent_response); } break; } @@ -3935,30 +3970,28 @@ function _get_channel_packet($client_channel, $skip_extended = false) if ($client_channel == $channel) { return $data; } - if (!isset($this->channel_buffers[$channel])) { - $this->channel_buffers[$channel] = array(); - } - $this->channel_buffers[$channel][] = $data; + $this->channel_buffers[$channel][] = chr($type) . $data; break; case NET_SSH2_MSG_CHANNEL_CLOSE: $this->curTimeout = 5; if ($this->bitmap & self::MASK_SHELL) { - $this->bitmap&= ~self::MASK_SHELL; + $this->bitmap &= ~self::MASK_SHELL; } if ($this->channel_status[$channel] != NET_SSH2_MSG_CHANNEL_EOF) { - $this->_send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_CLOSE, $this->server_channels[$channel])); + $this->send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_CLOSE, $this->server_channels[$channel])); } $this->channel_status[$channel] = NET_SSH2_MSG_CHANNEL_CLOSE; if ($client_channel == $channel) { return true; } + // fall-through case NET_SSH2_MSG_CHANNEL_EOF: break; default: - user_error('Error reading channel data'); - return $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION); + $this->disconnect_helper(NET_SSH2_DISCONNECT_BY_APPLICATION); + throw new \RuntimeException("Error reading channel data ($type)"); } } } @@ -3971,57 +4004,142 @@ function _get_channel_packet($client_channel, $skip_extended = false) * @param string $data * @param string $logged * @see self::_get_binary_packet() - * @return bool - * @access private + * @return void */ - function _send_binary_packet($data, $logged = null) + protected function send_binary_packet($data, $logged = null) { if (!is_resource($this->fsock) || feof($this->fsock)) { $this->bitmap = 0; - user_error('Connection closed prematurely'); - return false; + throw new ConnectionClosedException('Connection closed prematurely'); + } + + if (!isset($logged)) { + $logged = $data; } - //if ($this->compress) { - // // the -4 removes the checksum: - // // http://php.net/function.gzcompress#57710 - // $data = substr(gzcompress($data), 0, -4); - //} + switch ($this->compress) { + case self::NET_SSH2_COMPRESSION_ZLIB_AT_OPENSSH: + if (!$this->isAuthenticated()) { + break; + } + // fall-through + case self::NET_SSH2_COMPRESSION_ZLIB: + if (!$this->regenerate_compression_context) { + $header = ''; + } else { + $this->regenerate_compression_context = false; + $this->compress_context = deflate_init(ZLIB_ENCODING_RAW, ['window' => 15]); + $header = "\x78\x9C"; + } + if ($this->compress_context) { + $data = $header . deflate_add($this->compress_context, $data, ZLIB_PARTIAL_FLUSH); + } + } // 4 (packet length) + 1 (padding length) + 4 (minimal padding amount) == 9 $packet_length = strlen($data) + 9; + if ($this->encrypt && $this->encrypt->usesNonce()) { + $packet_length -= 4; + } // round up to the nearest $this->encrypt_block_size - $packet_length+= (($this->encrypt_block_size - 1) * $packet_length) % $this->encrypt_block_size; + $packet_length += (($this->encrypt_block_size - 1) * $packet_length) % $this->encrypt_block_size; // subtracting strlen($data) is obvious - subtracting 5 is necessary because of packet_length and padding_length $padding_length = $packet_length - strlen($data) - 5; + switch (true) { + case $this->encrypt && $this->encrypt->usesNonce(): + case $this->hmac_create instanceof Hash && $this->hmac_create_etm: + $padding_length += 4; + $packet_length += 4; + } + $padding = Random::string($padding_length); // we subtract 4 from packet_length because the packet_length field isn't supposed to include itself $packet = pack('NCa*', $packet_length - 4, $padding_length, $data . $padding); - $hmac = $this->hmac_create !== false ? $this->hmac_create->hash(pack('Na*', $this->send_seq_no, $packet)) : ''; - $this->send_seq_no++; + $hmac = ''; + if ($this->hmac_create instanceof Hash && !$this->hmac_create_etm) { + if (($this->hmac_create->getHash() & "\xFF\xFF\xFF\xFF") == 'umac') { + $this->hmac_create->setNonce("\0\0\0\0" . pack('N', $this->send_seq_no)); + $hmac = $this->hmac_create->hash($packet); + } else { + $hmac = $this->hmac_create->hash(pack('Na*', $this->send_seq_no, $packet)); + } + } + + if ($this->encrypt) { + switch ($this->encryptName) { + case 'aes128-gcm@openssh.com': + case 'aes256-gcm@openssh.com': + $this->encrypt->setNonce( + $this->encryptFixedPart . + $this->encryptInvocationCounter + ); + Strings::increment_str($this->encryptInvocationCounter); + $this->encrypt->setAAD($temp = ($packet & "\xFF\xFF\xFF\xFF")); + $packet = $temp . $this->encrypt->encrypt(substr($packet, 4)); + break; + case 'chacha20-poly1305@openssh.com': + // This should be impossible, but we are checking anyway to narrow the type for Psalm. + if (!($this->encrypt instanceof ChaCha20)) { + throw new \LogicException('$this->encrypt is not a ' . ChaCha20::class); + } + + $nonce = pack('N2', 0, $this->send_seq_no); + + $this->encrypt->setNonce($nonce); + $this->lengthEncrypt->setNonce($nonce); - if ($this->encrypt !== false) { - $packet = $this->encrypt->encrypt($packet); + $length = $this->lengthEncrypt->encrypt($packet & "\xFF\xFF\xFF\xFF"); + + $this->encrypt->setCounter(0); + // this is the same approach that's implemented in Salsa20::createPoly1305Key() + // but we don't want to use the same AEAD construction that RFC8439 describes + // for ChaCha20-Poly1305 so we won't rely on it (see Salsa20::poly1305()) + $this->encrypt->setPoly1305Key( + $this->encrypt->encrypt(str_repeat("\0", 32)) + ); + $this->encrypt->setAAD($length); + $this->encrypt->setCounter(1); + $packet = $length . $this->encrypt->encrypt(substr($packet, 4)); + break; + default: + $packet = $this->hmac_create instanceof Hash && $this->hmac_create_etm ? + ($packet & "\xFF\xFF\xFF\xFF") . $this->encrypt->encrypt(substr($packet, 4)) : + $this->encrypt->encrypt($packet); + } + } + + if ($this->hmac_create instanceof Hash && $this->hmac_create_etm) { + if (($this->hmac_create->getHash() & "\xFF\xFF\xFF\xFF") == 'umac') { + $this->hmac_create->setNonce("\0\0\0\0" . pack('N', $this->send_seq_no)); + $hmac = $this->hmac_create->hash($packet); + } else { + $hmac = $this->hmac_create->hash(pack('Na*', $this->send_seq_no, $packet)); + } } - $packet.= $hmac; + $this->send_seq_no++; + + $packet .= $this->encrypt && $this->encrypt->usesNonce() ? $this->encrypt->getTag() : $hmac; $start = microtime(true); - $result = strlen($packet) == @fputs($this->fsock, $packet); + $sent = @fputs($this->fsock, $packet); $stop = microtime(true); if (defined('NET_SSH2_LOGGING')) { $current = microtime(true); - $message_number = isset($this->message_numbers[ord($data[0])]) ? $this->message_numbers[ord($data[0])] : 'UNKNOWN (' . ord($data[0]) . ')'; + $message_number = isset($this->message_numbers[ord($logged[0])]) ? $this->message_numbers[ord($logged[0])] : 'UNKNOWN (' . ord($logged[0]) . ')'; $message_number = '-> ' . $message_number . ' (since last: ' . round($current - $this->last_packet, 4) . ', network: ' . round($stop - $start, 4) . 's)'; - $this->_append_log($message_number, isset($logged) ? $logged : $data); + $this->append_log($message_number, $logged); $this->last_packet = $current; } - return $result; + if (strlen($packet) != $sent) { + $this->bitmap = 0; + throw new \RuntimeException("Only $sent of " . strlen($packet) . " bytes were sent"); + } } /** @@ -4031,28 +4149,61 @@ function _send_binary_packet($data, $logged = null) * * @param string $message_number * @param string $message - * @access private */ - function _append_log($message_number, $message) + private function append_log($message_number, $message) + { + $this->append_log_helper( + NET_SSH2_LOGGING, + $message_number, + $message, + $this->message_number_log, + $this->message_log, + $this->log_size, + $this->realtime_log_file, + $this->realtime_log_wrap, + $this->realtime_log_size + ); + } + + /** + * Logs data packet helper + * + * @param int $constant + * @param string $message_number + * @param string $message + * @param array &$message_number_log + * @param array &$message_log + * @param int &$log_size + * @param resource &$realtime_log_file + * @param bool &$realtime_log_wrap + * @param int &$realtime_log_size + */ + protected function append_log_helper($constant, $message_number, $message, array &$message_number_log, array &$message_log, &$log_size, &$realtime_log_file, &$realtime_log_wrap, &$realtime_log_size) { // remove the byte identifying the message type from all but the first two messages (ie. the identification strings) if (strlen($message_number) > 2) { - $this->_string_shift($message); + Strings::shift($message); } - switch (NET_SSH2_LOGGING) { + switch ($constant) { // useful for benchmarks case self::LOG_SIMPLE: - $this->message_number_log[] = $message_number; + $message_number_log[] = $message_number; + break; + case self::LOG_SIMPLE_REALTIME: + echo $message_number; + echo PHP_SAPI == 'cli' ? "\r\n" : '
'; + @flush(); + @ob_flush(); break; // the most useful log for SSH2 case self::LOG_COMPLEX: - $this->message_number_log[] = $message_number; - $this->log_size+= strlen($message); - $this->message_log[] = $message; - while ($this->log_size > self::LOG_MAX_SIZE) { - $this->log_size-= strlen(array_shift($this->message_log)); - array_shift($this->message_number_log); + $message_number_log[] = $message_number; + $log_size += strlen($message); + $message_log[] = $message; + while ($log_size > self::LOG_MAX_SIZE) { + $log_size -= strlen(array_shift($message_log)); + array_shift($message_number_log); } break; // dump the output out realtime; packets may be interspersed with non packets, @@ -4067,37 +4218,37 @@ function _append_log($message_number, $message) $start = '
';
                         $stop = '
'; } - echo $start . $this->_format_log(array($message), array($message_number)) . $stop; + echo $start . $this->format_log([$message], [$message_number]) . $stop; @flush(); @ob_flush(); break; - // basically the same thing as self::LOG_REALTIME with the caveat that self::LOG_REALTIME_FILE + // basically the same thing as self::LOG_REALTIME with the caveat that NET_SSH2_LOG_REALTIME_FILENAME // needs to be defined and that the resultant log file will be capped out at self::LOG_MAX_SIZE. // the earliest part of the log file is denoted by the first <<< START >>> and is not going to necessarily // at the beginning of the file case self::LOG_REALTIME_FILE: - if (!isset($this->realtime_log_file)) { + if (!isset($realtime_log_file)) { // PHP doesn't seem to like using constants in fopen() - $filename = self::LOG_REALTIME_FILENAME; + $filename = NET_SSH2_LOG_REALTIME_FILENAME; $fp = fopen($filename, 'w'); - $this->realtime_log_file = $fp; + $realtime_log_file = $fp; } - if (!is_resource($this->realtime_log_file)) { + if (!is_resource($realtime_log_file)) { break; } - $entry = $this->_format_log(array($message), array($message_number)); - if ($this->realtime_log_wrap) { + $entry = $this->format_log([$message], [$message_number]); + if ($realtime_log_wrap) { $temp = "<<< START >>>\r\n"; - $entry.= $temp; - fseek($this->realtime_log_file, ftell($this->realtime_log_file) - strlen($temp)); + $entry .= $temp; + fseek($realtime_log_file, ftell($realtime_log_file) - strlen($temp)); } - $this->realtime_log_size+= strlen($entry); - if ($this->realtime_log_size > self::LOG_MAX_SIZE) { - fseek($this->realtime_log_file, 0); - $this->realtime_log_size = strlen($entry); - $this->realtime_log_wrap = true; + $realtime_log_size += strlen($entry); + if ($realtime_log_size > self::LOG_MAX_SIZE) { + fseek($realtime_log_file, 0); + $realtime_log_size = strlen($entry); + $realtime_log_wrap = true; } - fputs($this->realtime_log_file, $entry); + fputs($realtime_log_file, $entry); } } @@ -4108,17 +4259,16 @@ function _append_log($message_number, $message) * * @param int $client_channel * @param string $data - * @return bool - * @access private + * @return void */ - function _send_channel_packet($client_channel, $data) + protected function send_channel_packet($client_channel, $data) { while (strlen($data)) { if (!$this->window_size_client_to_server[$client_channel]) { - $this->bitmap^= self::MASK_WINDOW_ADJUST; + $this->bitmap ^= self::MASK_WINDOW_ADJUST; // using an invalid channel will let the buffers be built up for the valid channels - $this->_get_channel_packet(-1); - $this->bitmap^= self::MASK_WINDOW_ADJUST; + $this->get_channel_packet(-1); + $this->bitmap ^= self::MASK_WINDOW_ADJUST; } /* The maximum amount of data allowed is determined by the maximum @@ -4130,50 +4280,44 @@ function _send_channel_packet($client_channel, $data) $this->window_size_client_to_server[$client_channel] ); - $temp = $this->_string_shift($data, $max_size); - $packet = pack( - 'CN2a*', + $temp = Strings::shift($data, $max_size); + $packet = Strings::packSSH2( + 'CNs', NET_SSH2_MSG_CHANNEL_DATA, $this->server_channels[$client_channel], - strlen($temp), $temp ); - $this->window_size_client_to_server[$client_channel]-= strlen($temp); - if (!$this->_send_binary_packet($packet)) { - return false; - } + $this->window_size_client_to_server[$client_channel] -= strlen($temp); + $this->send_binary_packet($packet); } - - return true; } /** * Closes and flushes a channel * - * \phpseclib\Net\SSH2 doesn't properly close most channels. For exec() channels are normally closed by the server + * \phpseclib3\Net\SSH2 doesn't properly close most channels. For exec() channels are normally closed by the server * and for SFTP channels are presumably closed when the client disconnects. This functions is intended * for SCP more than anything. * * @param int $client_channel * @param bool $want_reply - * @return bool - * @access private + * @return void */ - function _close_channel($client_channel, $want_reply = false) + private function close_channel($client_channel, $want_reply = false) { // see http://tools.ietf.org/html/rfc4254#section-5.3 - $this->_send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_EOF, $this->server_channels[$client_channel])); + $this->send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_EOF, $this->server_channels[$client_channel])); if (!$want_reply) { - $this->_send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_CLOSE, $this->server_channels[$client_channel])); + $this->send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_CLOSE, $this->server_channels[$client_channel])); } $this->channel_status[$client_channel] = NET_SSH2_MSG_CHANNEL_CLOSE; $this->curTimeout = 5; - while (!is_bool($this->_get_channel_packet($client_channel))) { + while (!is_bool($this->get_channel_packet($client_channel))) { } if ($this->is_timeout) { @@ -4181,11 +4325,11 @@ function _close_channel($client_channel, $want_reply = false) } if ($want_reply) { - $this->_send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_CLOSE, $this->server_channels[$client_channel])); + $this->send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_CLOSE, $this->server_channels[$client_channel])); } if ($this->bitmap & self::MASK_SHELL) { - $this->bitmap&= ~self::MASK_SHELL; + $this->bitmap &= ~self::MASK_SHELL; } } @@ -4193,39 +4337,24 @@ function _close_channel($client_channel, $want_reply = false) * Disconnect * * @param int $reason - * @return bool - * @access private + * @return false */ - function _disconnect($reason) + protected function disconnect_helper($reason) { if ($this->bitmap & self::MASK_CONNECTED) { - $data = pack('CNNa*Na*', NET_SSH2_MSG_DISCONNECT, $reason, 0, '', 0, ''); - $this->_send_binary_packet($data); + $data = Strings::packSSH2('CNss', NET_SSH2_MSG_DISCONNECT, $reason, '', ''); + try { + $this->send_binary_packet($data); + } catch (\Exception $e) { + } } $this->bitmap = 0; - if (is_resource($this->fsock) && get_resource_type($this->fsock) == 'stream') { + if (is_resource($this->fsock) && get_resource_type($this->fsock) === 'stream') { fclose($this->fsock); } - return false; - } - - /** - * String Shift - * - * Inspired by array_shift - * - * @param string $string - * @param int $index - * @return string - * @access private - */ - function _string_shift(&$string, $index = 1) - { - $substr = substr($string, 0, $index); - $string = substr($string, $index); - return $substr; + return false; } /** @@ -4235,11 +4364,11 @@ function _string_shift(&$string, $index = 1) * named constants from it, using the value as the name of the constant and the index as the value of the constant. * If any of the constants that would be defined already exists, none of the constants will be defined. * - * @access private + * @param mixed[] ...$args + * @access protected */ - function _define_array() + protected function define_array(...$args) { - $args = func_get_args(); foreach ($args as $arg) { foreach ($arg as $key => $value) { if (!defined($value)) { @@ -4256,10 +4385,9 @@ function _define_array() * * Returns a string if NET_SSH2_LOGGING == self::LOG_COMPLEX, an array if NET_SSH2_LOGGING == self::LOG_SIMPLE and false if !defined('NET_SSH2_LOGGING') * - * @access public * @return array|false|string */ - function getLog() + public function getLog() { if (!defined('NET_SSH2_LOGGING')) { return false; @@ -4269,7 +4397,7 @@ function getLog() case self::LOG_SIMPLE: return $this->message_number_log; case self::LOG_COMPLEX: - $log = $this->_format_log($this->message_log, $this->message_number_log); + $log = $this->format_log($this->message_log, $this->message_number_log); return PHP_SAPI == 'cli' ? $log : '
' . $log . '
'; default: return false; @@ -4281,62 +4409,48 @@ function getLog() * * @param array $message_log * @param array $message_number_log - * @access private * @return string */ - function _format_log($message_log, $message_number_log) + protected function format_log(array $message_log, array $message_number_log) { $output = ''; for ($i = 0; $i < count($message_log); $i++) { - $output.= $message_number_log[$i] . "\r\n"; + $output .= $message_number_log[$i] . "\r\n"; $current_log = $message_log[$i]; $j = 0; do { if (strlen($current_log)) { - $output.= str_pad(dechex($j), 7, '0', STR_PAD_LEFT) . '0 '; + $output .= str_pad(dechex($j), 7, '0', STR_PAD_LEFT) . '0 '; } - $fragment = $this->_string_shift($current_log, $this->log_short_width); - $hex = substr(preg_replace_callback('#.#s', array($this, '_format_log_helper'), $fragment), strlen($this->log_boundary)); + $fragment = Strings::shift($current_log, $this->log_short_width); + $hex = substr(preg_replace_callback('#.#s', function ($matches) { + return $this->log_boundary . str_pad(dechex(ord($matches[0])), 2, '0', STR_PAD_LEFT); + }, $fragment), strlen($this->log_boundary)); // replace non ASCII printable characters with dots // http://en.wikipedia.org/wiki/ASCII#ASCII_printable_characters // also replace < with a . since < messes up the output on web browsers $raw = preg_replace('#[^\x20-\x7E]|<#', '.', $fragment); - $output.= str_pad($hex, $this->log_long_width - $this->log_short_width, ' ') . $raw . "\r\n"; + $output .= str_pad($hex, $this->log_long_width - $this->log_short_width, ' ') . $raw . "\r\n"; $j++; } while (strlen($current_log)); - $output.= "\r\n"; + $output .= "\r\n"; } return $output; } /** - * Helper function for _format_log - * - * For use with preg_replace_callback() - * - * @param array $matches - * @access private - * @return string - */ - function _format_log_helper($matches) - { - return $this->log_boundary . str_pad(dechex(ord($matches[0])), 2, '0', STR_PAD_LEFT); - } - - /** - * Helper function for agent->_on_channel_open() + * Helper function for agent->on_channel_open() * * Used when channels are created to inform agent * of said channel opening. Must be called after * channel open confirmation received * - * @access private */ - function _on_channel_open() + private function on_channel_open() { if (isset($this->agent)) { - $this->agent->_on_channel_open($this); + $this->agent->registerChannelOpen($this); } } @@ -4347,9 +4461,8 @@ function _on_channel_open() * @param array $array1 * @param array $array2 * @return mixed False if intersection is empty, else intersected value. - * @access private */ - function _array_intersect_first($array1, $array2) + private static function array_intersect_first(array $array1, array $array2) { foreach ($array1 as $value) { if (in_array($value, $array2)) { @@ -4363,9 +4476,8 @@ function _array_intersect_first($array1, $array2) * Returns all errors * * @return string[] - * @access public */ - function getErrors() + public function getErrors() { return $this->errors; } @@ -4374,9 +4486,8 @@ function getErrors() * Returns the last error * * @return string - * @access public */ - function getLastError() + public function getLastError() { $count = count($this->errors); @@ -4388,203 +4499,74 @@ function getLastError() /** * Return the server identification. * - * @return string - * @access public + * @return string|false */ - function getServerIdentification() + public function getServerIdentification() { - $this->_connect(); + $this->connect(); return $this->server_identifier; } - /** - * Return a list of the key exchange algorithms the server supports. - * - * @return array - * @access public - */ - function getKexAlgorithms() - { - $this->_connect(); - - return $this->kex_algorithms; - } - - /** - * Return a list of the host key (public key) algorithms the server supports. - * - * @return array - * @access public - */ - function getServerHostKeyAlgorithms() - { - $this->_connect(); - - return $this->server_host_key_algorithms; - } - - /** - * Return a list of the (symmetric key) encryption algorithms the server supports, when receiving stuff from the client. - * - * @return array - * @access public - */ - function getEncryptionAlgorithmsClient2Server() - { - $this->_connect(); - - return $this->encryption_algorithms_client_to_server; - } - - /** - * Return a list of the (symmetric key) encryption algorithms the server supports, when sending stuff to the client. - * - * @return array - * @access public - */ - function getEncryptionAlgorithmsServer2Client() - { - $this->_connect(); - - return $this->encryption_algorithms_server_to_client; - } - - /** - * Return a list of the MAC algorithms the server supports, when receiving stuff from the client. - * - * @return array - * @access public - */ - function getMACAlgorithmsClient2Server() - { - $this->_connect(); - - return $this->mac_algorithms_client_to_server; - } - - /** - * Return a list of the MAC algorithms the server supports, when sending stuff to the client. - * - * @return array - * @access public - */ - function getMACAlgorithmsServer2Client() - { - $this->_connect(); - - return $this->mac_algorithms_server_to_client; - } - - /** - * Return a list of the compression algorithms the server supports, when receiving stuff from the client. - * - * @return array - * @access public - */ - function getCompressionAlgorithmsClient2Server() - { - $this->_connect(); - - return $this->compression_algorithms_client_to_server; - } - - /** - * Return a list of the compression algorithms the server supports, when sending stuff to the client. - * - * @return array - * @access public - */ - function getCompressionAlgorithmsServer2Client() - { - $this->_connect(); - - return $this->compression_algorithms_server_to_client; - } - - /** - * Return a list of the languages the server supports, when sending stuff to the client. - * - * @return array - * @access public - */ - function getLanguagesServer2Client() - { - $this->_connect(); - - return $this->languages_server_to_client; - } - - /** - * Return a list of the languages the server supports, when receiving stuff from the client. - * - * @return array - * @access public - */ - function getLanguagesClient2Server() - { - $this->_connect(); - - return $this->languages_client_to_server; - } - /** * Returns a list of algorithms the server supports * * @return array - * @access public */ - function getServerAlgorithms() + public function getServerAlgorithms() { - $this->_connect(); + $this->connect(); - return array( + return [ 'kex' => $this->kex_algorithms, 'hostkey' => $this->server_host_key_algorithms, - 'client_to_server' => array( + 'client_to_server' => [ 'crypt' => $this->encryption_algorithms_client_to_server, 'mac' => $this->mac_algorithms_client_to_server, 'comp' => $this->compression_algorithms_client_to_server, 'lang' => $this->languages_client_to_server - ), - 'server_to_client' => array( + ], + 'server_to_client' => [ 'crypt' => $this->encryption_algorithms_server_to_client, 'mac' => $this->mac_algorithms_server_to_client, 'comp' => $this->compression_algorithms_server_to_client, 'lang' => $this->languages_server_to_client - ) - ); + ] + ]; } /** * Returns a list of KEX algorithms that phpseclib supports * * @return array - * @access public */ - function getSupportedKEXAlgorithms() + public static function getSupportedKEXAlgorithms() { - $kex_algorithms = array( + $kex_algorithms = [ // Elliptic Curve Diffie-Hellman Key Agreement (ECDH) using // Curve25519. See doc/curve25519-sha256@libssh.org.txt in the // libssh repository for more information. + 'curve25519-sha256', 'curve25519-sha256@libssh.org', + 'ecdh-sha2-nistp256', // RFC 5656 + 'ecdh-sha2-nistp384', // RFC 5656 + 'ecdh-sha2-nistp521', // RFC 5656 + 'diffie-hellman-group-exchange-sha256',// RFC 4419 'diffie-hellman-group-exchange-sha1', // RFC 4419 // Diffie-Hellman Key Agreement (DH) using integer modulo prime // groups. + 'diffie-hellman-group14-sha256', 'diffie-hellman-group14-sha1', // REQUIRED - 'diffie-hellman-group1-sha1', // REQUIRED - ); + 'diffie-hellman-group15-sha512', + 'diffie-hellman-group16-sha512', + 'diffie-hellman-group17-sha512', + 'diffie-hellman-group18-sha512', - if (!function_exists('sodium_crypto_box_publickey_from_secretkey')) { - $kex_algorithms = array_diff( - $kex_algorithms, - array('curve25519-sha256@libssh.org') - ); - } + 'diffie-hellman-group1-sha1', // REQUIRED + ]; return $kex_algorithms; } @@ -4593,27 +4575,33 @@ function getSupportedKEXAlgorithms() * Returns a list of host key algorithms that phpseclib supports * * @return array - * @access public */ - function getSupportedHostKeyAlgorithms() + public static function getSupportedHostKeyAlgorithms() { - return array( + return [ + 'ssh-ed25519', // https://tools.ietf.org/html/draft-ietf-curdle-ssh-ed25519-02 + 'ecdsa-sha2-nistp256', // RFC 5656 + 'ecdsa-sha2-nistp384', // RFC 5656 + 'ecdsa-sha2-nistp521', // RFC 5656 'rsa-sha2-256', // RFC 8332 'rsa-sha2-512', // RFC 8332 'ssh-rsa', // RECOMMENDED sign Raw RSA Key 'ssh-dss' // REQUIRED sign Raw DSS Key - ); + ]; } /** * Returns a list of symmetric key algorithms that phpseclib supports * * @return array - * @access public */ - function getSupportedEncryptionAlgorithms() + public static function getSupportedEncryptionAlgorithms() { - $algos = array( + $algos = [ + // from : + 'aes128-gcm@openssh.com', + 'aes256-gcm@openssh.com', + // from : 'arcfour256', 'arcfour128', @@ -4625,6 +4613,16 @@ function getSupportedEncryptionAlgorithms() 'aes192-ctr', // RECOMMENDED AES with 192-bit key 'aes256-ctr', // RECOMMENDED AES with 256-bit key + // from : + // one of the big benefits of chacha20-poly1305 is speed. the problem is... + // libsodium doesn't generate the poly1305 keys in the way ssh does and openssl's PHP bindings don't even + // seem to support poly1305 currently. so even if libsodium or openssl are being used for the chacha20 + // part, pure-PHP has to be used for the poly1305 part and that's gonna cause a big slow down. + // speed-wise it winds up being faster to use AES (when openssl or mcrypt are available) and some HMAC + // (which is always gonna be super fast to compute thanks to the hash extension, which + // "is bundled and compiled into PHP by default") + 'chacha20-poly1305@openssh.com', + 'twofish128-ctr', // OPTIONAL Twofish in SDCTR mode, with 128-bit key 'twofish192-ctr', // OPTIONAL Twofish with 192-bit key 'twofish256-ctr', // OPTIONAL Twofish with 256-bit key @@ -4648,34 +4646,46 @@ function getSupportedEncryptionAlgorithms() '3des-cbc', // REQUIRED three-key 3DES in CBC mode //'none' // OPTIONAL no encryption; NOT RECOMMENDED - ); + ]; - if ($this->crypto_engine) { - $engines = array($this->crypto_engine); + if (self::$crypto_engine) { + $engines = [self::$crypto_engine]; } else { - $engines = array( - Base::ENGINE_OPENSSL, - Base::ENGINE_MCRYPT, - Base::ENGINE_INTERNAL - ); + $engines = [ + 'libsodium', + 'OpenSSL (GCM)', + 'OpenSSL', + 'mcrypt', + 'Eval', + 'PHP' + ]; } - $ciphers = array(); + $ciphers = []; + foreach ($engines as $engine) { foreach ($algos as $algo) { - $obj = $this->_encryption_algorithm_to_crypt_instance($algo); + $obj = self::encryption_algorithm_to_crypt_instance($algo); if ($obj instanceof Rijndael) { $obj->setKeyLength(preg_replace('#[^\d]#', '', $algo)); } switch ($algo) { + case 'chacha20-poly1305@openssh.com': case 'arcfour128': case 'arcfour256': - if ($engine != Base::ENGINE_INTERNAL) { + if ($engine != 'Eval') { + continue 2; + } + break; + case 'aes128-gcm@openssh.com': + case 'aes256-gcm@openssh.com': + if ($engine == 'OpenSSL') { continue 2; } + $obj->setNonce('dummydummydu'); } if ($obj->isValidEngine($engine)) { - $algos = array_diff($algos, array($algo)); + $algos = array_diff($algos, [$algo]); $ciphers[] = $algo; } } @@ -4688,34 +4698,45 @@ function getSupportedEncryptionAlgorithms() * Returns a list of MAC algorithms that phpseclib supports * * @return array - * @access public */ - function getSupportedMACAlgorithms() + public static function getSupportedMACAlgorithms() { - return array( + return [ + 'hmac-sha2-256-etm@openssh.com', + 'hmac-sha2-512-etm@openssh.com', + 'umac-64-etm@openssh.com', + 'umac-128-etm@openssh.com', + 'hmac-sha1-etm@openssh.com', + // from : 'hmac-sha2-256',// RECOMMENDED HMAC-SHA256 (digest length = key length = 32) + 'hmac-sha2-512',// OPTIONAL HMAC-SHA512 (digest length = key length = 64) + + // from : + 'umac-64@openssh.com', + 'umac-128@openssh.com', 'hmac-sha1-96', // RECOMMENDED first 96 bits of HMAC-SHA1 (digest length = 12, key length = 20) 'hmac-sha1', // REQUIRED HMAC-SHA1 (digest length = key length = 20) 'hmac-md5-96', // OPTIONAL first 96 bits of HMAC-MD5 (digest length = 12, key length = 16) 'hmac-md5', // OPTIONAL HMAC-MD5 (digest length = key length = 16) //'none' // OPTIONAL no MAC; NOT RECOMMENDED - ); + ]; } /** * Returns a list of compression algorithms that phpseclib supports * * @return array - * @access public */ - function getSupportedCompressionAlgorithms() + public static function getSupportedCompressionAlgorithms() { - return array( - 'none' // REQUIRED no compression - //'zlib' // OPTIONAL ZLIB (LZ77) compression - ); + $algos = ['none']; // REQUIRED no compression + if (function_exists('deflate_init')) { + $algos[] = 'zlib@openssh.com'; // https://datatracker.ietf.org/doc/html/draft-miller-secsh-compression-delayed + $algos[] = 'zlib'; + } + return $algos; } /** @@ -4724,26 +4745,41 @@ function getSupportedCompressionAlgorithms() * Uses the same format as https://www.php.net/ssh2-methods-negotiated * * @return array - * @access public */ - function getAlgorithmsNegotiated() + public function getAlgorithmsNegotiated() { - $this->_connect(); + $this->connect(); + + $compression_map = [ + self::NET_SSH2_COMPRESSION_NONE => 'none', + self::NET_SSH2_COMPRESSION_ZLIB => 'zlib', + self::NET_SSH2_COMPRESSION_ZLIB_AT_OPENSSH => 'zlib@openssh.com' + ]; - return array( + return [ 'kex' => $this->kex_algorithm, 'hostkey' => $this->signature_format, - 'client_to_server' => array( - 'crypt' => $this->encrypt->name, - 'mac' => $this->hmac_create->name, - 'comp' => 'none', - ), - 'server_to_client' => array( - 'crypt' => $this->decrypt->name, - 'mac' => $this->hmac_check->name, - 'comp' => 'none', - ) - ); + 'client_to_server' => [ + 'crypt' => $this->encryptName, + 'mac' => $this->hmac_create_name, + 'comp' => $compression_map[$this->compress], + ], + 'server_to_client' => [ + 'crypt' => $this->decryptName, + 'mac' => $this->hmac_check_name, + 'comp' => $compression_map[$this->decompress], + ] + ]; + } + + /** + * Allows you to set the terminal + * + * @param string $term + */ + public function setTerminal($term) + { + $this->term = $term; } /** @@ -4751,52 +4787,51 @@ function getAlgorithmsNegotiated() * * * @param array $methods - * @access public */ - function setPreferredAlgorithms($methods) + public function setPreferredAlgorithms(array $methods) { $preferred = $methods; if (isset($preferred['kex'])) { $preferred['kex'] = array_intersect( $preferred['kex'], - $this->getSupportedKEXAlgorithms() + static::getSupportedKEXAlgorithms() ); } if (isset($preferred['hostkey'])) { $preferred['hostkey'] = array_intersect( $preferred['hostkey'], - $this->getSupportedHostKeyAlgorithms() + static::getSupportedHostKeyAlgorithms() ); } - $keys = array('client_to_server', 'server_to_client'); + $keys = ['client_to_server', 'server_to_client']; foreach ($keys as $key) { if (isset($preferred[$key])) { $a = &$preferred[$key]; if (isset($a['crypt'])) { $a['crypt'] = array_intersect( $a['crypt'], - $this->getSupportedEncryptionAlgorithms() + static::getSupportedEncryptionAlgorithms() ); } if (isset($a['comp'])) { $a['comp'] = array_intersect( $a['comp'], - $this->getSupportedCompressionAlgorithms() + static::getSupportedCompressionAlgorithms() ); } if (isset($a['mac'])) { $a['mac'] = array_intersect( $a['mac'], - $this->getSupportedMACAlgorithms() + static::getSupportedMACAlgorithms() ); } } } - $keys = array( + $keys = [ 'kex', 'hostkey', 'client_to_server/crypt', @@ -4805,7 +4840,7 @@ function setPreferredAlgorithms($methods) 'server_to_client/crypt', 'server_to_client/comp', 'server_to_client/mac', - ); + ]; foreach ($keys as $key) { $p = $preferred; $m = $methods; @@ -4824,8 +4859,7 @@ function setPreferredAlgorithms($methods) $msg = count($diff) == 1 ? ' is not a supported algorithm' : ' are not supported algorithms'; - user_error(implode(', ', $diff) . $msg); - return false; + throw new UnsupportedAlgorithmException(implode(', ', $diff) . $msg); } } @@ -4839,9 +4873,8 @@ function setPreferredAlgorithms($methods) * authentication may be relevant for getting legal protection." * * @return string - * @access public */ - function getBannerMessage() + public function getBannerMessage() { return $this->banner_message; } @@ -4852,170 +4885,64 @@ function getBannerMessage() * Caching this the first time you connect to a server and checking the result on subsequent connections * is recommended. Returns false if the server signature is not signed correctly with the public host key. * - * @return mixed - * @access public + * @return string|false + * @throws \RuntimeException on badly formatted keys + * @throws \phpseclib3\Exception\NoSupportedAlgorithmsException when the key isn't in a supported format */ - function getServerPublicHostKey() + public function getServerPublicHostKey() { if (!($this->bitmap & self::MASK_CONSTRUCTOR)) { - if (!$this->_connect()) { - return false; - } + $this->connect(); } $signature = $this->signature; - $server_public_host_key = $this->server_public_host_key; - - if (strlen($server_public_host_key) < 4) { - return false; - } - extract(unpack('Nlength', $this->_string_shift($server_public_host_key, 4))); - $this->_string_shift($server_public_host_key, $length); + $server_public_host_key = base64_encode($this->server_public_host_key); if ($this->signature_validated) { return $this->bitmap ? - $this->signature_format . ' ' . base64_encode($this->server_public_host_key) : + $this->signature_format . ' ' . $server_public_host_key : false; } $this->signature_validated = true; switch ($this->signature_format) { - case 'ssh-dss': - $zero = new BigInteger(); - - if (strlen($server_public_host_key) < 4) { - return false; - } - $temp = unpack('Nlength', $this->_string_shift($server_public_host_key, 4)); - $p = new BigInteger($this->_string_shift($server_public_host_key, $temp['length']), -256); - - if (strlen($server_public_host_key) < 4) { - return false; - } - $temp = unpack('Nlength', $this->_string_shift($server_public_host_key, 4)); - $q = new BigInteger($this->_string_shift($server_public_host_key, $temp['length']), -256); - - if (strlen($server_public_host_key) < 4) { - return false; - } - $temp = unpack('Nlength', $this->_string_shift($server_public_host_key, 4)); - $g = new BigInteger($this->_string_shift($server_public_host_key, $temp['length']), -256); - - if (strlen($server_public_host_key) < 4) { - return false; - } - $temp = unpack('Nlength', $this->_string_shift($server_public_host_key, 4)); - $y = new BigInteger($this->_string_shift($server_public_host_key, $temp['length']), -256); - - /* The value for 'dss_signature_blob' is encoded as a string containing - r, followed by s (which are 160-bit integers, without lengths or - padding, unsigned, and in network byte order). */ - $temp = unpack('Nlength', $this->_string_shift($signature, 4)); - if ($temp['length'] != 40) { - user_error('Invalid signature'); - return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); - } - - $r = new BigInteger($this->_string_shift($signature, 20), 256); - $s = new BigInteger($this->_string_shift($signature, 20), 256); - - switch (true) { - case $r->equals($zero): - case $r->compare($q) >= 0: - case $s->equals($zero): - case $s->compare($q) >= 0: - user_error('Invalid signature'); - return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); - } - - $w = $s->modInverse($q); - - $u1 = $w->multiply(new BigInteger(sha1($this->exchange_hash), 16)); - list(, $u1) = $u1->divide($q); - - $u2 = $w->multiply($r); - list(, $u2) = $u2->divide($q); - - $g = $g->modPow($u1, $p); - $y = $y->modPow($u2, $p); - - $v = $g->multiply($y); - list(, $v) = $v->divide($p); - list(, $v) = $v->divide($q); - - if (!$v->equals($r)) { - user_error('Bad server signature'); - return $this->_disconnect(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE); - } - - break; - case 'ssh-rsa': - case 'rsa-sha2-256': - case 'rsa-sha2-512': - if (strlen($server_public_host_key) < 4) { - return false; - } - $temp = unpack('Nlength', $this->_string_shift($server_public_host_key, 4)); - $e = new BigInteger($this->_string_shift($server_public_host_key, $temp['length']), -256); - - if (strlen($server_public_host_key) < 4) { - return false; - } - $temp = unpack('Nlength', $this->_string_shift($server_public_host_key, 4)); - $rawN = $this->_string_shift($server_public_host_key, $temp['length']); - $n = new BigInteger($rawN, -256); - $nLength = strlen(ltrim($rawN, "\0")); - - /* - if (strlen($signature) < 4) { - return false; - } - $temp = unpack('Nlength', $this->_string_shift($signature, 4)); - $signature = $this->_string_shift($signature, $temp['length']); - - $rsa = new RSA(); + case 'ssh-ed25519': + case 'ecdsa-sha2-nistp256': + case 'ecdsa-sha2-nistp384': + case 'ecdsa-sha2-nistp521': + $key = EC::loadFormat('OpenSSH', $server_public_host_key) + ->withSignatureFormat('SSH2'); switch ($this->signature_format) { - case 'rsa-sha2-512': + case 'ssh-ed25519': $hash = 'sha512'; break; - case 'rsa-sha2-256': + case 'ecdsa-sha2-nistp256': $hash = 'sha256'; break; - //case 'ssh-rsa': - default: - $hash = 'sha1'; - } - $rsa->setHash($hash); - $rsa->setSignatureMode(RSA::SIGNATURE_PKCS1); - $rsa->loadKey(array('e' => $e, 'n' => $n), RSA::PUBLIC_FORMAT_RAW); - - if (!$rsa->verify($this->exchange_hash, $signature)) { - user_error('Bad server signature'); - return $this->_disconnect(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE); - } - */ - - if (strlen($signature) < 4) { - return false; - } - $temp = unpack('Nlength', $this->_string_shift($signature, 4)); - $s = new BigInteger($this->_string_shift($signature, $temp['length']), 256); - - // validate an RSA signature per "8.2 RSASSA-PKCS1-v1_5", "5.2.2 RSAVP1", and "9.1 EMSA-PSS" in the - // following URL: - // ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-1/pkcs-1v2-1.pdf - - // also, see SSHRSA.c (rsa2_verifysig) in PuTTy's source. - - if ($s->compare(new BigInteger()) < 0 || $s->compare($n->subtract(new BigInteger(1))) > 0) { - user_error('Invalid signature'); - return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); + case 'ecdsa-sha2-nistp384': + $hash = 'sha384'; + break; + case 'ecdsa-sha2-nistp521': + $hash = 'sha512'; } + $key = $key->withHash($hash); + break; + case 'ssh-dss': + $key = DSA::loadFormat('OpenSSH', $server_public_host_key) + ->withSignatureFormat('SSH2') + ->withHash('sha1'); + break; + case 'ssh-rsa': + case 'rsa-sha2-256': + case 'rsa-sha2-512': + // could be ssh-rsa, rsa-sha2-256, rsa-sha2-512 + // we don't check here because we already checked in key_exchange + // some signatures have the type embedded within the message and some don't + list(, $signature) = Strings::unpackSSH2('ss', $signature); - $s = $s->modPow($e, $n); - $s = $s->toBytes(); - + $key = RSA::loadFormat('OpenSSH', $server_public_host_key) + ->withPadding(RSA::SIGNATURE_PKCS1); switch ($this->signature_format) { case 'rsa-sha2-512': $hash = 'sha512'; @@ -5027,41 +4954,26 @@ function getServerPublicHostKey() default: $hash = 'sha1'; } - $hashObj = new Hash($hash); - switch ($this->signature_format) { - case 'rsa-sha2-512': - $h = pack('N5a*', 0x00305130, 0x0D060960, 0x86480165, 0x03040203, 0x05000440, $hashObj->hash($this->exchange_hash)); - break; - case 'rsa-sha2-256': - $h = pack('N5a*', 0x00303130, 0x0D060960, 0x86480165, 0x03040201, 0x05000420, $hashObj->hash($this->exchange_hash)); - break; - //case 'ssh-rsa': - default: - $hash = 'sha1'; - $h = pack('N4a*', 0x00302130, 0x0906052B, 0x0E03021A, 0x05000414, $hashObj->hash($this->exchange_hash)); - } - $h = chr(0x01) . str_repeat(chr(0xFF), $nLength - 2 - strlen($h)) . $h; - - if ($s != $h) { - user_error('Bad server signature'); - return $this->_disconnect(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE); - } + $key = $key->withHash($hash); break; default: - user_error('Unsupported signature format'); - return $this->_disconnect(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE); + $this->disconnect_helper(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE); + throw new NoSupportedAlgorithmsException('Unsupported signature format'); } - return $this->signature_format . ' ' . base64_encode($this->server_public_host_key); + if (!$key->verify($this->exchange_hash, $signature)) { + return $this->disconnect_helper(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE); + }; + + return $this->signature_format . ' ' . $server_public_host_key; } /** * Returns the exit status of an SSH command or false. * * @return false|int - * @access public */ - function getExitStatus() + public function getExitStatus() { if (is_null($this->exit_status)) { return false; @@ -5073,9 +4985,8 @@ function getExitStatus() * Returns the number of columns for the terminal window size. * * @return int - * @access public */ - function getWindowColumns() + public function getWindowColumns() { return $this->windowColumns; } @@ -5084,9 +4995,8 @@ function getWindowColumns() * Returns the number of rows for the terminal window size. * * @return int - * @access public */ - function getWindowRows() + public function getWindowRows() { return $this->windowRows; } @@ -5095,9 +5005,8 @@ function getWindowRows() * Sets the number of columns for the terminal window size. * * @param int $value - * @access public */ - function setWindowColumns($value) + public function setWindowColumns($value) { $this->windowColumns = $value; } @@ -5106,9 +5015,8 @@ function setWindowColumns($value) * Sets the number of rows for the terminal window size. * * @param int $value - * @access public */ - function setWindowRows($value) + public function setWindowRows($value) { $this->windowRows = $value; } @@ -5118,22 +5026,79 @@ function setWindowRows($value) * * @param int $columns * @param int $rows - * @access public */ - function setWindowSize($columns = 80, $rows = 24) + public function setWindowSize($columns = 80, $rows = 24) { $this->windowColumns = $columns; $this->windowRows = $rows; } /** + * To String Magic Method + * + * @return string + */ + #[\ReturnTypeWillChange] + public function __toString() + { + return $this->getResourceId(); + } + + /** + * Get Resource ID + * + * We use {} because that symbols should not be in URL according to + * {@link http://tools.ietf.org/html/rfc3986#section-2 RFC}. + * It will safe us from any conflicts, because otherwise regexp will + * match all alphanumeric domains. + * + * @return string + */ + public function getResourceId() + { + return '{' . spl_object_hash($this) . '}'; + } + + /** + * Return existing connection + * + * @param string $id + * + * @return bool|SSH2 will return false if no such connection + */ + public static function getConnectionByResourceId($id) + { + if (isset(self::$connections[$id])) { + return self::$connections[$id] instanceof \WeakReference ? self::$connections[$id]->get() : self::$connections[$id]; + } + return false; + } + + /** + * Return all excising connections + * + * @return array + */ + public static function getConnections() + { + if (!class_exists('WeakReference')) { + /** @var array */ + return self::$connections; + } + $temp = []; + foreach (self::$connections as $key => $ref) { + $temp[$key] = $ref->get(); + } + return $temp; + } + + /* * Update packet types in log history * * @param string $old * @param string $new - * @access private */ - function _updateLogHistory($old, $new) + private function updateLogHistory($old, $new) { if (defined('NET_SSH2_LOGGING') && NET_SSH2_LOGGING == self::LOG_COMPLEX) { $this->message_number_log[count($this->message_number_log) - 1] = str_replace( @@ -5143,4 +5108,31 @@ function _updateLogHistory($old, $new) ); } } + + /** + * Return the list of authentication methods that may productively continue authentication. + * + * @see https://tools.ietf.org/html/rfc4252#section-5.1 + * @return array|null + */ + public function getAuthMethodsToContinue() + { + return $this->auth_methods_to_continue; + } + + /** + * Enables "smart" multi-factor authentication (MFA) + */ + public function enableSmartMFA() + { + $this->smartMFA = true; + } + + /** + * Disables "smart" multi-factor authentication (MFA) + */ + public function disableSmartMFA() + { + $this->smartMFA = false; + } } diff --git a/vendor/phpseclib/phpseclib/phpseclib/System/SSH/Agent.php b/vendor/phpseclib/phpseclib/phpseclib/System/SSH/Agent.php index 2b25250b..d03bf244 100644 --- a/vendor/phpseclib/phpseclib/phpseclib/System/SSH/Agent.php +++ b/vendor/phpseclib/phpseclib/phpseclib/System/SSH/Agent.php @@ -3,6 +3,8 @@ /** * Pure-PHP ssh-agent client. * + * {@internal See http://api.libssh.org/rfc/PROTOCOL.agent} + * * PHP version 5 * * Here are some examples of how to use this library: @@ -10,9 +12,9 @@ * login('username', $agent)) { * exit('Login Failed'); * } @@ -22,36 +24,34 @@ * ?> * * - * @category System - * @package SSH\Agent * @author Jim Wigginton * @copyright 2014 Jim Wigginton * @license http://www.opensource.org/licenses/mit-license.html MIT License * @link http://phpseclib.sourceforge.net - * @internal See http://api.libssh.org/rfc/PROTOCOL.agent */ -namespace phpseclib\System\SSH; +namespace phpseclib3\System\SSH; -use phpseclib\Crypt\RSA; -use phpseclib\System\SSH\Agent\Identity; +use phpseclib3\Common\Functions\Strings; +use phpseclib3\Crypt\PublicKeyLoader; +use phpseclib3\Crypt\RSA; +use phpseclib3\Exception\BadConfigurationException; +use phpseclib3\Net\SSH2; +use phpseclib3\System\SSH\Agent\Identity; /** * Pure-PHP ssh-agent client identity factory * - * requestIdentities() method pumps out \phpseclib\System\SSH\Agent\Identity objects + * requestIdentities() method pumps out \phpseclib3\System\SSH\Agent\Identity objects * - * @package SSH\Agent * @author Jim Wigginton - * @access public */ class Agent { - /**#@+ - * Message numbers - * - * @access private - */ + use Common\Traits\ReadBytes; + + // Message numbers + // to request SSH1 keys you have to use SSH_AGENTC_REQUEST_RSA_IDENTITIES (1) const SSH_AGENTC_REQUEST_IDENTITIES = 11; // this is the SSH2 response; the SSH1 response is SSH_AGENT_RSA_IDENTITIES_ANSWER (2). @@ -60,20 +60,15 @@ class Agent const SSH_AGENTC_SIGN_REQUEST = 13; // the SSH1 response is SSH_AGENT_RSA_RESPONSE (4) const SSH_AGENT_SIGN_RESPONSE = 14; - /**#@-*/ - /**@+ - * Agent forwarding status - * - * @access private - */ + // Agent forwarding status + // no forwarding requested and not active const FORWARD_NONE = 0; // request agent forwarding when opportune const FORWARD_REQUEST = 1; // forwarding has been request and is active const FORWARD_ACTIVE = 2; - /**#@-*/ /** * Unused @@ -84,40 +79,42 @@ class Agent * Socket Resource * * @var resource - * @access private */ - var $fsock; + private $fsock; /** * Agent forwarding status * - * @access private + * @var int */ - var $forward_status = self::FORWARD_NONE; + private $forward_status = self::FORWARD_NONE; /** * Buffer for accumulating forwarded authentication * agent data arriving on SSH data channel destined * for agent unix socket * - * @access private + * @var string */ - var $socket_buffer = ''; + private $socket_buffer = ''; /** * Tracking the number of bytes we are expecting * to arrive for the agent socket on the SSH data * channel + * + * @var int */ - var $expected_bytes = 0; + private $expected_bytes = 0; /** * Default Constructor * - * @return \phpseclib\System\SSH\Agent - * @access public + * @return \phpseclib3\System\SSH\Agent + * @throws \phpseclib3\Exception\BadConfigurationException if SSH_AUTH_SOCK cannot be found + * @throws \RuntimeException on connection errors */ - function __construct($address = null) + public function __construct($address = null) { if (!$address) { switch (true) { @@ -128,14 +125,13 @@ function __construct($address = null) $address = $_ENV['SSH_AUTH_SOCK']; break; default: - user_error('SSH_AUTH_SOCK not found'); - return false; + throw new BadConfigurationException('SSH_AUTH_SOCK not found'); } } $this->fsock = fsockopen('unix://' . $address, 0, $errno, $errstr); if (!$this->fsock) { - user_error("Unable to connect to ssh-agent (Error $errno: $errstr)"); + throw new \RuntimeException("Unable to connect to ssh-agent (Error $errno: $errstr)"); } } @@ -143,85 +139,49 @@ function __construct($address = null) * Request Identities * * See "2.5.2 Requesting a list of protocol 2 keys" - * Returns an array containing zero or more \phpseclib\System\SSH\Agent\Identity objects + * Returns an array containing zero or more \phpseclib3\System\SSH\Agent\Identity objects * * @return array - * @access public + * @throws \RuntimeException on receipt of unexpected packets */ - function requestIdentities() + public function requestIdentities() { if (!$this->fsock) { - return array(); + return []; } $packet = pack('NC', 1, self::SSH_AGENTC_REQUEST_IDENTITIES); if (strlen($packet) != fputs($this->fsock, $packet)) { - user_error('Connection closed while requesting identities'); - return array(); + throw new \RuntimeException('Connection closed while requesting identities'); } - $temp = fread($this->fsock, 4); - if (strlen($temp) != 4) { - user_error('Connection closed while requesting identities'); - return array(); - } - $length = current(unpack('N', $temp)); - $type = ord(fread($this->fsock, 1)); + $length = current(unpack('N', $this->readBytes(4))); + $packet = $this->readBytes($length); + + list($type, $keyCount) = Strings::unpackSSH2('CN', $packet); if ($type != self::SSH_AGENT_IDENTITIES_ANSWER) { - user_error('Unable to request identities'); - return array(); + throw new \RuntimeException('Unable to request identities'); } - $identities = array(); - $temp = fread($this->fsock, 4); - if (strlen($temp) != 4) { - user_error('Connection closed while requesting identities'); - return array(); - } - $keyCount = current(unpack('N', $temp)); + $identities = []; for ($i = 0; $i < $keyCount; $i++) { - $temp = fread($this->fsock, 4); - if (strlen($temp) != 4) { - user_error('Connection closed while requesting identities'); - return array(); - } - $length = current(unpack('N', $temp)); - $key_blob = fread($this->fsock, $length); - if (strlen($key_blob) != $length) { - user_error('Connection closed while requesting identities'); - return array(); - } - $key_str = 'ssh-rsa ' . base64_encode($key_blob); - $temp = fread($this->fsock, 4); - if (strlen($temp) != 4) { - user_error('Connection closed while requesting identities'); - return array(); - } - $length = current(unpack('N', $temp)); - if ($length) { - $temp = fread($this->fsock, $length); - if (strlen($temp) != $length) { - user_error('Connection closed while requesting identities'); - return array(); - } - $key_str.= ' ' . $temp; - } - $length = current(unpack('N', substr($key_blob, 0, 4))); - $key_type = substr($key_blob, 4, $length); + list($key_blob, $comment) = Strings::unpackSSH2('ss', $packet); + $temp = $key_blob; + list($key_type) = Strings::unpackSSH2('s', $temp); switch ($key_type) { case 'ssh-rsa': - $key = new RSA(); - $key->loadKey($key_str); - break; case 'ssh-dss': - // not currently supported - break; + case 'ssh-ed25519': + case 'ecdsa-sha2-nistp256': + case 'ecdsa-sha2-nistp384': + case 'ecdsa-sha2-nistp521': + $key = PublicKeyLoader::load($key_type . ' ' . base64_encode($key_blob)); } // resources are passed by reference by default if (isset($key)) { - $identity = new Identity($this->fsock); - $identity->setPublicKey($key); - $identity->setPublicKeyBlob($key_blob); + $identity = (new Identity($this->fsock)) + ->withPublicKey($key) + ->withPublicKeyBlob($key_blob); $identities[] = $identity; unset($key); } @@ -234,11 +194,9 @@ function requestIdentities() * Signal that agent forwarding should * be requested when a channel is opened * - * @param Net_SSH2 $ssh - * @return bool - * @access public + * @return void */ - function startSSHForwarding($ssh) + public function startSSHForwarding() { if ($this->forward_status == self::FORWARD_NONE) { $this->forward_status = self::FORWARD_REQUEST; @@ -248,38 +206,15 @@ function startSSHForwarding($ssh) /** * Request agent forwarding of remote server * - * @param Net_SSH2 $ssh + * @param \phpseclib3\Net\SSH2 $ssh * @return bool - * @access private */ - function _request_forwarding($ssh) + private function request_forwarding(SSH2 $ssh) { - $request_channel = $ssh->_get_open_channel(); - if ($request_channel === false) { - return false; - } - - $packet = pack( - 'CNNa*C', - NET_SSH2_MSG_CHANNEL_REQUEST, - $ssh->server_channels[$request_channel], - strlen('auth-agent-req@openssh.com'), - 'auth-agent-req@openssh.com', - 1 - ); - - $ssh->channel_status[$request_channel] = NET_SSH2_MSG_CHANNEL_REQUEST; - - if (!$ssh->_send_binary_packet($packet)) { + if (!$ssh->requestAgentForwarding()) { return false; } - $response = $ssh->_get_channel_packet($request_channel); - if ($response === false) { - return false; - } - - $ssh->channel_status[$request_channel] = NET_SSH2_MSG_CHANNEL_OPEN; $this->forward_status = self::FORWARD_ACTIVE; return true; @@ -292,13 +227,12 @@ function _request_forwarding($ssh) * open to give the SSH Agent an opportunity * to take further action. i.e. request agent forwarding * - * @param Net_SSH2 $ssh - * @access private + * @param \phpseclib3\Net\SSH2 $ssh */ - function _on_channel_open($ssh) + public function registerChannelOpen(SSH2 $ssh) { if ($this->forward_status == self::FORWARD_REQUEST) { - $this->_request_forwarding($ssh); + $this->request_forwarding($ssh); } } @@ -306,13 +240,13 @@ function _on_channel_open($ssh) * Forward data to SSH Agent and return data reply * * @param string $data - * @return data from SSH Agent - * @access private + * @return string Data from SSH Agent + * @throws \RuntimeException on connection errors */ - function _forward_data($data) + public function forwardData($data) { if ($this->expected_bytes > 0) { - $this->socket_buffer.= $data; + $this->socket_buffer .= $data; $this->expected_bytes -= strlen($data); } else { $agent_data_bytes = current(unpack('N', $data)); @@ -325,25 +259,15 @@ function _forward_data($data) } if (strlen($this->socket_buffer) != fwrite($this->fsock, $this->socket_buffer)) { - user_error('Connection closed attempting to forward data to SSH agent'); - return false; + throw new \RuntimeException('Connection closed attempting to forward data to SSH agent'); } $this->socket_buffer = ''; $this->expected_bytes = 0; - $temp = fread($this->fsock, 4); - if (strlen($temp) != 4) { - user_error('Connection closed while reading data response'); - return false; - } - $agent_reply_bytes = current(unpack('N', $temp)); + $agent_reply_bytes = current(unpack('N', $this->readBytes(4))); - $agent_reply_data = fread($this->fsock, $agent_reply_bytes); - if (strlen($agent_reply_data) != $agent_reply_bytes) { - user_error('Connection closed while reading data response'); - return false; - } + $agent_reply_data = $this->readBytes($agent_reply_bytes); $agent_reply_data = current(unpack('a*', $agent_reply_data)); return pack('Na*', $agent_reply_bytes, $agent_reply_data); diff --git a/vendor/phpseclib/phpseclib/phpseclib/System/SSH/Agent/Identity.php b/vendor/phpseclib/phpseclib/phpseclib/System/SSH/Agent/Identity.php index 68b6bfdf..653e8ea5 100644 --- a/vendor/phpseclib/phpseclib/phpseclib/System/SSH/Agent/Identity.php +++ b/vendor/phpseclib/phpseclib/phpseclib/System/SSH/Agent/Identity.php @@ -1,93 +1,102 @@ * @copyright 2009 Jim Wigginton * @license http://www.opensource.org/licenses/mit-license.html MIT License * @link http://phpseclib.sourceforge.net - * @internal See http://api.libssh.org/rfc/PROTOCOL.agent */ -namespace phpseclib\System\SSH\Agent; +namespace phpseclib3\System\SSH\Agent; -use phpseclib\System\SSH\Agent; +use phpseclib3\Common\Functions\Strings; +use phpseclib3\Crypt\Common\PrivateKey; +use phpseclib3\Crypt\Common\PublicKey; +use phpseclib3\Crypt\DSA; +use phpseclib3\Crypt\EC; +use phpseclib3\Crypt\RSA; +use phpseclib3\Exception\UnsupportedAlgorithmException; +use phpseclib3\System\SSH\Agent; +use phpseclib3\System\SSH\Common\Traits\ReadBytes; /** * Pure-PHP ssh-agent client identity object * - * Instantiation should only be performed by \phpseclib\System\SSH\Agent class. - * This could be thought of as implementing an interface that phpseclib\Crypt\RSA + * Instantiation should only be performed by \phpseclib3\System\SSH\Agent class. + * This could be thought of as implementing an interface that phpseclib3\Crypt\RSA * implements. ie. maybe a Net_SSH_Auth_PublicKey interface or something. * The methods in this interface would be getPublicKey and sign since those are the * methods phpseclib looks for to perform public key authentication. * - * @package SSH\Agent * @author Jim Wigginton - * @access internal + * @internal */ -class Identity +class Identity implements PrivateKey { - /**@+ - * Signature Flags - * - * See https://tools.ietf.org/html/draft-miller-ssh-agent-00#section-5.3 - * - * @access private - */ + use ReadBytes; + + // Signature Flags + // See https://tools.ietf.org/html/draft-miller-ssh-agent-00#section-5.3 const SSH_AGENT_RSA2_256 = 2; const SSH_AGENT_RSA2_512 = 4; - /**#@-*/ /** * Key Object * - * @var \phpseclib\Crypt\RSA - * @access private + * @var PublicKey * @see self::getPublicKey() */ - var $key; + private $key; /** * Key Blob * * @var string - * @access private * @see self::sign() */ - var $key_blob; + private $key_blob; /** * Socket Resource * * @var resource - * @access private * @see self::sign() */ - var $fsock; + private $fsock; /** * Signature flags * * @var int - * @access private * @see self::sign() * @see self::setHash() */ - var $flags = 0; + private $flags = 0; + + /** + * Curve Aliases + * + * @var array + */ + private static $curveAliases = [ + 'secp256r1' => 'nistp256', + 'secp384r1' => 'nistp384', + 'secp521r1' => 'nistp521', + 'Ed25519' => 'Ed25519' + ]; /** * Default Constructor. * * @param resource $fsock - * @return \phpseclib\System\SSH\Agent\Identity - * @access private */ - function __construct($fsock) + public function __construct($fsock) { $this->fsock = $fsock; } @@ -95,29 +104,36 @@ function __construct($fsock) /** * Set Public Key * - * Called by \phpseclib\System\SSH\Agent::requestIdentities() + * Called by \phpseclib3\System\SSH\Agent::requestIdentities() * - * @param \phpseclib\Crypt\RSA $key - * @access private + * @param \phpseclib3\Crypt\Common\PublicKey $key */ - function setPublicKey($key) + public function withPublicKey(PublicKey $key) { - $this->key = $key; - $this->key->setPublicKey(); + if ($key instanceof EC) { + if (is_array($key->getCurve()) || !isset(self::$curveAliases[$key->getCurve()])) { + throw new UnsupportedAlgorithmException('The only supported curves are nistp256, nistp384, nistp512 and Ed25519'); + } + } + + $new = clone $this; + $new->key = $key; + return $new; } /** * Set Public Key * - * Called by \phpseclib\System\SSH\Agent::requestIdentities(). The key blob could be extracted from $this->key + * Called by \phpseclib3\System\SSH\Agent::requestIdentities(). The key blob could be extracted from $this->key * but this saves a small amount of computation. * * @param string $key_blob - * @access private */ - function setPublicKeyBlob($key_blob) + public function withPublicKeyBlob($key_blob) { - $this->key_blob = $key_blob; + $new = clone $this; + $new->key_blob = $key_blob; + return $new; } /** @@ -125,51 +141,116 @@ function setPublicKeyBlob($key_blob) * * Wrapper for $this->key->getPublicKey() * - * @param int $format optional + * @param string $type optional * @return mixed - * @access public */ - function getPublicKey($format = null) + public function getPublicKey($type = 'PKCS8') + { + return $this->key; + } + + /** + * Sets the hash + * + * @param string $hash + */ + public function withHash($hash) + { + $new = clone $this; + + $hash = strtolower($hash); + + if ($this->key instanceof RSA) { + $new->flags = 0; + switch ($hash) { + case 'sha1': + break; + case 'sha256': + $new->flags = self::SSH_AGENT_RSA2_256; + break; + case 'sha512': + $new->flags = self::SSH_AGENT_RSA2_512; + break; + default: + throw new UnsupportedAlgorithmException('The only supported hashes for RSA are sha1, sha256 and sha512'); + } + } + if ($this->key instanceof EC) { + switch ($this->key->getCurve()) { + case 'secp256r1': + $expectedHash = 'sha256'; + break; + case 'secp384r1': + $expectedHash = 'sha384'; + break; + //case 'secp521r1': + //case 'Ed25519': + default: + $expectedHash = 'sha512'; + } + if ($hash != $expectedHash) { + throw new UnsupportedAlgorithmException('The only supported hash for ' . self::$curveAliases[$this->key->getCurve()] . ' is ' . $expectedHash); + } + } + if ($this->key instanceof DSA) { + if ($hash != 'sha1') { + throw new UnsupportedAlgorithmException('The only supported hash for DSA is sha1'); + } + } + return $new; + } + + /** + * Sets the padding + * + * Only PKCS1 padding is supported + * + * @param string $padding + */ + public function withPadding($padding) { - return !isset($format) ? $this->key->getPublicKey() : $this->key->getPublicKey($format); + if (!$this->key instanceof RSA) { + throw new UnsupportedAlgorithmException('Only RSA keys support padding'); + } + if ($padding != RSA::SIGNATURE_PKCS1 && $padding != RSA::SIGNATURE_RELAXED_PKCS1) { + throw new UnsupportedAlgorithmException('ssh-agent can only create PKCS1 signatures'); + } + return $this; } /** - * Set Signature Mode + * Determines the signature padding mode * - * Doesn't do anything as ssh-agent doesn't let you pick and choose the signature mode. ie. - * ssh-agent's only supported mode is \phpseclib\Crypt\RSA::SIGNATURE_PKCS1 + * Valid values are: ASN1, SSH2, Raw * - * @param int $mode - * @access public + * @param string $format */ - function setSignatureMode($mode) + public function withSignatureFormat($format) { + if ($this->key instanceof RSA) { + throw new UnsupportedAlgorithmException('Only DSA and EC keys support signature format setting'); + } + if ($format != 'SSH2') { + throw new UnsupportedAlgorithmException('Only SSH2-formatted signatures are currently supported'); + } + + return $this; } /** - * Set Hash + * Returns the curve * - * ssh-agent doesn't support using hashes for RSA other than SHA1 + * Returns a string if it's a named curve, an array if not * - * @param string $hash - * @access public + * @return string|array */ - function setHash($hash) + public function getCurve() { - $this->flags = 0; - switch ($hash) { - case 'sha1': - break; - case 'sha256': - $this->flags = self::SSH_AGENT_RSA2_256; - break; - case 'sha512': - $this->flags = self::SSH_AGENT_RSA2_512; - break; - default: - user_error('The only supported hashes for RSA are sha1, sha256 and sha512'); + if (!$this->key instanceof EC) { + throw new UnsupportedAlgorithmException('Only EC keys have curves'); } + + return $this->key->getCurve(); } /** @@ -179,63 +260,61 @@ function setHash($hash) * * @param string $message * @return string - * @access public + * @throws \RuntimeException on connection errors + * @throws \phpseclib3\Exception\UnsupportedAlgorithmException if the algorithm is unsupported */ - function sign($message) + public function sign($message) { // the last parameter (currently 0) is for flags and ssh-agent only defines one flag (for ssh-dss): SSH_AGENT_OLD_SIGNATURE - $packet = pack('CNa*Na*N', Agent::SSH_AGENTC_SIGN_REQUEST, strlen($this->key_blob), $this->key_blob, strlen($message), $message, $this->flags); - $packet = pack('Na*', strlen($packet), $packet); + $packet = Strings::packSSH2( + 'CssN', + Agent::SSH_AGENTC_SIGN_REQUEST, + $this->key_blob, + $message, + $this->flags + ); + $packet = Strings::packSSH2('s', $packet); if (strlen($packet) != fputs($this->fsock, $packet)) { - user_error('Connection closed during signing'); - return false; + throw new \RuntimeException('Connection closed during signing'); } - $temp = fread($this->fsock, 4); - if (strlen($temp) != 4) { - user_error('Connection closed during signing'); - return false; - } - $length = current(unpack('N', $temp)); - $type = ord(fread($this->fsock, 1)); + $length = current(unpack('N', $this->readBytes(4))); + $packet = $this->readBytes($length); + + list($type, $signature_blob) = Strings::unpackSSH2('Cs', $packet); if ($type != Agent::SSH_AGENT_SIGN_RESPONSE) { - user_error('Unable to retrieve signature'); - return false; + throw new \RuntimeException('Unable to retrieve signature'); } - $signature_blob = fread($this->fsock, $length - 1); - if (strlen($signature_blob) != $length - 1) { - user_error('Connection closed during signing'); - return false; - } - $length = current(unpack('N', $this->_string_shift($signature_blob, 4))); - if ($length != strlen($signature_blob)) { - user_error('Malformed signature blob'); + if (!$this->key instanceof RSA) { + return $signature_blob; } - $length = current(unpack('N', $this->_string_shift($signature_blob, 4))); - if ($length > strlen($signature_blob) + 4) { - user_error('Malformed signature blob'); - } - $type = $this->_string_shift($signature_blob, $length); - $this->_string_shift($signature_blob, 4); + + list($type, $signature_blob) = Strings::unpackSSH2('ss', $signature_blob); return $signature_blob; } /** - * String Shift - * - * Inspired by array_shift + * Returns the private key * - * @param string $string - * @param int $index + * @param string $type + * @param array $options optional * @return string - * @access private */ - function _string_shift(&$string, $index = 1) + public function toString($type, array $options = []) + { + throw new \RuntimeException('ssh-agent does not provide a mechanism to get the private key'); + } + + /** + * Sets the password + * + * @param string|bool $password + * @return never + */ + public function withPassword($password = false) { - $substr = substr($string, 0, $index); - $string = substr($string, $index); - return $substr; + throw new \RuntimeException('ssh-agent does not provide a mechanism to get the private key'); } } diff --git a/vendor/phpseclib/phpseclib/phpseclib/System/SSH/Common/Traits/ReadBytes.php b/vendor/phpseclib/phpseclib/phpseclib/System/SSH/Common/Traits/ReadBytes.php new file mode 100644 index 00000000..6fd032bd --- /dev/null +++ b/vendor/phpseclib/phpseclib/phpseclib/System/SSH/Common/Traits/ReadBytes.php @@ -0,0 +1,37 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\System\SSH\Common\Traits; + +/** + * ReadBytes trait + * + * @author Jim Wigginton + */ +trait ReadBytes +{ + /** + * Read data + * + * @param int $length + * @throws \RuntimeException on connection errors + */ + public function readBytes($length) + { + $temp = fread($this->fsock, $length); + if (strlen($temp) != $length) { + throw new \RuntimeException("Expected $length bytes; got " . strlen($temp)); + } + return $temp; + } +} diff --git a/vendor/phpseclib/phpseclib/phpseclib/bootstrap.php b/vendor/phpseclib/phpseclib/phpseclib/bootstrap.php index 0da0999f..517106c3 100644 --- a/vendor/phpseclib/phpseclib/phpseclib/bootstrap.php +++ b/vendor/phpseclib/phpseclib/phpseclib/bootstrap.php @@ -1,14 +1,20 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\EventDispatcher; + +use Psr\EventDispatcher\StoppableEventInterface; + +if (interface_exists(StoppableEventInterface::class)) { + /** + * Event is the base class for classes containing event data. + * + * This class contains no event data. It is used by events that do not pass + * state information to an event handler when an event is raised. + * + * You can call the method stopPropagation() to abort the execution of + * further listeners in your event listener. + * + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + * @author Bernhard Schussek + * @author Nicolas Grekas + */ + class Event implements StoppableEventInterface + { + private $propagationStopped = false; + + /** + * Returns whether further event listeners should be triggered. + */ + public function isPropagationStopped(): bool + { + return $this->propagationStopped; + } + + /** + * Stops the propagation of the event to further event listeners. + * + * If multiple event listeners are connected to the same event, no + * further event listener will be triggered once any trigger calls + * stopPropagation(). + */ + public function stopPropagation(): void + { + $this->propagationStopped = true; + } + } +} else { + /** + * Event is the base class for classes containing event data. + * + * This class contains no event data. It is used by events that do not pass + * state information to an event handler when an event is raised. + * + * You can call the method stopPropagation() to abort the execution of + * further listeners in your event listener. + * + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + * @author Bernhard Schussek + * @author Nicolas Grekas + */ + class Event + { + private $propagationStopped = false; + + /** + * Returns whether further event listeners should be triggered. + */ + public function isPropagationStopped(): bool + { + return $this->propagationStopped; + } + + /** + * Stops the propagation of the event to further event listeners. + * + * If multiple event listeners are connected to the same event, no + * further event listener will be triggered once any trigger calls + * stopPropagation(). + */ + public function stopPropagation(): void + { + $this->propagationStopped = true; + } + } +} diff --git a/vendor/symfony/event-dispatcher-contracts/EventDispatcherInterface.php b/vendor/symfony/event-dispatcher-contracts/EventDispatcherInterface.php new file mode 100644 index 00000000..2d470af9 --- /dev/null +++ b/vendor/symfony/event-dispatcher-contracts/EventDispatcherInterface.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\EventDispatcher; + +use Psr\EventDispatcher\EventDispatcherInterface as PsrEventDispatcherInterface; + +if (interface_exists(PsrEventDispatcherInterface::class)) { + /** + * Allows providing hooks on domain-specific lifecycles by dispatching events. + */ + interface EventDispatcherInterface extends PsrEventDispatcherInterface + { + /** + * Dispatches an event to all registered listeners. + * + * For BC with Symfony 4, the $eventName argument is not declared explicitly on the + * signature of the method. Implementations that are not bound by this BC constraint + * MUST declare it explicitly, as allowed by PHP. + * + * @param object $event The event to pass to the event handlers/listeners + * @param string|null $eventName The name of the event to dispatch. If not supplied, + * the class of $event should be used instead. + * + * @return object The passed $event MUST be returned + */ + public function dispatch($event/*, string $eventName = null*/); + } +} else { + /** + * Allows providing hooks on domain-specific lifecycles by dispatching events. + */ + interface EventDispatcherInterface + { + /** + * Dispatches an event to all registered listeners. + * + * For BC with Symfony 4, the $eventName argument is not declared explicitly on the + * signature of the method. Implementations that are not bound by this BC constraint + * MUST declare it explicitly, as allowed by PHP. + * + * @param object $event The event to pass to the event handlers/listeners + * @param string|null $eventName The name of the event to dispatch. If not supplied, + * the class of $event should be used instead. + * + * @return object The passed $event MUST be returned + */ + public function dispatch($event/*, string $eventName = null*/); + } +} diff --git a/vendor/symfony/event-dispatcher-contracts/LICENSE b/vendor/symfony/event-dispatcher-contracts/LICENSE new file mode 100644 index 00000000..74cdc2db --- /dev/null +++ b/vendor/symfony/event-dispatcher-contracts/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018-2022 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 +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/event-dispatcher/ContainerAwareEventDispatcher.php b/vendor/symfony/event-dispatcher/ContainerAwareEventDispatcher.php deleted file mode 100644 index 3b98a684..00000000 --- a/vendor/symfony/event-dispatcher/ContainerAwareEventDispatcher.php +++ /dev/null @@ -1,195 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\EventDispatcher; - -use Symfony\Component\DependencyInjection\ContainerInterface; - -/** - * Lazily loads listeners and subscribers from the dependency injection - * container. - * - * @author Fabien Potencier - * @author Bernhard Schussek - * @author Jordan Alliot - */ -class ContainerAwareEventDispatcher extends EventDispatcher -{ - /** - * The container from where services are loaded. - * - * @var ContainerInterface - */ - private $container; - - /** - * The service IDs of the event listeners and subscribers. - * - * @var array - */ - private $listenerIds = array(); - - /** - * The services registered as listeners. - * - * @var array - */ - private $listeners = array(); - - /** - * Constructor. - * - * @param ContainerInterface $container A ContainerInterface instance - */ - public function __construct(ContainerInterface $container) - { - $this->container = $container; - } - - /** - * Adds a service as event listener. - * - * @param string $eventName Event for which the listener is added - * @param array $callback The service ID of the listener service & the method - * name that has to be called - * @param int $priority The higher this value, the earlier an event listener - * will be triggered in the chain. - * Defaults to 0. - * - * @throws \InvalidArgumentException - */ - public function addListenerService($eventName, $callback, $priority = 0) - { - if (!is_array($callback) || 2 !== count($callback)) { - throw new \InvalidArgumentException('Expected an array("service", "method") argument'); - } - - $this->listenerIds[$eventName][] = array($callback[0], $callback[1], $priority); - } - - public function removeListener($eventName, $listener) - { - $this->lazyLoad($eventName); - - if (isset($this->listenerIds[$eventName])) { - foreach ($this->listenerIds[$eventName] as $i => list($serviceId, $method, $priority)) { - $key = $serviceId.'.'.$method; - if (isset($this->listeners[$eventName][$key]) && $listener === array($this->listeners[$eventName][$key], $method)) { - unset($this->listeners[$eventName][$key]); - if (empty($this->listeners[$eventName])) { - unset($this->listeners[$eventName]); - } - unset($this->listenerIds[$eventName][$i]); - if (empty($this->listenerIds[$eventName])) { - unset($this->listenerIds[$eventName]); - } - } - } - } - - parent::removeListener($eventName, $listener); - } - - /** - * {@inheritdoc} - */ - public function hasListeners($eventName = null) - { - if (null === $eventName) { - return $this->listenerIds || $this->listeners || parent::hasListeners(); - } - - if (isset($this->listenerIds[$eventName])) { - return true; - } - - return parent::hasListeners($eventName); - } - - /** - * {@inheritdoc} - */ - public function getListeners($eventName = null) - { - if (null === $eventName) { - foreach ($this->listenerIds as $serviceEventName => $args) { - $this->lazyLoad($serviceEventName); - } - } else { - $this->lazyLoad($eventName); - } - - return parent::getListeners($eventName); - } - - /** - * {@inheritdoc} - */ - public function getListenerPriority($eventName, $listener) - { - $this->lazyLoad($eventName); - - return parent::getListenerPriority($eventName, $listener); - } - - /** - * Adds a service as event subscriber. - * - * @param string $serviceId The service ID of the subscriber service - * @param string $class The service's class name (which must implement EventSubscriberInterface) - */ - public function addSubscriberService($serviceId, $class) - { - foreach ($class::getSubscribedEvents() as $eventName => $params) { - if (is_string($params)) { - $this->listenerIds[$eventName][] = array($serviceId, $params, 0); - } elseif (is_string($params[0])) { - $this->listenerIds[$eventName][] = array($serviceId, $params[0], isset($params[1]) ? $params[1] : 0); - } else { - foreach ($params as $listener) { - $this->listenerIds[$eventName][] = array($serviceId, $listener[0], isset($listener[1]) ? $listener[1] : 0); - } - } - } - } - - public function getContainer() - { - return $this->container; - } - - /** - * Lazily loads listeners for this event from the dependency injection - * container. - * - * @param string $eventName The name of the event to dispatch. The name of - * the event is the name of the method that is - * invoked on listeners. - */ - protected function lazyLoad($eventName) - { - if (isset($this->listenerIds[$eventName])) { - foreach ($this->listenerIds[$eventName] as list($serviceId, $method, $priority)) { - $listener = $this->container->get($serviceId); - - $key = $serviceId.'.'.$method; - if (!isset($this->listeners[$eventName][$key])) { - $this->addListener($eventName, array($listener, $method), $priority); - } elseif ($listener !== $this->listeners[$eventName][$key]) { - parent::removeListener($eventName, array($this->listeners[$eventName][$key], $method)); - $this->addListener($eventName, array($listener, $method), $priority); - } - - $this->listeners[$eventName][$key] = $listener; - } - } - } -} diff --git a/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php b/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php index 988cf112..56116cf4 100644 --- a/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php +++ b/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php @@ -11,11 +11,17 @@ namespace Symfony\Component\EventDispatcher\Debug; +use Psr\EventDispatcher\StoppableEventInterface; +use Psr\Log\LoggerInterface; +use Symfony\Component\EventDispatcher\Event; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface; -use Symfony\Component\EventDispatcher\Event; +use Symfony\Component\EventDispatcher\LegacyEventDispatcherProxy; +use Symfony\Component\EventDispatcher\LegacyEventProxy; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\Stopwatch\Stopwatch; -use Psr\Log\LoggerInterface; +use Symfony\Contracts\EventDispatcher\Event as ContractsEvent; /** * Collects some data about event listeners. @@ -29,24 +35,21 @@ class TraceableEventDispatcher implements TraceableEventDispatcherInterface protected $logger; protected $stopwatch; - private $called; + private $callStack; private $dispatcher; private $wrappedListeners; + private $orphanedEvents; + private $requestStack; + private $currentRequestHash = ''; - /** - * Constructor. - * - * @param EventDispatcherInterface $dispatcher An EventDispatcherInterface instance - * @param Stopwatch $stopwatch A Stopwatch instance - * @param LoggerInterface $logger A LoggerInterface instance - */ - public function __construct(EventDispatcherInterface $dispatcher, Stopwatch $stopwatch, LoggerInterface $logger = null) + public function __construct(EventDispatcherInterface $dispatcher, Stopwatch $stopwatch, LoggerInterface $logger = null, RequestStack $requestStack = null) { - $this->dispatcher = $dispatcher; + $this->dispatcher = LegacyEventDispatcherProxy::decorate($dispatcher); $this->stopwatch = $stopwatch; $this->logger = $logger; - $this->called = array(); - $this->wrappedListeners = array(); + $this->wrappedListeners = []; + $this->orphanedEvents = []; + $this->requestStack = $requestStack; } /** @@ -72,7 +75,7 @@ public function removeListener($eventName, $listener) { if (isset($this->wrappedListeners[$eventName])) { foreach ($this->wrappedListeners[$eventName] as $index => $wrappedListener) { - if ($wrappedListener->getWrappedListener() === $listener) { + if ($wrappedListener->getWrappedListener() === $listener || ($listener instanceof \Closure && $wrappedListener->getWrappedListener() == $listener)) { $listener = $wrappedListener; unset($this->wrappedListeners[$eventName][$index]); break; @@ -107,8 +110,8 @@ public function getListenerPriority($eventName, $listener) // we might have wrapped listeners for the event (if called while dispatching) // in that case get the priority by wrapper if (isset($this->wrappedListeners[$eventName])) { - foreach ($this->wrappedListeners[$eventName] as $index => $wrappedListener) { - if ($wrappedListener->getWrappedListener() === $listener) { + foreach ($this->wrappedListeners[$eventName] as $wrappedListener) { + if ($wrappedListener->getWrappedListener() === $listener || ($listener instanceof \Closure && $wrappedListener->getWrappedListener() == $listener)) { return $this->dispatcher->getListenerPriority($eventName, $wrappedListener); } } @@ -127,43 +130,75 @@ public function hasListeners($eventName = null) /** * {@inheritdoc} + * + * @param string|null $eventName */ - public function dispatch($eventName, Event $event = null) + public function dispatch($event/*, string $eventName = null*/) { - if (null === $event) { - $event = new Event(); + if (null === $this->callStack) { + $this->callStack = new \SplObjectStorage(); } - if (null !== $this->logger && $event->isPropagationStopped()) { - $this->logger->debug(sprintf('The "%s" event is already stopped. No listeners have been called.', $eventName)); - } - - $this->preProcess($eventName); - $this->preDispatch($eventName, $event); + $currentRequestHash = $this->currentRequestHash = $this->requestStack && ($request = $this->requestStack->getCurrentRequest()) ? spl_object_hash($request) : ''; + $eventName = 1 < \func_num_args() ? func_get_arg(1) : null; - $e = $this->stopwatch->start($eventName, 'section'); + if (\is_object($event)) { + $eventName = $eventName ?? \get_class($event); + } else { + @trigger_error(sprintf('Calling the "%s::dispatch()" method with the event name as first argument is deprecated since Symfony 4.3, pass it second and provide the event object first instead.', EventDispatcherInterface::class), \E_USER_DEPRECATED); + $swap = $event; + $event = $eventName ?? new Event(); + $eventName = $swap; - $this->dispatcher->dispatch($eventName, $event); + if (!$event instanceof Event) { + throw new \TypeError(sprintf('Argument 1 passed to "%s::dispatch()" must be an instance of "%s", "%s" given.', EventDispatcherInterface::class, Event::class, \is_object($event) ? \get_class($event) : \gettype($event))); + } + } - if ($e->isStarted()) { - $e->stop(); + if (null !== $this->logger && ($event instanceof Event || $event instanceof ContractsEvent || $event instanceof StoppableEventInterface) && $event->isPropagationStopped()) { + $this->logger->debug(sprintf('The "%s" event is already stopped. No listeners have been called.', $eventName)); } - $this->postDispatch($eventName, $event); - $this->postProcess($eventName); + $this->preProcess($eventName); + try { + $this->beforeDispatch($eventName, $event); + try { + $e = $this->stopwatch->start($eventName, 'section'); + try { + $this->dispatcher->dispatch($event, $eventName); + } finally { + if ($e->isStarted()) { + $e->stop(); + } + } + } finally { + $this->afterDispatch($eventName, $event); + } + } finally { + $this->currentRequestHash = $currentRequestHash; + $this->postProcess($eventName); + } return $event; } /** * {@inheritdoc} + * + * @param Request|null $request The request to get listeners for */ - public function getCalledListeners() + public function getCalledListeners(/* Request $request = null */) { - $called = array(); - foreach ($this->called as $eventName => $listeners) { - foreach ($listeners as $listener) { - $called[$eventName.'.'.$listener->getPretty()] = $listener->getInfo($eventName); + if (null === $this->callStack) { + return []; + } + + $hash = 1 <= \func_num_args() && null !== ($request = func_get_arg(0)) ? spl_object_hash($request) : null; + $called = []; + foreach ($this->callStack as $listener) { + [$eventName, $requestHash] = $this->callStack->getInfo(); + if (null === $hash || $hash === $requestHash) { + $called[] = $listener->getInfo($eventName); } } @@ -172,48 +207,75 @@ public function getCalledListeners() /** * {@inheritdoc} + * + * @param Request|null $request The request to get listeners for */ - public function getNotCalledListeners() + public function getNotCalledListeners(/* Request $request = null */) { try { $allListeners = $this->getListeners(); } catch (\Exception $e) { if (null !== $this->logger) { - $this->logger->info('An exception was thrown while getting the uncalled listeners.', array('exception' => $e)); + $this->logger->info('An exception was thrown while getting the uncalled listeners.', ['exception' => $e]); } // unable to retrieve the uncalled listeners - return array(); + return []; } - $notCalled = array(); - foreach ($allListeners as $eventName => $listeners) { - foreach ($listeners as $listener) { - $called = false; - if (isset($this->called[$eventName])) { - foreach ($this->called[$eventName] as $l) { - if ($l->getWrappedListener() === $listener) { - $called = true; - - break; - } - } + $hash = 1 <= \func_num_args() && null !== ($request = func_get_arg(0)) ? spl_object_hash($request) : null; + $calledListeners = []; + + if (null !== $this->callStack) { + foreach ($this->callStack as $calledListener) { + [, $requestHash] = $this->callStack->getInfo(); + + if (null === $hash || $hash === $requestHash) { + $calledListeners[] = $calledListener->getWrappedListener(); } + } + } - if (!$called) { + $notCalled = []; + foreach ($allListeners as $eventName => $listeners) { + foreach ($listeners as $listener) { + if (!\in_array($listener, $calledListeners, true)) { if (!$listener instanceof WrappedListener) { $listener = new WrappedListener($listener, null, $this->stopwatch, $this); } - $notCalled[$eventName.'.'.$listener->getPretty()] = $listener->getInfo($eventName); + $notCalled[] = $listener->getInfo($eventName); } } } - uasort($notCalled, array($this, 'sortListenersByPriority')); + uasort($notCalled, [$this, 'sortNotCalledListeners']); return $notCalled; } + /** + * @param Request|null $request The request to get orphaned events for + */ + public function getOrphanedEvents(/* Request $request = null */): array + { + if (1 <= \func_num_args() && null !== $request = func_get_arg(0)) { + return $this->orphanedEvents[spl_object_hash($request)] ?? []; + } + + if (!$this->orphanedEvents) { + return []; + } + + return array_merge(...array_values($this->orphanedEvents)); + } + + public function reset() + { + $this->callStack = null; + $this->orphanedEvents = []; + $this->currentRequestHash = ''; + } + /** * Proxies all method calls to the original event dispatcher. * @@ -224,41 +286,62 @@ public function getNotCalledListeners() */ public function __call($method, $arguments) { - return call_user_func_array(array($this->dispatcher, $method), $arguments); + return $this->dispatcher->{$method}(...$arguments); } /** * Called before dispatching the event. * - * @param string $eventName The event name - * @param Event $event The event + * @param object $event */ - protected function preDispatch($eventName, Event $event) + protected function beforeDispatch(string $eventName, $event) { + $this->preDispatch($eventName, $event instanceof Event ? $event : new LegacyEventProxy($event)); } /** * Called after dispatching the event. * - * @param string $eventName The event name - * @param Event $event The event + * @param object $event + */ + protected function afterDispatch(string $eventName, $event) + { + $this->postDispatch($eventName, $event instanceof Event ? $event : new LegacyEventProxy($event)); + } + + /** + * @deprecated since Symfony 4.3, will be removed in 5.0, use beforeDispatch instead + */ + protected function preDispatch($eventName, Event $event) + { + } + + /** + * @deprecated since Symfony 4.3, will be removed in 5.0, use afterDispatch instead */ protected function postDispatch($eventName, Event $event) { } - private function preProcess($eventName) + private function preProcess(string $eventName) { + if (!$this->dispatcher->hasListeners($eventName)) { + $this->orphanedEvents[$this->currentRequestHash][] = $eventName; + + return; + } + foreach ($this->dispatcher->getListeners($eventName) as $listener) { $priority = $this->getListenerPriority($eventName, $listener); - $wrappedListener = new WrappedListener($listener, null, $this->stopwatch, $this); + $wrappedListener = new WrappedListener($listener instanceof WrappedListener ? $listener->getWrappedListener() : $listener, null, $this->stopwatch, $this); $this->wrappedListeners[$eventName][] = $wrappedListener; $this->dispatcher->removeListener($eventName, $listener); $this->dispatcher->addListener($eventName, $wrappedListener, $priority); + $this->callStack->attach($wrappedListener, [$eventName, $this->currentRequestHash]); } } - private function postProcess($eventName) + private function postProcess(string $eventName) { unset($this->wrappedListeners[$eventName]); $skipped = false; @@ -272,19 +355,15 @@ private function postProcess($eventName) $this->dispatcher->addListener($eventName, $listener->getWrappedListener(), $priority); if (null !== $this->logger) { - $context = array('event' => $eventName, 'listener' => $listener->getPretty()); + $context = ['event' => $eventName, 'listener' => $listener->getPretty()]; } if ($listener->wasCalled()) { if (null !== $this->logger) { $this->logger->debug('Notified event "{event}" to listener "{listener}".', $context); } - - if (!isset($this->called[$eventName])) { - $this->called[$eventName] = new \SplObjectStorage(); - } - - $this->called[$eventName]->attach($listener); + } else { + $this->callStack->detach($listener); } if (null !== $this->logger && $skipped) { @@ -301,13 +380,17 @@ private function postProcess($eventName) } } - private function sortListenersByPriority($a, $b) + private function sortNotCalledListeners(array $a, array $b) { - if (is_int($a['priority']) && !is_int($b['priority'])) { + if (0 !== $cmp = strcmp($a['event'], $b['event'])) { + return $cmp; + } + + if (\is_int($a['priority']) && !\is_int($b['priority'])) { return 1; } - if (!is_int($a['priority']) && is_int($b['priority'])) { + if (!\is_int($a['priority']) && \is_int($b['priority'])) { return -1; } diff --git a/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcherInterface.php b/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcherInterface.php index 5483e815..4fedb9a4 100644 --- a/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcherInterface.php +++ b/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcherInterface.php @@ -12,23 +12,31 @@ namespace Symfony\Component\EventDispatcher\Debug; use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Contracts\Service\ResetInterface; /** + * @deprecated since Symfony 4.1 + * * @author Fabien Potencier */ -interface TraceableEventDispatcherInterface extends EventDispatcherInterface +interface TraceableEventDispatcherInterface extends EventDispatcherInterface, ResetInterface { /** * Gets the called listeners. * + * @param Request|null $request The request to get listeners for + * * @return array An array of called listeners */ - public function getCalledListeners(); + public function getCalledListeners(/* Request $request = null */); /** * Gets the not called listeners. * + * @param Request|null $request The request to get listeners for + * * @return array An array of not called listeners */ - public function getNotCalledListeners(); + public function getNotCalledListeners(/* Request $request = null */); } diff --git a/vendor/symfony/event-dispatcher/Debug/WrappedListener.php b/vendor/symfony/event-dispatcher/Debug/WrappedListener.php index 45208a19..9b910e66 100644 --- a/vendor/symfony/event-dispatcher/Debug/WrappedListener.php +++ b/vendor/symfony/event-dispatcher/Debug/WrappedListener.php @@ -11,46 +11,59 @@ namespace Symfony\Component\EventDispatcher\Debug; -use Symfony\Component\Stopwatch\Stopwatch; +use Psr\EventDispatcher\StoppableEventInterface; use Symfony\Component\EventDispatcher\Event; use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\EventDispatcher\LegacyEventProxy; +use Symfony\Component\Stopwatch\Stopwatch; use Symfony\Component\VarDumper\Caster\ClassStub; -use Symfony\Component\VarDumper\Cloner\VarCloner; +use Symfony\Contracts\EventDispatcher\Event as ContractsEvent; /** * @author Fabien Potencier + * + * @final since Symfony 4.3: the "Event" type-hint on __invoke() will be replaced by "object" in 5.0 */ class WrappedListener { private $listener; + private $optimizedListener; private $name; private $called; private $stoppedPropagation; private $stopwatch; private $dispatcher; private $pretty; - private $data; - - private static $cloner; + private $stub; + private $priority; + private static $hasClassStub; - public function __construct($listener, $name, Stopwatch $stopwatch, EventDispatcherInterface $dispatcher = null) + public function __construct($listener, ?string $name, Stopwatch $stopwatch, EventDispatcherInterface $dispatcher = null) { $this->listener = $listener; - $this->name = $name; + $this->optimizedListener = $listener instanceof \Closure ? $listener : (\is_callable($listener) ? \Closure::fromCallable($listener) : null); $this->stopwatch = $stopwatch; $this->dispatcher = $dispatcher; $this->called = false; $this->stoppedPropagation = false; - if (is_array($listener)) { - $this->name = is_object($listener[0]) ? get_class($listener[0]) : $listener[0]; + if (\is_array($listener)) { + $this->name = \is_object($listener[0]) ? \get_class($listener[0]) : $listener[0]; $this->pretty = $this->name.'::'.$listener[1]; } elseif ($listener instanceof \Closure) { - $this->pretty = $this->name = 'closure'; - } elseif (is_string($listener)) { + $r = new \ReflectionFunction($listener); + if (str_contains($r->name, '{closure}')) { + $this->pretty = $this->name = 'closure'; + } elseif ($class = $r->getClosureScopeClass()) { + $this->name = $class->name; + $this->pretty = $this->name.'::'.$r->name; + } else { + $this->pretty = $this->name = $r->name; + } + } elseif (\is_string($listener)) { $this->pretty = $this->name = $listener; } else { - $this->name = get_class($listener); + $this->name = \get_class($listener); $this->pretty = $this->name.'::__invoke'; } @@ -58,8 +71,8 @@ public function __construct($listener, $name, Stopwatch $stopwatch, EventDispatc $this->name = $name; } - if (null === self::$cloner) { - self::$cloner = class_exists(ClassStub::class) ? new VarCloner() : false; + if (null === self::$hasClassStub) { + self::$hasClassStub = class_exists(ClassStub::class); } } @@ -85,31 +98,38 @@ public function getPretty() public function getInfo($eventName) { - if (null === $this->data) { - $this->data = false !== self::$cloner ? self::$cloner->cloneVar(array(new ClassStub($this->pretty.'()', $this->listener)))->seek(0) : $this->pretty; + if (null === $this->stub) { + $this->stub = self::$hasClassStub ? new ClassStub($this->pretty.'()', $this->listener) : $this->pretty.'()'; } - return array( + return [ 'event' => $eventName, - 'priority' => null !== $this->dispatcher ? $this->dispatcher->getListenerPriority($eventName, $this->listener) : null, + 'priority' => null !== $this->priority ? $this->priority : (null !== $this->dispatcher ? $this->dispatcher->getListenerPriority($eventName, $this->listener) : null), 'pretty' => $this->pretty, - 'data' => $this->data, - ); + 'stub' => $this->stub, + ]; } public function __invoke(Event $event, $eventName, EventDispatcherInterface $dispatcher) { + if ($event instanceof LegacyEventProxy) { + $event = $event->getEvent(); + } + + $dispatcher = $this->dispatcher ?: $dispatcher; + $this->called = true; + $this->priority = $dispatcher->getListenerPriority($eventName, $this->listener); $e = $this->stopwatch->start($this->name, 'event_listener'); - call_user_func($this->listener, $event, $eventName, $this->dispatcher ?: $dispatcher); + ($this->optimizedListener ?? $this->listener)($event, $eventName, $dispatcher); if ($e->isStarted()) { $e->stop(); } - if ($event->isPropagationStopped()) { + if (($event instanceof Event || $event instanceof ContractsEvent || $event instanceof StoppableEventInterface) && $event->isPropagationStopped()) { $this->stoppedPropagation = true; } } diff --git a/vendor/symfony/event-dispatcher/DependencyInjection/AddEventAliasesPass.php b/vendor/symfony/event-dispatcher/DependencyInjection/AddEventAliasesPass.php new file mode 100644 index 00000000..c4ea50f7 --- /dev/null +++ b/vendor/symfony/event-dispatcher/DependencyInjection/AddEventAliasesPass.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher\DependencyInjection; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * This pass allows bundles to extend the list of event aliases. + * + * @author Alexander M. Turek + */ +class AddEventAliasesPass implements CompilerPassInterface +{ + private $eventAliases; + private $eventAliasesParameter; + + public function __construct(array $eventAliases, string $eventAliasesParameter = 'event_dispatcher.event_aliases') + { + $this->eventAliases = $eventAliases; + $this->eventAliasesParameter = $eventAliasesParameter; + } + + public function process(ContainerBuilder $container): void + { + $eventAliases = $container->hasParameter($this->eventAliasesParameter) ? $container->getParameter($this->eventAliasesParameter) : []; + + $container->setParameter( + $this->eventAliasesParameter, + array_merge($eventAliases, $this->eventAliases) + ); + } +} diff --git a/vendor/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php b/vendor/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php index 4636ba3a..1c4e12ec 100644 --- a/vendor/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php +++ b/vendor/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php @@ -11,42 +11,43 @@ namespace Symfony\Component\EventDispatcher\DependencyInjection; -use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\EventDispatcher\Event as LegacyEvent; +use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Contracts\EventDispatcher\Event; /** * Compiler pass to register tagged services for an event dispatcher. */ class RegisterListenersPass implements CompilerPassInterface { - /** - * @var string - */ protected $dispatcherService; - - /** - * @var string - */ protected $listenerTag; - - /** - * @var string - */ protected $subscriberTag; + protected $eventAliasesParameter; + + private $hotPathEvents = []; + private $hotPathTagName; - /** - * Constructor. - * - * @param string $dispatcherService Service name of the event dispatcher in processed container - * @param string $listenerTag Tag name used for listener - * @param string $subscriberTag Tag name used for subscribers - */ - public function __construct($dispatcherService = 'event_dispatcher', $listenerTag = 'kernel.event_listener', $subscriberTag = 'kernel.event_subscriber') + public function __construct(string $dispatcherService = 'event_dispatcher', string $listenerTag = 'kernel.event_listener', string $subscriberTag = 'kernel.event_subscriber', string $eventAliasesParameter = 'event_dispatcher.event_aliases') { $this->dispatcherService = $dispatcherService; $this->listenerTag = $listenerTag; $this->subscriberTag = $subscriberTag; + $this->eventAliasesParameter = $eventAliasesParameter; + } + + public function setHotPathEvents(array $hotPathEvents, $tagName = 'container.hot_path') + { + $this->hotPathEvents = array_flip($hotPathEvents); + $this->hotPathTagName = $tagName; + + return $this; } public function process(ContainerBuilder $container) @@ -55,60 +56,123 @@ public function process(ContainerBuilder $container) return; } - $definition = $container->findDefinition($this->dispatcherService); + $aliases = []; - foreach ($container->findTaggedServiceIds($this->listenerTag) as $id => $events) { - $def = $container->getDefinition($id); - if (!$def->isPublic()) { - throw new InvalidArgumentException(sprintf('The service "%s" must be public as event listeners are lazy-loaded.', $id)); - } + if ($container->hasParameter($this->eventAliasesParameter)) { + $aliases = $container->getParameter($this->eventAliasesParameter); + } - if ($def->isAbstract()) { - throw new InvalidArgumentException(sprintf('The service "%s" must not be abstract as event listeners are lazy-loaded.', $id)); - } + $definition = $container->findDefinition($this->dispatcherService); + foreach ($container->findTaggedServiceIds($this->listenerTag, true) as $id => $events) { foreach ($events as $event) { - $priority = isset($event['priority']) ? $event['priority'] : 0; + $priority = $event['priority'] ?? 0; if (!isset($event['event'])) { - throw new InvalidArgumentException(sprintf('Service "%s" must define the "event" attribute on "%s" tags.', $id, $this->listenerTag)); + if ($container->getDefinition($id)->hasTag($this->subscriberTag)) { + continue; + } + + $event['method'] = $event['method'] ?? '__invoke'; + $event['event'] = $this->getEventFromTypeDeclaration($container, $id, $event['method']); } + $event['event'] = $aliases[$event['event']] ?? $event['event']; + if (!isset($event['method'])) { - $event['method'] = 'on'.preg_replace_callback(array( - '/(?<=\b)[a-z]/i', + $event['method'] = 'on'.preg_replace_callback([ + '/(?<=\b|_)[a-z]/i', '/[^a-z0-9]/i', - ), function ($matches) { return strtoupper($matches[0]); }, $event['event']); + ], function ($matches) { return strtoupper($matches[0]); }, $event['event']); $event['method'] = preg_replace('/[^a-z0-9]/i', '', $event['method']); + + if (null !== ($class = $container->getDefinition($id)->getClass()) && ($r = $container->getReflectionClass($class, false)) && !$r->hasMethod($event['method']) && $r->hasMethod('__invoke')) { + $event['method'] = '__invoke'; + } } - $definition->addMethodCall('addListenerService', array($event['event'], array($id, $event['method']), $priority)); + $definition->addMethodCall('addListener', [$event['event'], [new ServiceClosureArgument(new Reference($id)), $event['method']], $priority]); + + if (isset($this->hotPathEvents[$event['event']])) { + $container->getDefinition($id)->addTag($this->hotPathTagName); + } } } - foreach ($container->findTaggedServiceIds($this->subscriberTag) as $id => $attributes) { + $extractingDispatcher = new ExtractingEventDispatcher(); + + foreach ($container->findTaggedServiceIds($this->subscriberTag, true) as $id => $attributes) { $def = $container->getDefinition($id); - if (!$def->isPublic()) { - throw new InvalidArgumentException(sprintf('The service "%s" must be public as event subscribers are lazy-loaded.', $id)); - } - if ($def->isAbstract()) { - throw new InvalidArgumentException(sprintf('The service "%s" must not be abstract as event subscribers are lazy-loaded.', $id)); + // We must assume that the class value has been correctly filled, even if the service is created by a factory + $class = $def->getClass(); + + if (!$r = $container->getReflectionClass($class)) { + throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id)); + } + if (!$r->isSubclassOf(EventSubscriberInterface::class)) { + throw new InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $id, EventSubscriberInterface::class)); } + $class = $r->name; - // We must assume that the class value has been correctly filled, even if the service is created by a factory - $class = $container->getParameterBag()->resolveValue($def->getClass()); - $interface = 'Symfony\Component\EventDispatcher\EventSubscriberInterface'; + ExtractingEventDispatcher::$aliases = $aliases; + ExtractingEventDispatcher::$subscriber = $class; + $extractingDispatcher->addSubscriber($extractingDispatcher); + foreach ($extractingDispatcher->listeners as $args) { + $args[1] = [new ServiceClosureArgument(new Reference($id)), $args[1]]; + $definition->addMethodCall('addListener', $args); - if (!is_subclass_of($class, $interface)) { - if (!class_exists($class, false)) { - throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id)); + if (isset($this->hotPathEvents[$args[0]])) { + $container->getDefinition($id)->addTag($this->hotPathTagName); } - - throw new InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $id, $interface)); } + $extractingDispatcher->listeners = []; + ExtractingEventDispatcher::$aliases = []; + } + } - $definition->addMethodCall('addSubscriberService', array($id, $class)); + private function getEventFromTypeDeclaration(ContainerBuilder $container, string $id, string $method): string + { + if ( + null === ($class = $container->getDefinition($id)->getClass()) + || !($r = $container->getReflectionClass($class, false)) + || !$r->hasMethod($method) + || 1 > ($m = $r->getMethod($method))->getNumberOfParameters() + || !($type = $m->getParameters()[0]->getType()) instanceof \ReflectionNamedType + || $type->isBuiltin() + || Event::class === ($name = $type->getName()) + || LegacyEvent::class === $name + ) { + throw new InvalidArgumentException(sprintf('Service "%s" must define the "event" attribute on "%s" tags.', $id, $this->listenerTag)); } + + return $name; + } +} + +/** + * @internal + */ +class ExtractingEventDispatcher extends EventDispatcher implements EventSubscriberInterface +{ + public $listeners = []; + + public static $aliases = []; + public static $subscriber; + + public function addListener($eventName, $listener, $priority = 0) + { + $this->listeners[] = [$eventName, $listener[1], $priority]; + } + + public static function getSubscribedEvents(): array + { + $events = []; + + foreach ([self::$subscriber, 'getSubscribedEvents']() as $eventName => $params) { + $events[self::$aliases[$eventName] ?? $eventName] = $params; + } + + return $events; } } diff --git a/vendor/symfony/event-dispatcher/Event.php b/vendor/symfony/event-dispatcher/Event.php index 9c56b2f5..307c4be5 100644 --- a/vendor/symfony/event-dispatcher/Event.php +++ b/vendor/symfony/event-dispatcher/Event.php @@ -12,32 +12,16 @@ namespace Symfony\Component\EventDispatcher; /** - * Event is the base class for classes containing event data. - * - * This class contains no event data. It is used by events that do not pass - * state information to an event handler when an event is raised. - * - * You can call the method stopPropagation() to abort the execution of - * further listeners in your event listener. - * - * @author Guilherme Blanco - * @author Jonathan Wage - * @author Roman Borschel - * @author Bernhard Schussek + * @deprecated since Symfony 4.3, use "Symfony\Contracts\EventDispatcher\Event" instead */ class Event { - /** - * @var bool Whether no further event listeners should be triggered - */ private $propagationStopped = false; /** - * Returns whether further event listeners should be triggered. - * - * @see Event::stopPropagation() - * * @return bool Whether propagation was already stopped for this event + * + * @deprecated since Symfony 4.3, use "Symfony\Contracts\EventDispatcher\Event" instead */ public function isPropagationStopped() { @@ -45,11 +29,7 @@ public function isPropagationStopped() } /** - * Stops the propagation of the event to further event listeners. - * - * If multiple event listeners are connected to the same event, no - * further event listener will be triggered once any trigger calls - * stopPropagation(). + * @deprecated since Symfony 4.3, use "Symfony\Contracts\EventDispatcher\Event" instead */ public function stopPropagation() { diff --git a/vendor/symfony/event-dispatcher/EventDispatcher.php b/vendor/symfony/event-dispatcher/EventDispatcher.php index 01bfc006..4a8f6c6f 100644 --- a/vendor/symfony/event-dispatcher/EventDispatcher.php +++ b/vendor/symfony/event-dispatcher/EventDispatcher.php @@ -11,6 +11,10 @@ namespace Symfony\Component\EventDispatcher; +use Psr\EventDispatcher\StoppableEventInterface; +use Symfony\Component\EventDispatcher\Debug\WrappedListener; +use Symfony\Contracts\EventDispatcher\Event as ContractsEvent; + /** * The EventDispatcherInterface is the central point of Symfony's event listener system. * @@ -24,23 +28,49 @@ * @author Fabien Potencier * @author Jordi Boggiano * @author Jordan Alliot + * @author Nicolas Grekas */ class EventDispatcher implements EventDispatcherInterface { - private $listeners = array(); - private $sorted = array(); + private $listeners = []; + private $sorted = []; + private $optimized; + + public function __construct() + { + if (__CLASS__ === static::class) { + $this->optimized = []; + } + } /** * {@inheritdoc} + * + * @param string|null $eventName */ - public function dispatch($eventName, Event $event = null) + public function dispatch($event/*, string $eventName = null*/) { - if (null === $event) { - $event = new Event(); + $eventName = 1 < \func_num_args() ? func_get_arg(1) : null; + + if (\is_object($event)) { + $eventName = $eventName ?? \get_class($event); + } elseif (\is_string($event) && (null === $eventName || $eventName instanceof ContractsEvent || $eventName instanceof Event)) { + @trigger_error(sprintf('Calling the "%s::dispatch()" method with the event name as the first argument is deprecated since Symfony 4.3, pass it as the second argument and provide the event object as the first argument instead.', EventDispatcherInterface::class), \E_USER_DEPRECATED); + $swap = $event; + $event = $eventName ?? new Event(); + $eventName = $swap; + } else { + throw new \TypeError(sprintf('Argument 1 passed to "%s::dispatch()" must be an object, "%s" given.', EventDispatcherInterface::class, \is_object($event) ? \get_class($event) : \gettype($event))); } - if ($listeners = $this->getListeners($eventName)) { - $this->doDispatch($listeners, $eventName, $event); + if (null !== $this->optimized && null !== $eventName) { + $listeners = $this->optimized[$eventName] ?? (empty($this->listeners[$eventName]) ? [] : $this->optimizeListeners($eventName)); + } else { + $listeners = $this->getListeners($eventName); + } + + if ($listeners) { + $this->callListeners($listeners, $eventName, $event); } return $event; @@ -52,8 +82,8 @@ public function dispatch($eventName, Event $event = null) public function getListeners($eventName = null) { if (null !== $eventName) { - if (!isset($this->listeners[$eventName])) { - return array(); + if (empty($this->listeners[$eventName])) { + return []; } if (!isset($this->sorted[$eventName])) { @@ -77,15 +107,28 @@ public function getListeners($eventName = null) */ public function getListenerPriority($eventName, $listener) { - if (!isset($this->listeners[$eventName])) { - return; + if (empty($this->listeners[$eventName])) { + return null; } - foreach ($this->listeners[$eventName] as $priority => $listeners) { - if (false !== in_array($listener, $listeners, true)) { - return $priority; + if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure && 2 >= \count($listener)) { + $listener[0] = $listener[0](); + $listener[1] = $listener[1] ?? '__invoke'; + } + + foreach ($this->listeners[$eventName] as $priority => &$listeners) { + foreach ($listeners as &$v) { + if ($v !== $listener && \is_array($v) && isset($v[0]) && $v[0] instanceof \Closure && 2 >= \count($v)) { + $v[0] = $v[0](); + $v[1] = $v[1] ?? '__invoke'; + } + if ($v === $listener || ($listener instanceof \Closure && $v == $listener)) { + return $priority; + } } } + + return null; } /** @@ -93,7 +136,17 @@ public function getListenerPriority($eventName, $listener) */ public function hasListeners($eventName = null) { - return (bool) $this->getListeners($eventName); + if (null !== $eventName) { + return !empty($this->listeners[$eventName]); + } + + foreach ($this->listeners as $eventListeners) { + if ($eventListeners) { + return true; + } + } + + return false; } /** @@ -102,7 +155,7 @@ public function hasListeners($eventName = null) public function addListener($eventName, $listener, $priority = 0) { $this->listeners[$eventName][$priority][] = $listener; - unset($this->sorted[$eventName]); + unset($this->sorted[$eventName], $this->optimized[$eventName]); } /** @@ -110,13 +163,28 @@ public function addListener($eventName, $listener, $priority = 0) */ public function removeListener($eventName, $listener) { - if (!isset($this->listeners[$eventName])) { + if (empty($this->listeners[$eventName])) { return; } - foreach ($this->listeners[$eventName] as $priority => $listeners) { - if (false !== ($key = array_search($listener, $listeners, true))) { - unset($this->listeners[$eventName][$priority][$key], $this->sorted[$eventName]); + if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure && 2 >= \count($listener)) { + $listener[0] = $listener[0](); + $listener[1] = $listener[1] ?? '__invoke'; + } + + foreach ($this->listeners[$eventName] as $priority => &$listeners) { + foreach ($listeners as $k => &$v) { + if ($v !== $listener && \is_array($v) && isset($v[0]) && $v[0] instanceof \Closure && 2 >= \count($v)) { + $v[0] = $v[0](); + $v[1] = $v[1] ?? '__invoke'; + } + if ($v === $listener || ($listener instanceof \Closure && $v == $listener)) { + unset($listeners[$k], $this->sorted[$eventName], $this->optimized[$eventName]); + } + } + + if (!$listeners) { + unset($this->listeners[$eventName][$priority]); } } } @@ -127,13 +195,13 @@ public function removeListener($eventName, $listener) public function addSubscriber(EventSubscriberInterface $subscriber) { foreach ($subscriber->getSubscribedEvents() as $eventName => $params) { - if (is_string($params)) { - $this->addListener($eventName, array($subscriber, $params)); - } elseif (is_string($params[0])) { - $this->addListener($eventName, array($subscriber, $params[0]), isset($params[1]) ? $params[1] : 0); + if (\is_string($params)) { + $this->addListener($eventName, [$subscriber, $params]); + } elseif (\is_string($params[0])) { + $this->addListener($eventName, [$subscriber, $params[0]], $params[1] ?? 0); } else { foreach ($params as $listener) { - $this->addListener($eventName, array($subscriber, $listener[0]), isset($listener[1]) ? $listener[1] : 0); + $this->addListener($eventName, [$subscriber, $listener[0]], $listener[1] ?? 0); } } } @@ -145,12 +213,12 @@ public function addSubscriber(EventSubscriberInterface $subscriber) public function removeSubscriber(EventSubscriberInterface $subscriber) { foreach ($subscriber->getSubscribedEvents() as $eventName => $params) { - if (is_array($params) && is_array($params[0])) { + if (\is_array($params) && \is_array($params[0])) { foreach ($params as $listener) { - $this->removeListener($eventName, array($subscriber, $listener[0])); + $this->removeListener($eventName, [$subscriber, $listener[0]]); } } else { - $this->removeListener($eventName, array($subscriber, is_string($params) ? $params : $params[0])); + $this->removeListener($eventName, [$subscriber, \is_string($params) ? $params : $params[0]]); } } } @@ -163,7 +231,29 @@ public function removeSubscriber(EventSubscriberInterface $subscriber) * * @param callable[] $listeners The event listeners * @param string $eventName The name of the event to dispatch - * @param Event $event The event object to pass to the event handlers/listeners + * @param object $event The event object to pass to the event handlers/listeners + */ + protected function callListeners(iterable $listeners, string $eventName, $event) + { + if ($event instanceof Event) { + $this->doDispatch($listeners, $eventName, $event); + + return; + } + + $stoppable = $event instanceof ContractsEvent || $event instanceof StoppableEventInterface; + + foreach ($listeners as $listener) { + if ($stoppable && $event->isPropagationStopped()) { + break; + } + // @deprecated: the ternary operator is part of a BC layer and should be removed in 5.0 + $listener($listener instanceof WrappedListener ? new LegacyEventProxy($event) : $event, $eventName, $this); + } + } + + /** + * @deprecated since Symfony 4.3, use callListeners() instead */ protected function doDispatch($listeners, $eventName, Event $event) { @@ -171,18 +261,54 @@ protected function doDispatch($listeners, $eventName, Event $event) if ($event->isPropagationStopped()) { break; } - call_user_func($listener, $event, $eventName, $this); + $listener($event, $eventName, $this); } } /** * Sorts the internal list of listeners for the given event by priority. - * - * @param string $eventName The name of the event */ - private function sortListeners($eventName) + private function sortListeners(string $eventName) { krsort($this->listeners[$eventName]); - $this->sorted[$eventName] = call_user_func_array('array_merge', $this->listeners[$eventName]); + $this->sorted[$eventName] = []; + + foreach ($this->listeners[$eventName] as &$listeners) { + foreach ($listeners as $k => &$listener) { + if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure && 2 >= \count($listener)) { + $listener[0] = $listener[0](); + $listener[1] = $listener[1] ?? '__invoke'; + } + $this->sorted[$eventName][] = $listener; + } + } + } + + /** + * Optimizes the internal list of listeners for the given event by priority. + */ + private function optimizeListeners(string $eventName): array + { + krsort($this->listeners[$eventName]); + $this->optimized[$eventName] = []; + + foreach ($this->listeners[$eventName] as &$listeners) { + foreach ($listeners as &$listener) { + $closure = &$this->optimized[$eventName][]; + if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure && 2 >= \count($listener)) { + $closure = static function (...$args) use (&$listener, &$closure) { + if ($listener[0] instanceof \Closure) { + $listener[0] = $listener[0](); + $listener[1] = $listener[1] ?? '__invoke'; + } + ($closure = \Closure::fromCallable($listener))(...$args); + }; + } else { + $closure = $listener instanceof \Closure || $listener instanceof WrappedListener ? $listener : \Closure::fromCallable($listener); + } + } + } + + return $this->optimized[$eventName]; } } diff --git a/vendor/symfony/event-dispatcher/EventDispatcherInterface.php b/vendor/symfony/event-dispatcher/EventDispatcherInterface.php index 08ebf340..ceaa62ae 100644 --- a/vendor/symfony/event-dispatcher/EventDispatcherInterface.php +++ b/vendor/symfony/event-dispatcher/EventDispatcherInterface.php @@ -11,6 +11,8 @@ namespace Symfony\Component\EventDispatcher; +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface as ContractsEventDispatcherInterface; + /** * The EventDispatcherInterface is the central point of Symfony's event listener system. * Listeners are registered on the manager and events are dispatched through the @@ -18,21 +20,8 @@ * * @author Bernhard Schussek */ -interface EventDispatcherInterface +interface EventDispatcherInterface extends ContractsEventDispatcherInterface { - /** - * Dispatches an event to all registered listeners. - * - * @param string $eventName The name of the event to dispatch. The name of - * the event is the name of the method that is - * invoked on listeners. - * @param Event $event The event to pass to the event handlers/listeners - * If not supplied, an empty Event instance is created. - * - * @return Event - */ - public function dispatch($eventName, Event $event = null); - /** * Adds an event listener that listens on the specified events. * @@ -46,10 +35,8 @@ public function addListener($eventName, $listener, $priority = 0); /** * Adds an event subscriber. * - * The subscriber is asked for all the events he is + * The subscriber is asked for all the events it is * interested in and added as a listener for these events. - * - * @param EventSubscriberInterface $subscriber The subscriber */ public function addSubscriber(EventSubscriberInterface $subscriber); @@ -61,17 +48,12 @@ public function addSubscriber(EventSubscriberInterface $subscriber); */ public function removeListener($eventName, $listener); - /** - * Removes an event subscriber. - * - * @param EventSubscriberInterface $subscriber The subscriber - */ public function removeSubscriber(EventSubscriberInterface $subscriber); /** * Gets the listeners of a specific event or all listeners sorted by descending priority. * - * @param string $eventName The name of the event + * @param string|null $eventName The name of the event * * @return array The event listeners for the specified event, or all event listeners by event name */ @@ -92,7 +74,7 @@ public function getListenerPriority($eventName, $listener); /** * Checks whether an event has any registered listeners. * - * @param string $eventName The name of the event + * @param string|null $eventName The name of the event * * @return bool true if the specified event has any listeners, false otherwise */ diff --git a/vendor/symfony/event-dispatcher/EventSubscriberInterface.php b/vendor/symfony/event-dispatcher/EventSubscriberInterface.php index 8af77891..a0fc96df 100644 --- a/vendor/symfony/event-dispatcher/EventSubscriberInterface.php +++ b/vendor/symfony/event-dispatcher/EventSubscriberInterface.php @@ -12,7 +12,7 @@ namespace Symfony\Component\EventDispatcher; /** - * An EventSubscriber knows himself what events he is interested in. + * An EventSubscriber knows itself what events it is interested in. * If an EventSubscriber is added to an EventDispatcherInterface, the manager invokes * {@link getSubscribedEvents} and registers the subscriber as a listener for all * returned events. @@ -36,11 +36,14 @@ interface EventSubscriberInterface * * For instance: * - * * array('eventName' => 'methodName') - * * array('eventName' => array('methodName', $priority)) - * * array('eventName' => array(array('methodName1', $priority), array('methodName2'))) + * * ['eventName' => 'methodName'] + * * ['eventName' => ['methodName', $priority]] + * * ['eventName' => [['methodName1', $priority], ['methodName2']]] * - * @return array The event names to listen to + * The code must not depend on runtime state as it will only be called at compile time. + * All logic depending on runtime state must be put into the individual methods handling the events. + * + * @return array The event names to listen to */ public static function getSubscribedEvents(); } diff --git a/vendor/symfony/event-dispatcher/GenericEvent.php b/vendor/symfony/event-dispatcher/GenericEvent.php index e8e4cc05..23333bc2 100644 --- a/vendor/symfony/event-dispatcher/GenericEvent.php +++ b/vendor/symfony/event-dispatcher/GenericEvent.php @@ -20,27 +20,16 @@ */ class GenericEvent extends Event implements \ArrayAccess, \IteratorAggregate { - /** - * Event subject. - * - * @var mixed usually object or callable - */ protected $subject; - - /** - * Array of arguments. - * - * @var array - */ protected $arguments; /** * Encapsulate an event with $subject and $args. * - * @param mixed $subject The subject of the event, usually an object + * @param mixed $subject The subject of the event, usually an object or a callable * @param array $arguments Arguments to store in the event */ - public function __construct($subject = null, array $arguments = array()) + public function __construct($subject = null, array $arguments = []) { $this->subject = $subject; $this->arguments = $arguments; @@ -49,7 +38,7 @@ public function __construct($subject = null, array $arguments = array()) /** * Getter for subject property. * - * @return mixed $subject The observer subject + * @return mixed The observer subject */ public function getSubject() { @@ -63,7 +52,7 @@ public function getSubject() * * @return mixed Contents of array key * - * @throws \InvalidArgumentException If key is not found. + * @throws \InvalidArgumentException if key is not found */ public function getArgument($key) { @@ -106,7 +95,7 @@ public function getArguments() * * @return $this */ - public function setArguments(array $args = array()) + public function setArguments(array $args = []) { $this->arguments = $args; @@ -122,7 +111,7 @@ public function setArguments(array $args = array()) */ public function hasArgument($key) { - return array_key_exists($key, $this->arguments); + return \array_key_exists($key, $this->arguments); } /** @@ -132,8 +121,9 @@ public function hasArgument($key) * * @return mixed * - * @throws \InvalidArgumentException If key does not exist in $this->args. + * @throws \InvalidArgumentException if key does not exist in $this->args */ + #[\ReturnTypeWillChange] public function offsetGet($key) { return $this->getArgument($key); @@ -144,7 +134,10 @@ public function offsetGet($key) * * @param string $key Array key to set * @param mixed $value Value + * + * @return void */ + #[\ReturnTypeWillChange] public function offsetSet($key, $value) { $this->setArgument($key, $value); @@ -154,7 +147,10 @@ public function offsetSet($key, $value) * ArrayAccess for unset argument. * * @param string $key Array key + * + * @return void */ + #[\ReturnTypeWillChange] public function offsetUnset($key) { if ($this->hasArgument($key)) { @@ -169,6 +165,7 @@ public function offsetUnset($key) * * @return bool */ + #[\ReturnTypeWillChange] public function offsetExists($key) { return $this->hasArgument($key); @@ -179,6 +176,7 @@ public function offsetExists($key) * * @return \ArrayIterator */ + #[\ReturnTypeWillChange] public function getIterator() { return new \ArrayIterator($this->arguments); diff --git a/vendor/symfony/event-dispatcher/ImmutableEventDispatcher.php b/vendor/symfony/event-dispatcher/ImmutableEventDispatcher.php index 7f2be8d3..75a7d731 100644 --- a/vendor/symfony/event-dispatcher/ImmutableEventDispatcher.php +++ b/vendor/symfony/event-dispatcher/ImmutableEventDispatcher.php @@ -18,29 +18,30 @@ */ class ImmutableEventDispatcher implements EventDispatcherInterface { - /** - * The proxied dispatcher. - * - * @var EventDispatcherInterface - */ private $dispatcher; - /** - * Creates an unmodifiable proxy for an event dispatcher. - * - * @param EventDispatcherInterface $dispatcher The proxied event dispatcher - */ public function __construct(EventDispatcherInterface $dispatcher) { - $this->dispatcher = $dispatcher; + $this->dispatcher = LegacyEventDispatcherProxy::decorate($dispatcher); } /** * {@inheritdoc} + * + * @param string|null $eventName */ - public function dispatch($eventName, Event $event = null) + public function dispatch($event/*, string $eventName = null*/) { - return $this->dispatcher->dispatch($eventName, $event); + $eventName = 1 < \func_num_args() ? func_get_arg(1) : null; + + if (is_scalar($event)) { + // deprecated + $swap = $event; + $event = $eventName ?? new Event(); + $eventName = $swap; + } + + return $this->dispatcher->dispatch($event, $eventName); } /** diff --git a/vendor/symfony/event-dispatcher/LICENSE b/vendor/symfony/event-dispatcher/LICENSE index 17d16a13..88bf75bb 100644 --- a/vendor/symfony/event-dispatcher/LICENSE +++ b/vendor/symfony/event-dispatcher/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2017 Fabien Potencier +Copyright (c) 2004-2022 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 diff --git a/vendor/symfony/event-dispatcher/LegacyEventDispatcherProxy.php b/vendor/symfony/event-dispatcher/LegacyEventDispatcherProxy.php new file mode 100644 index 00000000..8ee6cba1 --- /dev/null +++ b/vendor/symfony/event-dispatcher/LegacyEventDispatcherProxy.php @@ -0,0 +1,147 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher; + +use Psr\EventDispatcher\StoppableEventInterface; +use Symfony\Contracts\EventDispatcher\Event as ContractsEvent; +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface as ContractsEventDispatcherInterface; + +/** + * A helper class to provide BC/FC with the legacy signature of EventDispatcherInterface::dispatch(). + * + * This class should be deprecated in Symfony 5.1 + * + * @author Nicolas Grekas + */ +final class LegacyEventDispatcherProxy implements EventDispatcherInterface +{ + private $dispatcher; + + public static function decorate(?ContractsEventDispatcherInterface $dispatcher): ?ContractsEventDispatcherInterface + { + if (null === $dispatcher) { + return null; + } + $r = new \ReflectionMethod($dispatcher, 'dispatch'); + $param2 = $r->getParameters()[1] ?? null; + + if (!$param2 || !$param2->hasType() || $param2->getType()->isBuiltin()) { + return $dispatcher; + } + + @trigger_error(sprintf('The signature of the "%s::dispatch()" method should be updated to "dispatch($event, string $eventName = null)", not doing so is deprecated since Symfony 4.3.', $r->class), \E_USER_DEPRECATED); + + $self = new self(); + $self->dispatcher = $dispatcher; + + return $self; + } + + /** + * {@inheritdoc} + * + * @param string|null $eventName + * + * @return object + */ + public function dispatch($event/*, string $eventName = null*/) + { + $eventName = 1 < \func_num_args() ? func_get_arg(1) : null; + + if (\is_object($event)) { + $eventName = $eventName ?? \get_class($event); + } elseif (\is_string($event) && (null === $eventName || $eventName instanceof ContractsEvent || $eventName instanceof Event)) { + @trigger_error(sprintf('Calling the "%s::dispatch()" method with the event name as the first argument is deprecated since Symfony 4.3, pass it as the second argument and provide the event object as the first argument instead.', ContractsEventDispatcherInterface::class), \E_USER_DEPRECATED); + $swap = $event; + $event = $eventName ?? new Event(); + $eventName = $swap; + } else { + throw new \TypeError(sprintf('Argument 1 passed to "%s::dispatch()" must be an object, "%s" given.', ContractsEventDispatcherInterface::class, \is_object($event) ? \get_class($event) : \gettype($event))); + } + + $listeners = $this->getListeners($eventName); + $stoppable = $event instanceof Event || $event instanceof ContractsEvent || $event instanceof StoppableEventInterface; + + foreach ($listeners as $listener) { + if ($stoppable && $event->isPropagationStopped()) { + break; + } + $listener($event, $eventName, $this); + } + + return $event; + } + + /** + * {@inheritdoc} + */ + public function addListener($eventName, $listener, $priority = 0) + { + return $this->dispatcher->addListener($eventName, $listener, $priority); + } + + /** + * {@inheritdoc} + */ + public function addSubscriber(EventSubscriberInterface $subscriber) + { + return $this->dispatcher->addSubscriber($subscriber); + } + + /** + * {@inheritdoc} + */ + public function removeListener($eventName, $listener) + { + return $this->dispatcher->removeListener($eventName, $listener); + } + + /** + * {@inheritdoc} + */ + public function removeSubscriber(EventSubscriberInterface $subscriber) + { + return $this->dispatcher->removeSubscriber($subscriber); + } + + /** + * {@inheritdoc} + */ + public function getListeners($eventName = null): array + { + return $this->dispatcher->getListeners($eventName); + } + + /** + * {@inheritdoc} + */ + public function getListenerPriority($eventName, $listener): ?int + { + return $this->dispatcher->getListenerPriority($eventName, $listener); + } + + /** + * {@inheritdoc} + */ + public function hasListeners($eventName = null): bool + { + return $this->dispatcher->hasListeners($eventName); + } + + /** + * Proxies all method calls to the original event dispatcher. + */ + public function __call($method, $arguments) + { + return $this->dispatcher->{$method}(...$arguments); + } +} diff --git a/vendor/symfony/event-dispatcher/LegacyEventProxy.php b/vendor/symfony/event-dispatcher/LegacyEventProxy.php new file mode 100644 index 00000000..45ee251d --- /dev/null +++ b/vendor/symfony/event-dispatcher/LegacyEventProxy.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher; + +use Psr\EventDispatcher\StoppableEventInterface; +use Symfony\Contracts\EventDispatcher\Event as ContractsEvent; + +/** + * @internal to be removed in 5.0. + */ +final class LegacyEventProxy extends Event +{ + private $event; + + /** + * @param object $event + */ + public function __construct($event) + { + $this->event = $event; + } + + /** + * @return object $event + */ + public function getEvent() + { + return $this->event; + } + + public function isPropagationStopped(): bool + { + if (!$this->event instanceof ContractsEvent && !$this->event instanceof StoppableEventInterface) { + return false; + } + + return $this->event->isPropagationStopped(); + } + + public function stopPropagation() + { + if (!$this->event instanceof ContractsEvent) { + return; + } + + $this->event->stopPropagation(); + } + + public function __call($name, $args) + { + return $this->event->{$name}(...$args); + } +} diff --git a/vendor/symfony/options-resolver/Debug/OptionsResolverIntrospector.php b/vendor/symfony/options-resolver/Debug/OptionsResolverIntrospector.php index bacb70b0..9ce52633 100644 --- a/vendor/symfony/options-resolver/Debug/OptionsResolverIntrospector.php +++ b/vendor/symfony/options-resolver/Debug/OptionsResolverIntrospector.php @@ -41,62 +41,68 @@ public function __construct(OptionsResolver $optionsResolver) } /** - * @param string $option - * * @return mixed * * @throws NoConfigurationException on no configured value */ - public function getDefault($option) + public function getDefault(string $option) { - return \call_user_func($this->get, 'defaults', $option, sprintf('No default value was set for the "%s" option.', $option)); + return ($this->get)('defaults', $option, sprintf('No default value was set for the "%s" option.', $option)); } /** - * @param string $option - * * @return \Closure[] * * @throws NoConfigurationException on no configured closures */ - public function getLazyClosures($option) + public function getLazyClosures(string $option): array { - return \call_user_func($this->get, 'lazy', $option, sprintf('No lazy closures were set for the "%s" option.', $option)); + return ($this->get)('lazy', $option, sprintf('No lazy closures were set for the "%s" option.', $option)); } /** - * @param string $option - * * @return string[] * * @throws NoConfigurationException on no configured types */ - public function getAllowedTypes($option) + public function getAllowedTypes(string $option): array { - return \call_user_func($this->get, 'allowedTypes', $option, sprintf('No allowed types were set for the "%s" option.', $option)); + return ($this->get)('allowedTypes', $option, sprintf('No allowed types were set for the "%s" option.', $option)); } /** - * @param string $option - * * @return mixed[] * * @throws NoConfigurationException on no configured values */ - public function getAllowedValues($option) + public function getAllowedValues(string $option): array { - return \call_user_func($this->get, 'allowedValues', $option, sprintf('No allowed values were set for the "%s" option.', $option)); + return ($this->get)('allowedValues', $option, sprintf('No allowed values were set for the "%s" option.', $option)); } /** - * @param string $option - * - * @return \Closure - * * @throws NoConfigurationException on no configured normalizer */ - public function getNormalizer($option) + public function getNormalizer(string $option): \Closure + { + return current($this->getNormalizers($option)); + } + + /** + * @throws NoConfigurationException when no normalizer is configured + */ + public function getNormalizers(string $option): array + { + return ($this->get)('normalizers', $option, sprintf('No normalizer was set for the "%s" option.', $option)); + } + + /** + * @return string|\Closure + * + * @throws NoConfigurationException on no configured deprecation + */ + public function getDeprecationMessage(string $option) { - return \call_user_func($this->get, 'normalizers', $option, sprintf('No normalizer was set for the "%s" option.', $option)); + return ($this->get)('deprecated', $option, sprintf('No deprecation was set for the "%s" option.', $option)); } } diff --git a/vendor/symfony/options-resolver/Exception/ExceptionInterface.php b/vendor/symfony/options-resolver/Exception/ExceptionInterface.php index b62bb51d..ea99d050 100644 --- a/vendor/symfony/options-resolver/Exception/ExceptionInterface.php +++ b/vendor/symfony/options-resolver/Exception/ExceptionInterface.php @@ -16,6 +16,6 @@ * * @author Bernhard Schussek */ -interface ExceptionInterface +interface ExceptionInterface extends \Throwable { } diff --git a/vendor/symfony/options-resolver/LICENSE b/vendor/symfony/options-resolver/LICENSE index 9e936ec0..88bf75bb 100644 --- a/vendor/symfony/options-resolver/LICENSE +++ b/vendor/symfony/options-resolver/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2020 Fabien Potencier +Copyright (c) 2004-2022 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 diff --git a/vendor/symfony/options-resolver/OptionsResolver.php b/vendor/symfony/options-resolver/OptionsResolver.php index cf8c3664..fc1cf854 100644 --- a/vendor/symfony/options-resolver/OptionsResolver.php +++ b/vendor/symfony/options-resolver/OptionsResolver.php @@ -12,6 +12,7 @@ namespace Symfony\Component\OptionsResolver; use Symfony\Component\OptionsResolver\Exception\AccessException; +use Symfony\Component\OptionsResolver\Exception\InvalidArgumentException; use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; use Symfony\Component\OptionsResolver\Exception\MissingOptionsException; use Symfony\Component\OptionsResolver\Exception\NoSuchOptionException; @@ -36,6 +37,13 @@ class OptionsResolver implements Options */ private $defaults = []; + /** + * A list of closure for nested options. + * + * @var \Closure[][] + */ + private $nested = []; + /** * The names of required options. */ @@ -49,7 +57,7 @@ class OptionsResolver implements Options /** * A list of normalizer closures. * - * @var \Closure[] + * @var \Closure[][] */ private $normalizers = []; @@ -75,6 +83,16 @@ class OptionsResolver implements Options */ private $calling = []; + /** + * A list of deprecated options. + */ + private $deprecated = []; + + /** + * The list of options provided by the user. + */ + private $given = []; + /** * Whether the instance is locked for reading. * @@ -85,7 +103,9 @@ class OptionsResolver implements Options */ private $locked = false; - private static $typeAliases = [ + private $parentsOptions = []; + + private const TYPE_ALIASES = [ 'boolean' => 'bool', 'integer' => 'int', 'double' => 'float', @@ -124,6 +144,20 @@ class OptionsResolver implements Options * is spread across different locations of your code, such as base and * sub-classes. * + * If you want to define nested options, you can pass a closure with the + * following signature: + * + * $options->setDefault('database', function (OptionsResolver $resolver) { + * $resolver->setDefined(['dbname', 'host', 'port', 'user', 'pass']); + * } + * + * To get access to the parent options, add a second argument to the closure's + * signature: + * + * function (OptionsResolver $resolver, Options $parent) { + * // 'default' === $parent['connection'] + * } + * * @param string $option The name of the option * @param mixed $value The default value of the option * @@ -161,15 +195,27 @@ public function setDefault($option, $value) $this->lazy[$option][] = $value; $this->defined[$option] = true; - // Make sure the option is processed - unset($this->resolved[$option]); + // Make sure the option is processed and is not nested anymore + unset($this->resolved[$option], $this->nested[$option]); + + return $this; + } + + if (isset($params[0]) && null !== ($type = $params[0]->getType()) && self::class === $type->getName() && (!isset($params[1]) || (($type = $params[1]->getType()) instanceof \ReflectionNamedType && Options::class === $type->getName()))) { + // Store closure for later evaluation + $this->nested[$option][] = $value; + $this->defaults[$option] = []; + $this->defined[$option] = true; + + // Make sure the option is processed and is not lazy anymore + unset($this->resolved[$option], $this->lazy[$option]); return $this; } } - // This option is not lazy anymore - unset($this->lazy[$option]); + // This option is not lazy nor nested anymore + unset($this->lazy[$option], $this->nested[$option]); // Yet undefined options can be marked as resolved, because we only need // to resolve options with lazy closures, normalizers or validation @@ -186,10 +232,6 @@ public function setDefault($option, $value) } /** - * Sets a list of default values. - * - * @param array $defaults The default values to set - * * @return $this * * @throws AccessException If called from a lazy option or normalizer @@ -348,6 +390,62 @@ public function getDefinedOptions() return array_keys($this->defined); } + public function isNested(string $option): bool + { + return isset($this->nested[$option]); + } + + /** + * Deprecates an option, allowed types or values. + * + * Instead of passing the message, you may also pass a closure with the + * following signature: + * + * function (Options $options, $value): string { + * // ... + * } + * + * The closure receives the value as argument and should return a string. + * Return an empty string to ignore the option deprecation. + * + * The closure is invoked when {@link resolve()} is called. The parameter + * passed to the closure is the value of the option after validating it + * and before normalizing it. + * + * @param string|\Closure $deprecationMessage + */ + public function setDeprecated(string $option, $deprecationMessage = 'The option "%name%" is deprecated.'): self + { + if ($this->locked) { + throw new AccessException('Options cannot be deprecated from a lazy option or normalizer.'); + } + + if (!isset($this->defined[$option])) { + throw new UndefinedOptionsException(sprintf('The option "%s" does not exist, defined options are: "%s".', $this->formatOptions([$option]), implode('", "', array_keys($this->defined)))); + } + + if (!\is_string($deprecationMessage) && !$deprecationMessage instanceof \Closure) { + throw new InvalidArgumentException(sprintf('Invalid type for deprecation message argument, expected string or \Closure, but got "%s".', \gettype($deprecationMessage))); + } + + // ignore if empty string + if ('' === $deprecationMessage) { + return $this; + } + + $this->deprecated[$option] = $deprecationMessage; + + // Make sure the option is processed + unset($this->resolved[$option]); + + return $this; + } + + public function isDeprecated(string $option): bool + { + return isset($this->deprecated[$option]); + } + /** * Sets the normalizer for an option. * @@ -366,8 +464,7 @@ public function getDefinedOptions() * * The resolved option value is set to the return value of the closure. * - * @param string $option The option name - * @param \Closure $normalizer The normalizer + * @param string $option The option name * * @return $this * @@ -381,10 +478,56 @@ public function setNormalizer($option, \Closure $normalizer) } if (!isset($this->defined[$option])) { - throw new UndefinedOptionsException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $option, implode('", "', array_keys($this->defined)))); + throw new UndefinedOptionsException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $this->formatOptions([$option]), implode('", "', array_keys($this->defined)))); } - $this->normalizers[$option] = $normalizer; + $this->normalizers[$option] = [$normalizer]; + + // Make sure the option is processed + unset($this->resolved[$option]); + + return $this; + } + + /** + * Adds a normalizer for an option. + * + * The normalizer should be a closure with the following signature: + * + * function (Options $options, $value): mixed { + * // ... + * } + * + * The closure is invoked when {@link resolve()} is called. The closure + * has access to the resolved values of other options through the passed + * {@link Options} instance. + * + * The second parameter passed to the closure is the value of + * the option. + * + * The resolved option value is set to the return value of the closure. + * + * @return $this + * + * @throws UndefinedOptionsException If the option is undefined + * @throws AccessException If called from a lazy option or normalizer + */ + public function addNormalizer(string $option, \Closure $normalizer, bool $forcePrepend = false): self + { + if ($this->locked) { + throw new AccessException('Normalizers cannot be set from a lazy option or normalizer.'); + } + + if (!isset($this->defined[$option])) { + throw new UndefinedOptionsException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $this->formatOptions([$option]), implode('", "', array_keys($this->defined)))); + } + + if ($forcePrepend) { + $this->normalizers[$option] = $this->normalizers[$option] ?? []; + array_unshift($this->normalizers[$option], $normalizer); + } else { + $this->normalizers[$option][] = $normalizer; + } // Make sure the option is processed unset($this->resolved[$option]); @@ -420,7 +563,7 @@ public function setAllowedValues($option, $allowedValues) } if (!isset($this->defined[$option])) { - throw new UndefinedOptionsException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $option, implode('", "', array_keys($this->defined)))); + throw new UndefinedOptionsException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $this->formatOptions([$option]), implode('", "', array_keys($this->defined)))); } $this->allowedValues[$option] = \is_array($allowedValues) ? $allowedValues : [$allowedValues]; @@ -461,7 +604,7 @@ public function addAllowedValues($option, $allowedValues) } if (!isset($this->defined[$option])) { - throw new UndefinedOptionsException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $option, implode('", "', array_keys($this->defined)))); + throw new UndefinedOptionsException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $this->formatOptions([$option]), implode('", "', array_keys($this->defined)))); } if (!\is_array($allowedValues)) { @@ -502,7 +645,7 @@ public function setAllowedTypes($option, $allowedTypes) } if (!isset($this->defined[$option])) { - throw new UndefinedOptionsException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $option, implode('", "', array_keys($this->defined)))); + throw new UndefinedOptionsException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $this->formatOptions([$option]), implode('", "', array_keys($this->defined)))); } $this->allowedTypes[$option] = (array) $allowedTypes; @@ -537,7 +680,7 @@ public function addAllowedTypes($option, $allowedTypes) } if (!isset($this->defined[$option])) { - throw new UndefinedOptionsException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $option, implode('", "', array_keys($this->defined)))); + throw new UndefinedOptionsException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $this->formatOptions([$option]), implode('", "', array_keys($this->defined)))); } if (!isset($this->allowedTypes[$option])) { @@ -592,12 +735,14 @@ public function clear() $this->defined = []; $this->defaults = []; + $this->nested = []; $this->required = []; $this->resolved = []; $this->lazy = []; $this->normalizers = []; $this->allowedTypes = []; $this->allowedValues = []; + $this->deprecated = []; return $this; } @@ -613,8 +758,6 @@ public function clear() * - Options have invalid types; * - Options have invalid values. * - * @param array $options A map of option names to values - * * @return array The merged and validated options * * @throws UndefinedOptionsException If an option name is undefined @@ -642,11 +785,12 @@ public function resolve(array $options = []) ksort($clone->defined); ksort($diff); - throw new UndefinedOptionsException(sprintf((\count($diff) > 1 ? 'The options "%s" do not exist.' : 'The option "%s" does not exist.').' Defined options are: "%s".', implode('", "', array_keys($diff)), implode('", "', array_keys($clone->defined)))); + throw new UndefinedOptionsException(sprintf((\count($diff) > 1 ? 'The options "%s" do not exist.' : 'The option "%s" does not exist.').' Defined options are: "%s".', $this->formatOptions(array_keys($diff)), implode('", "', array_keys($clone->defined)))); } // Override options set by the user foreach ($options as $option => $value) { + $clone->given[$option] = true; $clone->defaults[$option] = $value; unset($clone->resolved[$option], $clone->lazy[$option]); } @@ -657,7 +801,7 @@ public function resolve(array $options = []) if (\count($diff) > 0) { ksort($diff); - throw new MissingOptionsException(sprintf(\count($diff) > 1 ? 'The required options "%s" are missing.' : 'The required option "%s" is missing.', implode('", "', array_keys($diff)))); + throw new MissingOptionsException(sprintf(\count($diff) > 1 ? 'The required options "%s" are missing.' : 'The required option "%s" is missing.', $this->formatOptions(array_keys($diff)))); } // Lock the container @@ -675,7 +819,8 @@ public function resolve(array $options = []) /** * Returns the resolved value of an option. * - * @param string $option The option name + * @param string $option The option name + * @param bool $triggerDeprecation Whether to trigger the deprecation or not (true by default) * * @return mixed The option value * @@ -687,34 +832,67 @@ public function resolve(array $options = []) * @throws OptionDefinitionException If there is a cyclic dependency between * lazy options and/or normalizers */ - public function offsetGet($option) + #[\ReturnTypeWillChange] + public function offsetGet($option/*, bool $triggerDeprecation = true*/) { if (!$this->locked) { throw new AccessException('Array access is only supported within closures of lazy options and normalizers.'); } + $triggerDeprecation = 1 === \func_num_args() || func_get_arg(1); + // Shortcut for resolved options - if (\array_key_exists($option, $this->resolved)) { + if (isset($this->resolved[$option]) || \array_key_exists($option, $this->resolved)) { + if ($triggerDeprecation && isset($this->deprecated[$option]) && (isset($this->given[$option]) || $this->calling) && \is_string($this->deprecated[$option])) { + @trigger_error(strtr($this->deprecated[$option], ['%name%' => $option]), \E_USER_DEPRECATED); + } + return $this->resolved[$option]; } // Check whether the option is set at all - if (!\array_key_exists($option, $this->defaults)) { + if (!isset($this->defaults[$option]) && !\array_key_exists($option, $this->defaults)) { if (!isset($this->defined[$option])) { - throw new NoSuchOptionException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $option, implode('", "', array_keys($this->defined)))); + throw new NoSuchOptionException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $this->formatOptions([$option]), implode('", "', array_keys($this->defined)))); } - throw new NoSuchOptionException(sprintf('The optional option "%s" has no value set. You should make sure it is set with "isset" before reading it.', $option)); + throw new NoSuchOptionException(sprintf('The optional option "%s" has no value set. You should make sure it is set with "isset" before reading it.', $this->formatOptions([$option]))); } $value = $this->defaults[$option]; + // Resolve the option if it is a nested definition + if (isset($this->nested[$option])) { + // If the closure is already being called, we have a cyclic dependency + if (isset($this->calling[$option])) { + throw new OptionDefinitionException(sprintf('The options "%s" have a cyclic dependency.', $this->formatOptions(array_keys($this->calling)))); + } + + if (!\is_array($value)) { + throw new InvalidOptionsException(sprintf('The nested option "%s" with value %s is expected to be of type array, but is of type "%s".', $this->formatOptions([$option]), $this->formatValue($value), $this->formatTypeOf($value))); + } + + // The following section must be protected from cyclic calls. + $this->calling[$option] = true; + try { + $resolver = new self(); + $resolver->parentsOptions = $this->parentsOptions; + $resolver->parentsOptions[] = $option; + foreach ($this->nested[$option] as $closure) { + $closure($resolver, $this); + } + $value = $resolver->resolve($value); + } finally { + unset($this->calling[$option]); + } + } + // Resolve the option if the default value is lazily evaluated if (isset($this->lazy[$option])) { // If the closure is already being called, we have a cyclic // dependency if (isset($this->calling[$option])) { - throw new OptionDefinitionException(sprintf('The options "%s" have a cyclic dependency.', implode('", "', array_keys($this->calling)))); + throw new OptionDefinitionException(sprintf('The options "%s" have a cyclic dependency.', $this->formatOptions(array_keys($this->calling)))); } // The following section must be protected from cyclic @@ -738,7 +916,7 @@ public function offsetGet($option) $invalidTypes = []; foreach ($this->allowedTypes[$option] as $type) { - $type = isset(self::$typeAliases[$type]) ? self::$typeAliases[$type] : $type; + $type = self::TYPE_ALIASES[$type] ?? $type; if ($valid = $this->verifyTypes($type, $value, $invalidTypes)) { break; @@ -749,19 +927,15 @@ public function offsetGet($option) $fmtActualValue = $this->formatValue($value); $fmtAllowedTypes = implode('" or "', $this->allowedTypes[$option]); $fmtProvidedTypes = implode('|', array_keys($invalidTypes)); - - $allowedContainsArrayType = \count(array_filter( - $this->allowedTypes[$option], - function ($item) { - return '[]' === substr(isset(self::$typeAliases[$item]) ? self::$typeAliases[$item] : $item, -2); - } - )) > 0; + $allowedContainsArrayType = \count(array_filter($this->allowedTypes[$option], static function ($item) { + return str_ends_with(self::TYPE_ALIASES[$item] ?? $item, '[]'); + })) > 0; if (\is_array($value) && $allowedContainsArrayType) { - throw new InvalidOptionsException(sprintf('The option "%s" with value %s is expected to be of type "%s", but one of the elements is of type "%s".', $option, $fmtActualValue, $fmtAllowedTypes, $fmtProvidedTypes)); + throw new InvalidOptionsException(sprintf('The option "%s" with value %s is expected to be of type "%s", but one of the elements is of type "%s".', $this->formatOptions([$option]), $fmtActualValue, $fmtAllowedTypes, $fmtProvidedTypes)); } - throw new InvalidOptionsException(sprintf('The option "%s" with value %s is expected to be of type "%s", but is of type "%s".', $option, $fmtActualValue, $fmtAllowedTypes, $fmtProvidedTypes)); + throw new InvalidOptionsException(sprintf('The option "%s" with value %s is expected to be of type "%s", but is of type "%s".', $this->formatOptions([$option]), $fmtActualValue, $fmtAllowedTypes, $fmtProvidedTypes)); } } @@ -807,23 +981,49 @@ function ($item) { } } + // Check whether the option is deprecated + // and it is provided by the user or is being called from a lazy evaluation + if ($triggerDeprecation && isset($this->deprecated[$option]) && (isset($this->given[$option]) || ($this->calling && \is_string($this->deprecated[$option])))) { + $deprecationMessage = $this->deprecated[$option]; + + if ($deprecationMessage instanceof \Closure) { + // If the closure is already being called, we have a cyclic dependency + if (isset($this->calling[$option])) { + throw new OptionDefinitionException(sprintf('The options "%s" have a cyclic dependency.', $this->formatOptions(array_keys($this->calling)))); + } + + $this->calling[$option] = true; + try { + if (!\is_string($deprecationMessage = $deprecationMessage($this, $value))) { + throw new InvalidOptionsException(sprintf('Invalid type for deprecation message, expected string but got "%s", return an empty string to ignore.', \gettype($deprecationMessage))); + } + } finally { + unset($this->calling[$option]); + } + } + + if ('' !== $deprecationMessage) { + @trigger_error(strtr($deprecationMessage, ['%name%' => $option]), \E_USER_DEPRECATED); + } + } + // Normalize the validated option if (isset($this->normalizers[$option])) { // If the closure is already being called, we have a cyclic // dependency if (isset($this->calling[$option])) { - throw new OptionDefinitionException(sprintf('The options "%s" have a cyclic dependency.', implode('", "', array_keys($this->calling)))); + throw new OptionDefinitionException(sprintf('The options "%s" have a cyclic dependency.', $this->formatOptions(array_keys($this->calling)))); } - $normalizer = $this->normalizers[$option]; - // The following section must be protected from cyclic // calls. Set $calling for the current $option to detect a cyclic // dependency // BEGIN $this->calling[$option] = true; try { - $value = $normalizer($this, $value); + foreach ($this->normalizers[$option] as $normalizer) { + $value = $normalizer($this, $value); + } } finally { unset($this->calling[$option]); } @@ -836,63 +1036,30 @@ function ($item) { return $value; } - /** - * @param string $type - * @param mixed $value - * @param array &$invalidTypes - * - * @return bool - */ - private function verifyTypes($type, $value, array &$invalidTypes) + private function verifyTypes(string $type, $value, array &$invalidTypes, int $level = 0): bool { if (\is_array($value) && '[]' === substr($type, -2)) { - return $this->verifyArrayType($type, $value, $invalidTypes); - } - - if (self::isValueValidType($type, $value)) { - return true; - } - - if (!$invalidTypes) { - $invalidTypes[$this->formatTypeOf($value, null)] = true; - } - - return false; - } - - /** - * @return bool - */ - private function verifyArrayType($type, array $value, array &$invalidTypes, $level = 0) - { - $type = substr($type, 0, -2); - - if ('[]' === substr($type, -2)) { - $success = true; - foreach ($value as $item) { - if (!\is_array($item)) { - $invalidTypes[$this->formatTypeOf($item, null)] = true; + $type = substr($type, 0, -2); + $valid = true; - $success = false; - } elseif (!$this->verifyArrayType($type, $item, $invalidTypes, $level + 1)) { - $success = false; + foreach ($value as $val) { + if (!$this->verifyTypes($type, $val, $invalidTypes, $level + 1)) { + $valid = false; } } - return $success; + return $valid; } - $valid = true; - - foreach ($value as $item) { - if (!self::isValueValidType($type, $item)) { - $invalidTypes[$this->formatTypeOf($item, $type)] = $value; + if (('null' === $type && null === $value) || (\function_exists($func = 'is_'.$type) && $func($value)) || $value instanceof $type) { + return true; + } - $valid = false; - } + if (!$invalidTypes || $level > 0) { + $invalidTypes[$this->formatTypeOf($value)] = true; } - return $valid; + return false; } /** @@ -906,6 +1073,7 @@ private function verifyArrayType($type, array $value, array &$invalidTypes, $lev * * @see \ArrayAccess::offsetExists() */ + #[\ReturnTypeWillChange] public function offsetExists($option) { if (!$this->locked) { @@ -918,8 +1086,11 @@ public function offsetExists($option) /** * Not supported. * + * @return void + * * @throws AccessException */ + #[\ReturnTypeWillChange] public function offsetSet($option, $value) { throw new AccessException('Setting options via array access is not supported. Use setDefault() instead.'); @@ -928,8 +1099,11 @@ public function offsetSet($option, $value) /** * Not supported. * + * @return void + * * @throws AccessException */ + #[\ReturnTypeWillChange] public function offsetUnset($option) { throw new AccessException('Removing options via array access is not supported. Use remove() instead.'); @@ -946,6 +1120,7 @@ public function offsetUnset($option) * * @see \Countable::count() */ + #[\ReturnTypeWillChange] public function count() { if (!$this->locked) { @@ -958,43 +1133,13 @@ public function count() /** * Returns a string representation of the type of the value. * - * This method should be used if you pass the type of a value as - * message parameter to a constraint violation. Note that such - * parameters should usually not be included in messages aimed at - * non-technical people. - * - * @param mixed $value The value to return the type of - * @param string $type + * @param mixed $value The value to return the type of * * @return string The type of the value */ - private function formatTypeOf($value, $type) + private function formatTypeOf($value): string { - $suffix = ''; - - if ('[]' === substr($type, -2)) { - $suffix = '[]'; - $type = substr($type, 0, -2); - while ('[]' === substr($type, -2)) { - $type = substr($type, 0, -2); - $value = array_shift($value); - if (!\is_array($value)) { - break; - } - $suffix .= '[]'; - } - - if (\is_array($value)) { - $subTypes = []; - foreach ($value as $val) { - $subTypes[$this->formatTypeOf($val, null)] = true; - } - - return implode('|', array_keys($subTypes)).$suffix; - } - } - - return (\is_object($value) ? \get_class($value) : \gettype($value)).$suffix; + return \is_object($value) ? \get_class($value) : \gettype($value); } /** @@ -1005,10 +1150,8 @@ private function formatTypeOf($value, $type) * in double quotes ("). * * @param mixed $value The value to format as string - * - * @return string The string representation of the passed value */ - private function formatValue($value) + private function formatValue($value): string { if (\is_object($value)) { return \get_class($value); @@ -1047,13 +1190,9 @@ private function formatValue($value) * Each of the values is converted to a string using * {@link formatValue()}. The values are then concatenated with commas. * - * @param array $values A list of values - * - * @return string The string representation of the value list - * * @see formatValue() */ - private function formatValues(array $values) + private function formatValues(array $values): string { foreach ($values as $key => $value) { $values[$key] = $this->formatValue($value); @@ -1062,24 +1201,28 @@ private function formatValues(array $values) return implode(', ', $values); } - private static function isValueValidType($type, $value) + private function formatOptions(array $options): string { - return (\function_exists($isFunction = 'is_'.$type) && $isFunction($value)) || $value instanceof $type; - } + if ($this->parentsOptions) { + $prefix = array_shift($this->parentsOptions); + if ($this->parentsOptions) { + $prefix .= sprintf('[%s]', implode('][', $this->parentsOptions)); + } - /** - * @return string|null - */ - private function getParameterClassName(\ReflectionParameter $parameter) - { - if (!method_exists($parameter, 'getType')) { - return ($class = $parameter->getClass()) ? $class->name : null; + $options = array_map(static function (string $option) use ($prefix): string { + return sprintf('%s[%s]', $prefix, $option); + }, $options); } - if (!($type = $parameter->getType()) || $type->isBuiltin()) { + return implode('", "', $options); + } + + private function getParameterClassName(\ReflectionParameter $parameter): ?string + { + if (!($type = $parameter->getType()) instanceof \ReflectionNamedType || $type->isBuiltin()) { return null; } - return $type instanceof \ReflectionNamedType ? $type->getName() : (string) $type; + return $type->getName(); } } diff --git a/vendor/symfony/options-resolver/Tests/Debug/OptionsResolverIntrospectorTest.php b/vendor/symfony/options-resolver/Tests/Debug/OptionsResolverIntrospectorTest.php deleted file mode 100644 index 5fdb9e26..00000000 --- a/vendor/symfony/options-resolver/Tests/Debug/OptionsResolverIntrospectorTest.php +++ /dev/null @@ -1,183 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\OptionsResolver\Tests\Debug; - -use PHPUnit\Framework\TestCase; -use Symfony\Component\OptionsResolver\Debug\OptionsResolverIntrospector; -use Symfony\Component\OptionsResolver\Options; -use Symfony\Component\OptionsResolver\OptionsResolver; - -class OptionsResolverIntrospectorTest extends TestCase -{ - public function testGetDefault() - { - $resolver = new OptionsResolver(); - $resolver->setDefault($option = 'foo', 'bar'); - - $debug = new OptionsResolverIntrospector($resolver); - $this->assertSame('bar', $debug->getDefault($option)); - } - - public function testGetDefaultNull() - { - $resolver = new OptionsResolver(); - $resolver->setDefault($option = 'foo', null); - - $debug = new OptionsResolverIntrospector($resolver); - $this->assertNull($debug->getDefault($option)); - } - - public function testGetDefaultThrowsOnNoConfiguredValue() - { - $this->expectException('Symfony\Component\OptionsResolver\Exception\NoConfigurationException'); - $this->expectExceptionMessage('No default value was set for the "foo" option.'); - $resolver = new OptionsResolver(); - $resolver->setDefined($option = 'foo'); - - $debug = new OptionsResolverIntrospector($resolver); - $debug->getDefault($option); - } - - public function testGetDefaultThrowsOnNotDefinedOption() - { - $this->expectException('Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException'); - $this->expectExceptionMessage('The option "foo" does not exist.'); - $resolver = new OptionsResolver(); - - $debug = new OptionsResolverIntrospector($resolver); - $debug->getDefault('foo'); - } - - public function testGetLazyClosures() - { - $resolver = new OptionsResolver(); - $closures = []; - $resolver->setDefault($option = 'foo', $closures[] = function (Options $options) {}); - - $debug = new OptionsResolverIntrospector($resolver); - $this->assertSame($closures, $debug->getLazyClosures($option)); - } - - public function testGetLazyClosuresThrowsOnNoConfiguredValue() - { - $this->expectException('Symfony\Component\OptionsResolver\Exception\NoConfigurationException'); - $this->expectExceptionMessage('No lazy closures were set for the "foo" option.'); - $resolver = new OptionsResolver(); - $resolver->setDefined($option = 'foo'); - - $debug = new OptionsResolverIntrospector($resolver); - $debug->getLazyClosures($option); - } - - public function testGetLazyClosuresThrowsOnNotDefinedOption() - { - $this->expectException('Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException'); - $this->expectExceptionMessage('The option "foo" does not exist.'); - $resolver = new OptionsResolver(); - - $debug = new OptionsResolverIntrospector($resolver); - $debug->getLazyClosures('foo'); - } - - public function testGetAllowedTypes() - { - $resolver = new OptionsResolver(); - $resolver->setDefined($option = 'foo'); - $resolver->setAllowedTypes($option = 'foo', $allowedTypes = ['string', 'bool']); - - $debug = new OptionsResolverIntrospector($resolver); - $this->assertSame($allowedTypes, $debug->getAllowedTypes($option)); - } - - public function testGetAllowedTypesThrowsOnNoConfiguredValue() - { - $this->expectException('Symfony\Component\OptionsResolver\Exception\NoConfigurationException'); - $this->expectExceptionMessage('No allowed types were set for the "foo" option.'); - $resolver = new OptionsResolver(); - $resolver->setDefined($option = 'foo'); - - $debug = new OptionsResolverIntrospector($resolver); - $this->assertSame('bar', $debug->getAllowedTypes($option)); - } - - public function testGetAllowedTypesThrowsOnNotDefinedOption() - { - $this->expectException('Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException'); - $this->expectExceptionMessage('The option "foo" does not exist.'); - $resolver = new OptionsResolver(); - - $debug = new OptionsResolverIntrospector($resolver); - $this->assertSame('bar', $debug->getAllowedTypes('foo')); - } - - public function testGetAllowedValues() - { - $resolver = new OptionsResolver(); - $resolver->setDefined($option = 'foo'); - $resolver->setAllowedValues($option = 'foo', $allowedValues = ['bar', 'baz']); - - $debug = new OptionsResolverIntrospector($resolver); - $this->assertSame($allowedValues, $debug->getAllowedValues($option)); - } - - public function testGetAllowedValuesThrowsOnNoConfiguredValue() - { - $this->expectException('Symfony\Component\OptionsResolver\Exception\NoConfigurationException'); - $this->expectExceptionMessage('No allowed values were set for the "foo" option.'); - $resolver = new OptionsResolver(); - $resolver->setDefined($option = 'foo'); - - $debug = new OptionsResolverIntrospector($resolver); - $this->assertSame('bar', $debug->getAllowedValues($option)); - } - - public function testGetAllowedValuesThrowsOnNotDefinedOption() - { - $this->expectException('Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException'); - $this->expectExceptionMessage('The option "foo" does not exist.'); - $resolver = new OptionsResolver(); - - $debug = new OptionsResolverIntrospector($resolver); - $this->assertSame('bar', $debug->getAllowedValues('foo')); - } - - public function testGetNormalizer() - { - $resolver = new OptionsResolver(); - $resolver->setDefined($option = 'foo'); - $resolver->setNormalizer($option = 'foo', $normalizer = function () {}); - - $debug = new OptionsResolverIntrospector($resolver); - $this->assertSame($normalizer, $debug->getNormalizer($option)); - } - - public function testGetNormalizerThrowsOnNoConfiguredValue() - { - $this->expectException('Symfony\Component\OptionsResolver\Exception\NoConfigurationException'); - $this->expectExceptionMessage('No normalizer was set for the "foo" option.'); - $resolver = new OptionsResolver(); - $resolver->setDefined($option = 'foo'); - - $debug = new OptionsResolverIntrospector($resolver); - $this->assertSame('bar', $debug->getNormalizer($option)); - } - - public function testGetNormalizerThrowsOnNotDefinedOption() - { - $this->expectException('Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException'); - $this->expectExceptionMessage('The option "foo" does not exist.'); - $resolver = new OptionsResolver(); - - $debug = new OptionsResolverIntrospector($resolver); - $this->assertSame('bar', $debug->getNormalizer('foo')); - } -} diff --git a/vendor/symfony/options-resolver/Tests/OptionsResolverTest.php b/vendor/symfony/options-resolver/Tests/OptionsResolverTest.php deleted file mode 100644 index ce90f937..00000000 --- a/vendor/symfony/options-resolver/Tests/OptionsResolverTest.php +++ /dev/null @@ -1,1626 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\OptionsResolver\Tests; - -use PHPUnit\Framework\Assert; -use PHPUnit\Framework\TestCase; -use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; -use Symfony\Component\OptionsResolver\Options; -use Symfony\Component\OptionsResolver\OptionsResolver; - -class OptionsResolverTest extends TestCase -{ - /** - * @var OptionsResolver - */ - private $resolver; - - protected function setUp() - { - $this->resolver = new OptionsResolver(); - } - - public function testResolveFailsIfNonExistingOption() - { - $this->expectException('Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException'); - $this->expectExceptionMessage('The option "foo" does not exist. Defined options are: "a", "z".'); - $this->resolver->setDefault('z', '1'); - $this->resolver->setDefault('a', '2'); - - $this->resolver->resolve(['foo' => 'bar']); - } - - public function testResolveFailsIfMultipleNonExistingOptions() - { - $this->expectException('Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException'); - $this->expectExceptionMessage('The options "baz", "foo", "ping" do not exist. Defined options are: "a", "z".'); - $this->resolver->setDefault('z', '1'); - $this->resolver->setDefault('a', '2'); - - $this->resolver->resolve(['ping' => 'pong', 'foo' => 'bar', 'baz' => 'bam']); - } - - public function testResolveFailsFromLazyOption() - { - $this->expectException('Symfony\Component\OptionsResolver\Exception\AccessException'); - $this->resolver->setDefault('foo', function (Options $options) { - $options->resolve([]); - }); - - $this->resolver->resolve(); - } - - public function testSetDefaultReturnsThis() - { - $this->assertSame($this->resolver, $this->resolver->setDefault('foo', 'bar')); - } - - public function testSetDefault() - { - $this->resolver->setDefault('one', '1'); - $this->resolver->setDefault('two', '20'); - - $this->assertEquals([ - 'one' => '1', - 'two' => '20', - ], $this->resolver->resolve()); - } - - public function testFailIfSetDefaultFromLazyOption() - { - $this->expectException('Symfony\Component\OptionsResolver\Exception\AccessException'); - $this->resolver->setDefault('lazy', function (Options $options) { - $options->setDefault('default', 42); - }); - - $this->resolver->resolve(); - } - - public function testHasDefault() - { - $this->assertFalse($this->resolver->hasDefault('foo')); - $this->resolver->setDefault('foo', 42); - $this->assertTrue($this->resolver->hasDefault('foo')); - } - - public function testHasDefaultWithNullValue() - { - $this->assertFalse($this->resolver->hasDefault('foo')); - $this->resolver->setDefault('foo', null); - $this->assertTrue($this->resolver->hasDefault('foo')); - } - - public function testSetLazyReturnsThis() - { - $this->assertSame($this->resolver, $this->resolver->setDefault('foo', function (Options $options) {})); - } - - public function testSetLazyClosure() - { - $this->resolver->setDefault('foo', function (Options $options) { - return 'lazy'; - }); - - $this->assertEquals(['foo' => 'lazy'], $this->resolver->resolve()); - } - - public function testClosureWithoutTypeHintNotInvoked() - { - $closure = function ($options) { - Assert::fail('Should not be called'); - }; - - $this->resolver->setDefault('foo', $closure); - - $this->assertSame(['foo' => $closure], $this->resolver->resolve()); - } - - public function testClosureWithoutParametersNotInvoked() - { - $closure = function () { - Assert::fail('Should not be called'); - }; - - $this->resolver->setDefault('foo', $closure); - - $this->assertSame(['foo' => $closure], $this->resolver->resolve()); - } - - public function testAccessPreviousDefaultValue() - { - // defined by superclass - $this->resolver->setDefault('foo', 'bar'); - - // defined by subclass - $this->resolver->setDefault('foo', function (Options $options, $previousValue) { - Assert::assertEquals('bar', $previousValue); - - return 'lazy'; - }); - - $this->assertEquals(['foo' => 'lazy'], $this->resolver->resolve()); - } - - public function testAccessPreviousLazyDefaultValue() - { - // defined by superclass - $this->resolver->setDefault('foo', function (Options $options) { - return 'bar'; - }); - - // defined by subclass - $this->resolver->setDefault('foo', function (Options $options, $previousValue) { - Assert::assertEquals('bar', $previousValue); - - return 'lazy'; - }); - - $this->assertEquals(['foo' => 'lazy'], $this->resolver->resolve()); - } - - public function testPreviousValueIsNotEvaluatedIfNoSecondArgument() - { - // defined by superclass - $this->resolver->setDefault('foo', function () { - Assert::fail('Should not be called'); - }); - - // defined by subclass, no $previousValue argument defined! - $this->resolver->setDefault('foo', function (Options $options) { - return 'lazy'; - }); - - $this->assertEquals(['foo' => 'lazy'], $this->resolver->resolve()); - } - - public function testOverwrittenLazyOptionNotEvaluated() - { - $this->resolver->setDefault('foo', function (Options $options) { - Assert::fail('Should not be called'); - }); - - $this->resolver->setDefault('foo', 'bar'); - - $this->assertSame(['foo' => 'bar'], $this->resolver->resolve()); - } - - public function testInvokeEachLazyOptionOnlyOnce() - { - $calls = 0; - - $this->resolver->setDefault('lazy1', function (Options $options) use (&$calls) { - Assert::assertSame(1, ++$calls); - - $options['lazy2']; - }); - - $this->resolver->setDefault('lazy2', function (Options $options) use (&$calls) { - Assert::assertSame(2, ++$calls); - }); - - $this->resolver->resolve(); - - $this->assertSame(2, $calls); - } - - public function testSetRequiredReturnsThis() - { - $this->assertSame($this->resolver, $this->resolver->setRequired('foo')); - } - - public function testFailIfSetRequiredFromLazyOption() - { - $this->expectException('Symfony\Component\OptionsResolver\Exception\AccessException'); - $this->resolver->setDefault('foo', function (Options $options) { - $options->setRequired('bar'); - }); - - $this->resolver->resolve(); - } - - public function testResolveFailsIfRequiredOptionMissing() - { - $this->expectException('Symfony\Component\OptionsResolver\Exception\MissingOptionsException'); - $this->resolver->setRequired('foo'); - - $this->resolver->resolve(); - } - - public function testResolveSucceedsIfRequiredOptionSet() - { - $this->resolver->setRequired('foo'); - $this->resolver->setDefault('foo', 'bar'); - - $this->assertNotEmpty($this->resolver->resolve()); - } - - public function testResolveSucceedsIfRequiredOptionPassed() - { - $this->resolver->setRequired('foo'); - - $this->assertNotEmpty($this->resolver->resolve(['foo' => 'bar'])); - } - - public function testIsRequired() - { - $this->assertFalse($this->resolver->isRequired('foo')); - $this->resolver->setRequired('foo'); - $this->assertTrue($this->resolver->isRequired('foo')); - } - - public function testRequiredIfSetBefore() - { - $this->assertFalse($this->resolver->isRequired('foo')); - - $this->resolver->setDefault('foo', 'bar'); - $this->resolver->setRequired('foo'); - - $this->assertTrue($this->resolver->isRequired('foo')); - } - - public function testStillRequiredAfterSet() - { - $this->assertFalse($this->resolver->isRequired('foo')); - - $this->resolver->setRequired('foo'); - $this->resolver->setDefault('foo', 'bar'); - - $this->assertTrue($this->resolver->isRequired('foo')); - } - - public function testIsNotRequiredAfterRemove() - { - $this->assertFalse($this->resolver->isRequired('foo')); - $this->resolver->setRequired('foo'); - $this->resolver->remove('foo'); - $this->assertFalse($this->resolver->isRequired('foo')); - } - - public function testIsNotRequiredAfterClear() - { - $this->assertFalse($this->resolver->isRequired('foo')); - $this->resolver->setRequired('foo'); - $this->resolver->clear(); - $this->assertFalse($this->resolver->isRequired('foo')); - } - - public function testGetRequiredOptions() - { - $this->resolver->setRequired(['foo', 'bar']); - $this->resolver->setDefault('bam', 'baz'); - $this->resolver->setDefault('foo', 'boo'); - - $this->assertSame(['foo', 'bar'], $this->resolver->getRequiredOptions()); - } - - public function testIsMissingIfNotSet() - { - $this->assertFalse($this->resolver->isMissing('foo')); - $this->resolver->setRequired('foo'); - $this->assertTrue($this->resolver->isMissing('foo')); - } - - public function testIsNotMissingIfSet() - { - $this->resolver->setDefault('foo', 'bar'); - - $this->assertFalse($this->resolver->isMissing('foo')); - $this->resolver->setRequired('foo'); - $this->assertFalse($this->resolver->isMissing('foo')); - } - - public function testIsNotMissingAfterRemove() - { - $this->resolver->setRequired('foo'); - $this->resolver->remove('foo'); - $this->assertFalse($this->resolver->isMissing('foo')); - } - - public function testIsNotMissingAfterClear() - { - $this->resolver->setRequired('foo'); - $this->resolver->clear(); - $this->assertFalse($this->resolver->isRequired('foo')); - } - - public function testGetMissingOptions() - { - $this->resolver->setRequired(['foo', 'bar']); - $this->resolver->setDefault('bam', 'baz'); - $this->resolver->setDefault('foo', 'boo'); - - $this->assertSame(['bar'], $this->resolver->getMissingOptions()); - } - - public function testFailIfSetDefinedFromLazyOption() - { - $this->expectException('Symfony\Component\OptionsResolver\Exception\AccessException'); - $this->resolver->setDefault('foo', function (Options $options) { - $options->setDefined('bar'); - }); - - $this->resolver->resolve(); - } - - public function testDefinedOptionsNotIncludedInResolvedOptions() - { - $this->resolver->setDefined('foo'); - - $this->assertSame([], $this->resolver->resolve()); - } - - public function testDefinedOptionsIncludedIfDefaultSetBefore() - { - $this->resolver->setDefault('foo', 'bar'); - $this->resolver->setDefined('foo'); - - $this->assertSame(['foo' => 'bar'], $this->resolver->resolve()); - } - - public function testDefinedOptionsIncludedIfDefaultSetAfter() - { - $this->resolver->setDefined('foo'); - $this->resolver->setDefault('foo', 'bar'); - - $this->assertSame(['foo' => 'bar'], $this->resolver->resolve()); - } - - public function testDefinedOptionsIncludedIfPassedToResolve() - { - $this->resolver->setDefined('foo'); - - $this->assertSame(['foo' => 'bar'], $this->resolver->resolve(['foo' => 'bar'])); - } - - public function testIsDefined() - { - $this->assertFalse($this->resolver->isDefined('foo')); - $this->resolver->setDefined('foo'); - $this->assertTrue($this->resolver->isDefined('foo')); - } - - public function testLazyOptionsAreDefined() - { - $this->assertFalse($this->resolver->isDefined('foo')); - $this->resolver->setDefault('foo', function (Options $options) {}); - $this->assertTrue($this->resolver->isDefined('foo')); - } - - public function testRequiredOptionsAreDefined() - { - $this->assertFalse($this->resolver->isDefined('foo')); - $this->resolver->setRequired('foo'); - $this->assertTrue($this->resolver->isDefined('foo')); - } - - public function testSetOptionsAreDefined() - { - $this->assertFalse($this->resolver->isDefined('foo')); - $this->resolver->setDefault('foo', 'bar'); - $this->assertTrue($this->resolver->isDefined('foo')); - } - - public function testGetDefinedOptions() - { - $this->resolver->setDefined(['foo', 'bar']); - $this->resolver->setDefault('baz', 'bam'); - $this->resolver->setRequired('boo'); - - $this->assertSame(['foo', 'bar', 'baz', 'boo'], $this->resolver->getDefinedOptions()); - } - - public function testRemovedOptionsAreNotDefined() - { - $this->assertFalse($this->resolver->isDefined('foo')); - $this->resolver->setDefined('foo'); - $this->assertTrue($this->resolver->isDefined('foo')); - $this->resolver->remove('foo'); - $this->assertFalse($this->resolver->isDefined('foo')); - } - - public function testClearedOptionsAreNotDefined() - { - $this->assertFalse($this->resolver->isDefined('foo')); - $this->resolver->setDefined('foo'); - $this->assertTrue($this->resolver->isDefined('foo')); - $this->resolver->clear(); - $this->assertFalse($this->resolver->isDefined('foo')); - } - - public function testSetAllowedTypesFailsIfUnknownOption() - { - $this->expectException('Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException'); - $this->resolver->setAllowedTypes('foo', 'string'); - } - - public function testResolveTypedArray() - { - $this->resolver->setDefined('foo'); - $this->resolver->setAllowedTypes('foo', 'string[]'); - $options = $this->resolver->resolve(['foo' => ['bar', 'baz']]); - - $this->assertSame(['foo' => ['bar', 'baz']], $options); - } - - public function testFailIfSetAllowedTypesFromLazyOption() - { - $this->expectException('Symfony\Component\OptionsResolver\Exception\AccessException'); - $this->resolver->setDefault('foo', function (Options $options) { - $options->setAllowedTypes('bar', 'string'); - }); - - $this->resolver->setDefault('bar', 'baz'); - - $this->resolver->resolve(); - } - - public function testResolveFailsIfInvalidTypedArray() - { - $this->expectException('Symfony\Component\OptionsResolver\Exception\InvalidOptionsException'); - $this->expectExceptionMessage('The option "foo" with value array is expected to be of type "int[]", but one of the elements is of type "DateTime".'); - $this->resolver->setDefined('foo'); - $this->resolver->setAllowedTypes('foo', 'int[]'); - - $this->resolver->resolve(['foo' => [new \DateTime()]]); - } - - public function testResolveFailsWithNonArray() - { - $this->expectException('Symfony\Component\OptionsResolver\Exception\InvalidOptionsException'); - $this->expectExceptionMessage('The option "foo" with value "bar" is expected to be of type "int[]", but is of type "string".'); - $this->resolver->setDefined('foo'); - $this->resolver->setAllowedTypes('foo', 'int[]'); - - $this->resolver->resolve(['foo' => 'bar']); - } - - public function testResolveFailsIfTypedArrayContainsInvalidTypes() - { - $this->expectException('Symfony\Component\OptionsResolver\Exception\InvalidOptionsException'); - $this->expectExceptionMessage('The option "foo" with value array is expected to be of type "int[]", but one of the elements is of type "stdClass|array|DateTime".'); - $this->resolver->setDefined('foo'); - $this->resolver->setAllowedTypes('foo', 'int[]'); - - $values = range(1, 5); - $values[] = new \stdClass(); - $values[] = []; - $values[] = new \DateTime(); - $values[] = 123; - - $this->resolver->resolve(['foo' => $values]); - } - - public function testResolveFailsWithCorrectLevelsButWrongScalar() - { - $this->expectException('Symfony\Component\OptionsResolver\Exception\InvalidOptionsException'); - $this->expectExceptionMessage('The option "foo" with value array is expected to be of type "int[][]", but one of the elements is of type "double".'); - $this->resolver->setDefined('foo'); - $this->resolver->setAllowedTypes('foo', 'int[][]'); - - $this->resolver->resolve([ - 'foo' => [ - [1.2], - ], - ]); - } - - /** - * @dataProvider provideInvalidTypes - */ - public function testResolveFailsIfInvalidType($actualType, $allowedType, $exceptionMessage) - { - $this->resolver->setDefined('option'); - $this->resolver->setAllowedTypes('option', $allowedType); - - $this->expectException('Symfony\Component\OptionsResolver\Exception\InvalidOptionsException'); - $this->expectExceptionMessage($exceptionMessage); - - $this->resolver->resolve(['option' => $actualType]); - } - - public function provideInvalidTypes() - { - return [ - [true, 'string', 'The option "option" with value true is expected to be of type "string", but is of type "boolean".'], - [false, 'string', 'The option "option" with value false is expected to be of type "string", but is of type "boolean".'], - [fopen(__FILE__, 'r'), 'string', 'The option "option" with value resource is expected to be of type "string", but is of type "resource".'], - [[], 'string', 'The option "option" with value array is expected to be of type "string", but is of type "array".'], - [new OptionsResolver(), 'string', 'The option "option" with value Symfony\Component\OptionsResolver\OptionsResolver is expected to be of type "string", but is of type "Symfony\Component\OptionsResolver\OptionsResolver".'], - [42, 'string', 'The option "option" with value 42 is expected to be of type "string", but is of type "integer".'], - [null, 'string', 'The option "option" with value null is expected to be of type "string", but is of type "NULL".'], - ['bar', '\stdClass', 'The option "option" with value "bar" is expected to be of type "\stdClass", but is of type "string".'], - [['foo', 12], 'string[]', 'The option "option" with value array is expected to be of type "string[]", but one of the elements is of type "integer".'], - [123, ['string[]', 'string'], 'The option "option" with value 123 is expected to be of type "string[]" or "string", but is of type "integer".'], - [[null], ['string[]', 'string'], 'The option "option" with value array is expected to be of type "string[]" or "string", but one of the elements is of type "NULL".'], - [['string', null], ['string[]', 'string'], 'The option "option" with value array is expected to be of type "string[]" or "string", but one of the elements is of type "NULL".'], - [[\stdClass::class], ['string'], 'The option "option" with value array is expected to be of type "string", but is of type "array".'], - ]; - } - - public function testResolveSucceedsIfValidType() - { - $this->resolver->setDefault('foo', 'bar'); - $this->resolver->setAllowedTypes('foo', 'string'); - - $this->assertNotEmpty($this->resolver->resolve()); - } - - public function testResolveFailsIfInvalidTypeMultiple() - { - $this->expectException('Symfony\Component\OptionsResolver\Exception\InvalidOptionsException'); - $this->expectExceptionMessage('The option "foo" with value 42 is expected to be of type "string" or "bool", but is of type "integer".'); - $this->resolver->setDefault('foo', 42); - $this->resolver->setAllowedTypes('foo', ['string', 'bool']); - - $this->resolver->resolve(); - } - - public function testResolveSucceedsIfValidTypeMultiple() - { - $this->resolver->setDefault('foo', true); - $this->resolver->setAllowedTypes('foo', ['string', 'bool']); - - $this->assertNotEmpty($this->resolver->resolve()); - } - - public function testResolveSucceedsIfInstanceOfClass() - { - $this->resolver->setDefault('foo', new \stdClass()); - $this->resolver->setAllowedTypes('foo', '\stdClass'); - - $this->assertNotEmpty($this->resolver->resolve()); - } - - public function testResolveSucceedsIfTypedArray() - { - $this->resolver->setDefault('foo', null); - $this->resolver->setAllowedTypes('foo', ['null', 'DateTime[]']); - - $data = [ - 'foo' => [ - new \DateTime(), - new \DateTime(), - ], - ]; - - $result = $this->resolver->resolve($data); - $this->assertEquals($data, $result); - } - - public function testResolveFailsIfNotInstanceOfClass() - { - $this->expectException('Symfony\Component\OptionsResolver\Exception\InvalidOptionsException'); - $this->resolver->setDefault('foo', 'bar'); - $this->resolver->setAllowedTypes('foo', '\stdClass'); - - $this->resolver->resolve(); - } - - public function testAddAllowedTypesFailsIfUnknownOption() - { - $this->expectException('Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException'); - $this->resolver->addAllowedTypes('foo', 'string'); - } - - public function testFailIfAddAllowedTypesFromLazyOption() - { - $this->expectException('Symfony\Component\OptionsResolver\Exception\AccessException'); - $this->resolver->setDefault('foo', function (Options $options) { - $options->addAllowedTypes('bar', 'string'); - }); - - $this->resolver->setDefault('bar', 'baz'); - - $this->resolver->resolve(); - } - - public function testResolveFailsIfInvalidAddedType() - { - $this->expectException('Symfony\Component\OptionsResolver\Exception\InvalidOptionsException'); - $this->resolver->setDefault('foo', 42); - $this->resolver->addAllowedTypes('foo', 'string'); - - $this->resolver->resolve(); - } - - public function testResolveSucceedsIfValidAddedType() - { - $this->resolver->setDefault('foo', 'bar'); - $this->resolver->addAllowedTypes('foo', 'string'); - - $this->assertNotEmpty($this->resolver->resolve()); - } - - public function testResolveFailsIfInvalidAddedTypeMultiple() - { - $this->expectException('Symfony\Component\OptionsResolver\Exception\InvalidOptionsException'); - $this->resolver->setDefault('foo', 42); - $this->resolver->addAllowedTypes('foo', ['string', 'bool']); - - $this->resolver->resolve(); - } - - public function testResolveSucceedsIfValidAddedTypeMultiple() - { - $this->resolver->setDefault('foo', 'bar'); - $this->resolver->addAllowedTypes('foo', ['string', 'bool']); - - $this->assertNotEmpty($this->resolver->resolve()); - } - - public function testAddAllowedTypesDoesNotOverwrite() - { - $this->resolver->setDefault('foo', 'bar'); - $this->resolver->setAllowedTypes('foo', 'string'); - $this->resolver->addAllowedTypes('foo', 'bool'); - - $this->resolver->setDefault('foo', 'bar'); - - $this->assertNotEmpty($this->resolver->resolve()); - } - - public function testAddAllowedTypesDoesNotOverwrite2() - { - $this->resolver->setDefault('foo', 'bar'); - $this->resolver->setAllowedTypes('foo', 'string'); - $this->resolver->addAllowedTypes('foo', 'bool'); - - $this->resolver->setDefault('foo', false); - - $this->assertNotEmpty($this->resolver->resolve()); - } - - public function testSetAllowedValuesFailsIfUnknownOption() - { - $this->expectException('Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException'); - $this->resolver->setAllowedValues('foo', 'bar'); - } - - public function testFailIfSetAllowedValuesFromLazyOption() - { - $this->expectException('Symfony\Component\OptionsResolver\Exception\AccessException'); - $this->resolver->setDefault('foo', function (Options $options) { - $options->setAllowedValues('bar', 'baz'); - }); - - $this->resolver->setDefault('bar', 'baz'); - - $this->resolver->resolve(); - } - - public function testResolveFailsIfInvalidValue() - { - $this->expectException('Symfony\Component\OptionsResolver\Exception\InvalidOptionsException'); - $this->expectExceptionMessage('The option "foo" with value 42 is invalid. Accepted values are: "bar".'); - $this->resolver->setDefined('foo'); - $this->resolver->setAllowedValues('foo', 'bar'); - - $this->resolver->resolve(['foo' => 42]); - } - - public function testResolveFailsIfInvalidValueIsNull() - { - $this->expectException('Symfony\Component\OptionsResolver\Exception\InvalidOptionsException'); - $this->expectExceptionMessage('The option "foo" with value null is invalid. Accepted values are: "bar".'); - $this->resolver->setDefault('foo', null); - $this->resolver->setAllowedValues('foo', 'bar'); - - $this->resolver->resolve(); - } - - public function testResolveFailsIfInvalidValueStrict() - { - $this->expectException('Symfony\Component\OptionsResolver\Exception\InvalidOptionsException'); - $this->resolver->setDefault('foo', 42); - $this->resolver->setAllowedValues('foo', '42'); - - $this->resolver->resolve(); - } - - public function testResolveSucceedsIfValidValue() - { - $this->resolver->setDefault('foo', 'bar'); - $this->resolver->setAllowedValues('foo', 'bar'); - - $this->assertEquals(['foo' => 'bar'], $this->resolver->resolve()); - } - - public function testResolveSucceedsIfValidValueIsNull() - { - $this->resolver->setDefault('foo', null); - $this->resolver->setAllowedValues('foo', null); - - $this->assertEquals(['foo' => null], $this->resolver->resolve()); - } - - public function testResolveFailsIfInvalidValueMultiple() - { - $this->expectException('Symfony\Component\OptionsResolver\Exception\InvalidOptionsException'); - $this->expectExceptionMessage('The option "foo" with value 42 is invalid. Accepted values are: "bar", false, null.'); - $this->resolver->setDefault('foo', 42); - $this->resolver->setAllowedValues('foo', ['bar', false, null]); - - $this->resolver->resolve(); - } - - public function testResolveSucceedsIfValidValueMultiple() - { - $this->resolver->setDefault('foo', 'baz'); - $this->resolver->setAllowedValues('foo', ['bar', 'baz']); - - $this->assertEquals(['foo' => 'baz'], $this->resolver->resolve()); - } - - public function testResolveFailsIfClosureReturnsFalse() - { - $this->resolver->setDefault('foo', 42); - $this->resolver->setAllowedValues('foo', function ($value) use (&$passedValue) { - $passedValue = $value; - - return false; - }); - - try { - $this->resolver->resolve(); - $this->fail('Should fail'); - } catch (InvalidOptionsException $e) { - } - - $this->assertSame(42, $passedValue); - } - - public function testResolveSucceedsIfClosureReturnsTrue() - { - $this->resolver->setDefault('foo', 'bar'); - $this->resolver->setAllowedValues('foo', function ($value) use (&$passedValue) { - $passedValue = $value; - - return true; - }); - - $this->assertEquals(['foo' => 'bar'], $this->resolver->resolve()); - $this->assertSame('bar', $passedValue); - } - - public function testResolveFailsIfAllClosuresReturnFalse() - { - $this->expectException('Symfony\Component\OptionsResolver\Exception\InvalidOptionsException'); - $this->resolver->setDefault('foo', 42); - $this->resolver->setAllowedValues('foo', [ - function () { return false; }, - function () { return false; }, - function () { return false; }, - ]); - - $this->resolver->resolve(); - } - - public function testResolveSucceedsIfAnyClosureReturnsTrue() - { - $this->resolver->setDefault('foo', 'bar'); - $this->resolver->setAllowedValues('foo', [ - function () { return false; }, - function () { return true; }, - function () { return false; }, - ]); - - $this->assertEquals(['foo' => 'bar'], $this->resolver->resolve()); - } - - public function testAddAllowedValuesFailsIfUnknownOption() - { - $this->expectException('Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException'); - $this->resolver->addAllowedValues('foo', 'bar'); - } - - public function testFailIfAddAllowedValuesFromLazyOption() - { - $this->expectException('Symfony\Component\OptionsResolver\Exception\AccessException'); - $this->resolver->setDefault('foo', function (Options $options) { - $options->addAllowedValues('bar', 'baz'); - }); - - $this->resolver->setDefault('bar', 'baz'); - - $this->resolver->resolve(); - } - - public function testResolveFailsIfInvalidAddedValue() - { - $this->expectException('Symfony\Component\OptionsResolver\Exception\InvalidOptionsException'); - $this->resolver->setDefault('foo', 42); - $this->resolver->addAllowedValues('foo', 'bar'); - - $this->resolver->resolve(); - } - - public function testResolveSucceedsIfValidAddedValue() - { - $this->resolver->setDefault('foo', 'bar'); - $this->resolver->addAllowedValues('foo', 'bar'); - - $this->assertEquals(['foo' => 'bar'], $this->resolver->resolve()); - } - - public function testResolveSucceedsIfValidAddedValueIsNull() - { - $this->resolver->setDefault('foo', null); - $this->resolver->addAllowedValues('foo', null); - - $this->assertEquals(['foo' => null], $this->resolver->resolve()); - } - - public function testResolveFailsIfInvalidAddedValueMultiple() - { - $this->expectException('Symfony\Component\OptionsResolver\Exception\InvalidOptionsException'); - $this->resolver->setDefault('foo', 42); - $this->resolver->addAllowedValues('foo', ['bar', 'baz']); - - $this->resolver->resolve(); - } - - public function testResolveSucceedsIfValidAddedValueMultiple() - { - $this->resolver->setDefault('foo', 'bar'); - $this->resolver->addAllowedValues('foo', ['bar', 'baz']); - - $this->assertEquals(['foo' => 'bar'], $this->resolver->resolve()); - } - - public function testAddAllowedValuesDoesNotOverwrite() - { - $this->resolver->setDefault('foo', 'bar'); - $this->resolver->setAllowedValues('foo', 'bar'); - $this->resolver->addAllowedValues('foo', 'baz'); - - $this->assertEquals(['foo' => 'bar'], $this->resolver->resolve()); - } - - public function testAddAllowedValuesDoesNotOverwrite2() - { - $this->resolver->setDefault('foo', 'baz'); - $this->resolver->setAllowedValues('foo', 'bar'); - $this->resolver->addAllowedValues('foo', 'baz'); - - $this->assertEquals(['foo' => 'baz'], $this->resolver->resolve()); - } - - public function testResolveFailsIfAllAddedClosuresReturnFalse() - { - $this->expectException('Symfony\Component\OptionsResolver\Exception\InvalidOptionsException'); - $this->resolver->setDefault('foo', 42); - $this->resolver->setAllowedValues('foo', function () { return false; }); - $this->resolver->addAllowedValues('foo', function () { return false; }); - - $this->resolver->resolve(); - } - - public function testResolveSucceedsIfAnyAddedClosureReturnsTrue() - { - $this->resolver->setDefault('foo', 'bar'); - $this->resolver->setAllowedValues('foo', function () { return false; }); - $this->resolver->addAllowedValues('foo', function () { return true; }); - - $this->assertEquals(['foo' => 'bar'], $this->resolver->resolve()); - } - - public function testResolveSucceedsIfAnyAddedClosureReturnsTrue2() - { - $this->resolver->setDefault('foo', 'bar'); - $this->resolver->setAllowedValues('foo', function () { return true; }); - $this->resolver->addAllowedValues('foo', function () { return false; }); - - $this->assertEquals(['foo' => 'bar'], $this->resolver->resolve()); - } - - public function testSetNormalizerReturnsThis() - { - $this->resolver->setDefault('foo', 'bar'); - $this->assertSame($this->resolver, $this->resolver->setNormalizer('foo', function () {})); - } - - public function testSetNormalizerClosure() - { - $this->resolver->setDefault('foo', 'bar'); - $this->resolver->setNormalizer('foo', function () { - return 'normalized'; - }); - - $this->assertEquals(['foo' => 'normalized'], $this->resolver->resolve()); - } - - public function testSetNormalizerFailsIfUnknownOption() - { - $this->expectException('Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException'); - $this->resolver->setNormalizer('foo', function () {}); - } - - public function testFailIfSetNormalizerFromLazyOption() - { - $this->expectException('Symfony\Component\OptionsResolver\Exception\AccessException'); - $this->resolver->setDefault('foo', function (Options $options) { - $options->setNormalizer('foo', function () {}); - }); - - $this->resolver->setDefault('bar', 'baz'); - - $this->resolver->resolve(); - } - - public function testNormalizerReceivesSetOption() - { - $this->resolver->setDefault('foo', 'bar'); - - $this->resolver->setNormalizer('foo', function (Options $options, $value) { - return 'normalized['.$value.']'; - }); - - $this->assertEquals(['foo' => 'normalized[bar]'], $this->resolver->resolve()); - } - - public function testNormalizerReceivesPassedOption() - { - $this->resolver->setDefault('foo', 'bar'); - - $this->resolver->setNormalizer('foo', function (Options $options, $value) { - return 'normalized['.$value.']'; - }); - - $resolved = $this->resolver->resolve(['foo' => 'baz']); - - $this->assertEquals(['foo' => 'normalized[baz]'], $resolved); - } - - public function testValidateTypeBeforeNormalization() - { - $this->expectException('Symfony\Component\OptionsResolver\Exception\InvalidOptionsException'); - $this->resolver->setDefault('foo', 'bar'); - - $this->resolver->setAllowedTypes('foo', 'int'); - - $this->resolver->setNormalizer('foo', function () { - Assert::fail('Should not be called.'); - }); - - $this->resolver->resolve(); - } - - public function testValidateValueBeforeNormalization() - { - $this->expectException('Symfony\Component\OptionsResolver\Exception\InvalidOptionsException'); - $this->resolver->setDefault('foo', 'bar'); - - $this->resolver->setAllowedValues('foo', 'baz'); - - $this->resolver->setNormalizer('foo', function () { - Assert::fail('Should not be called.'); - }); - - $this->resolver->resolve(); - } - - public function testNormalizerCanAccessOtherOptions() - { - $this->resolver->setDefault('default', 'bar'); - $this->resolver->setDefault('norm', 'baz'); - - $this->resolver->setNormalizer('norm', function (Options $options) { - /* @var TestCase $test */ - Assert::assertSame('bar', $options['default']); - - return 'normalized'; - }); - - $this->assertEquals([ - 'default' => 'bar', - 'norm' => 'normalized', - ], $this->resolver->resolve()); - } - - public function testNormalizerCanAccessLazyOptions() - { - $this->resolver->setDefault('lazy', function (Options $options) { - return 'bar'; - }); - $this->resolver->setDefault('norm', 'baz'); - - $this->resolver->setNormalizer('norm', function (Options $options) { - /* @var TestCase $test */ - Assert::assertEquals('bar', $options['lazy']); - - return 'normalized'; - }); - - $this->assertEquals([ - 'lazy' => 'bar', - 'norm' => 'normalized', - ], $this->resolver->resolve()); - } - - public function testFailIfCyclicDependencyBetweenNormalizers() - { - $this->expectException('Symfony\Component\OptionsResolver\Exception\OptionDefinitionException'); - $this->resolver->setDefault('norm1', 'bar'); - $this->resolver->setDefault('norm2', 'baz'); - - $this->resolver->setNormalizer('norm1', function (Options $options) { - $options['norm2']; - }); - - $this->resolver->setNormalizer('norm2', function (Options $options) { - $options['norm1']; - }); - - $this->resolver->resolve(); - } - - public function testFailIfCyclicDependencyBetweenNormalizerAndLazyOption() - { - $this->expectException('Symfony\Component\OptionsResolver\Exception\OptionDefinitionException'); - $this->resolver->setDefault('lazy', function (Options $options) { - $options['norm']; - }); - - $this->resolver->setDefault('norm', 'baz'); - - $this->resolver->setNormalizer('norm', function (Options $options) { - $options['lazy']; - }); - - $this->resolver->resolve(); - } - - public function testCaughtExceptionFromNormalizerDoesNotCrashOptionResolver() - { - $throw = true; - - $this->resolver->setDefaults(['catcher' => null, 'thrower' => null]); - - $this->resolver->setNormalizer('catcher', function (Options $options) { - try { - return $options['thrower']; - } catch (\Exception $e) { - return false; - } - }); - - $this->resolver->setNormalizer('thrower', function () use (&$throw) { - if ($throw) { - $throw = false; - throw new \UnexpectedValueException('throwing'); - } - - return true; - }); - - $this->assertSame(['catcher' => false, 'thrower' => true], $this->resolver->resolve()); - } - - public function testCaughtExceptionFromLazyDoesNotCrashOptionResolver() - { - $throw = true; - - $this->resolver->setDefault('catcher', function (Options $options) { - try { - return $options['thrower']; - } catch (\Exception $e) { - return false; - } - }); - - $this->resolver->setDefault('thrower', function (Options $options) use (&$throw) { - if ($throw) { - $throw = false; - throw new \UnexpectedValueException('throwing'); - } - - return true; - }); - - $this->assertSame(['catcher' => false, 'thrower' => true], $this->resolver->resolve()); - } - - public function testInvokeEachNormalizerOnlyOnce() - { - $calls = 0; - - $this->resolver->setDefault('norm1', 'bar'); - $this->resolver->setDefault('norm2', 'baz'); - - $this->resolver->setNormalizer('norm1', function ($options) use (&$calls) { - Assert::assertSame(1, ++$calls); - - $options['norm2']; - }); - $this->resolver->setNormalizer('norm2', function () use (&$calls) { - Assert::assertSame(2, ++$calls); - }); - - $this->resolver->resolve(); - - $this->assertSame(2, $calls); - } - - public function testNormalizerNotCalledForUnsetOptions() - { - $this->resolver->setDefined('norm'); - - $this->resolver->setNormalizer('norm', function () { - Assert::fail('Should not be called.'); - }); - - $this->assertEmpty($this->resolver->resolve()); - } - - public function testSetDefaultsReturnsThis() - { - $this->assertSame($this->resolver, $this->resolver->setDefaults(['foo', 'bar'])); - } - - public function testSetDefaults() - { - $this->resolver->setDefault('one', '1'); - $this->resolver->setDefault('two', 'bar'); - - $this->resolver->setDefaults([ - 'two' => '2', - 'three' => '3', - ]); - - $this->assertEquals([ - 'one' => '1', - 'two' => '2', - 'three' => '3', - ], $this->resolver->resolve()); - } - - public function testFailIfSetDefaultsFromLazyOption() - { - $this->expectException('Symfony\Component\OptionsResolver\Exception\AccessException'); - $this->resolver->setDefault('foo', function (Options $options) { - $options->setDefaults(['two' => '2']); - }); - - $this->resolver->resolve(); - } - - public function testRemoveReturnsThis() - { - $this->resolver->setDefault('foo', 'bar'); - - $this->assertSame($this->resolver, $this->resolver->remove('foo')); - } - - public function testRemoveSingleOption() - { - $this->resolver->setDefault('foo', 'bar'); - $this->resolver->setDefault('baz', 'boo'); - $this->resolver->remove('foo'); - - $this->assertSame(['baz' => 'boo'], $this->resolver->resolve()); - } - - public function testRemoveMultipleOptions() - { - $this->resolver->setDefault('foo', 'bar'); - $this->resolver->setDefault('baz', 'boo'); - $this->resolver->setDefault('doo', 'dam'); - - $this->resolver->remove(['foo', 'doo']); - - $this->assertSame(['baz' => 'boo'], $this->resolver->resolve()); - } - - public function testRemoveLazyOption() - { - $this->resolver->setDefault('foo', function (Options $options) { - return 'lazy'; - }); - $this->resolver->remove('foo'); - - $this->assertSame([], $this->resolver->resolve()); - } - - public function testRemoveNormalizer() - { - $this->resolver->setDefault('foo', 'bar'); - $this->resolver->setNormalizer('foo', function (Options $options, $value) { - return 'normalized'; - }); - $this->resolver->remove('foo'); - $this->resolver->setDefault('foo', 'bar'); - - $this->assertSame(['foo' => 'bar'], $this->resolver->resolve()); - } - - public function testRemoveAllowedTypes() - { - $this->resolver->setDefault('foo', 'bar'); - $this->resolver->setAllowedTypes('foo', 'int'); - $this->resolver->remove('foo'); - $this->resolver->setDefault('foo', 'bar'); - - $this->assertSame(['foo' => 'bar'], $this->resolver->resolve()); - } - - public function testRemoveAllowedValues() - { - $this->resolver->setDefault('foo', 'bar'); - $this->resolver->setAllowedValues('foo', ['baz', 'boo']); - $this->resolver->remove('foo'); - $this->resolver->setDefault('foo', 'bar'); - - $this->assertSame(['foo' => 'bar'], $this->resolver->resolve()); - } - - public function testFailIfRemoveFromLazyOption() - { - $this->expectException('Symfony\Component\OptionsResolver\Exception\AccessException'); - $this->resolver->setDefault('foo', function (Options $options) { - $options->remove('bar'); - }); - - $this->resolver->setDefault('bar', 'baz'); - - $this->resolver->resolve(); - } - - public function testRemoveUnknownOptionIgnored() - { - $this->assertNotNull($this->resolver->remove('foo')); - } - - public function testClearReturnsThis() - { - $this->assertSame($this->resolver, $this->resolver->clear()); - } - - public function testClearRemovesAllOptions() - { - $this->resolver->setDefault('one', 1); - $this->resolver->setDefault('two', 2); - - $this->resolver->clear(); - - $this->assertEmpty($this->resolver->resolve()); - } - - public function testClearLazyOption() - { - $this->resolver->setDefault('foo', function (Options $options) { - return 'lazy'; - }); - $this->resolver->clear(); - - $this->assertSame([], $this->resolver->resolve()); - } - - public function testClearNormalizer() - { - $this->resolver->setDefault('foo', 'bar'); - $this->resolver->setNormalizer('foo', function (Options $options, $value) { - return 'normalized'; - }); - $this->resolver->clear(); - $this->resolver->setDefault('foo', 'bar'); - - $this->assertSame(['foo' => 'bar'], $this->resolver->resolve()); - } - - public function testClearAllowedTypes() - { - $this->resolver->setDefault('foo', 'bar'); - $this->resolver->setAllowedTypes('foo', 'int'); - $this->resolver->clear(); - $this->resolver->setDefault('foo', 'bar'); - - $this->assertSame(['foo' => 'bar'], $this->resolver->resolve()); - } - - public function testClearAllowedValues() - { - $this->resolver->setDefault('foo', 'bar'); - $this->resolver->setAllowedValues('foo', 'baz'); - $this->resolver->clear(); - $this->resolver->setDefault('foo', 'bar'); - - $this->assertSame(['foo' => 'bar'], $this->resolver->resolve()); - } - - public function testFailIfClearFromLazyption() - { - $this->expectException('Symfony\Component\OptionsResolver\Exception\AccessException'); - $this->resolver->setDefault('foo', function (Options $options) { - $options->clear(); - }); - - $this->resolver->setDefault('bar', 'baz'); - - $this->resolver->resolve(); - } - - public function testClearOptionAndNormalizer() - { - $this->resolver->setDefault('foo1', 'bar'); - $this->resolver->setNormalizer('foo1', function (Options $options) { - return ''; - }); - $this->resolver->setDefault('foo2', 'bar'); - $this->resolver->setNormalizer('foo2', function (Options $options) { - return ''; - }); - - $this->resolver->clear(); - $this->assertEmpty($this->resolver->resolve()); - } - - public function testArrayAccess() - { - $this->resolver->setDefault('default1', 0); - $this->resolver->setDefault('default2', 1); - $this->resolver->setRequired('required'); - $this->resolver->setDefined('defined'); - $this->resolver->setDefault('lazy1', function (Options $options) { - return 'lazy'; - }); - - $this->resolver->setDefault('lazy2', function (Options $options) { - Assert::assertArrayHasKey('default1', $options); - Assert::assertArrayHasKey('default2', $options); - Assert::assertArrayHasKey('required', $options); - Assert::assertArrayHasKey('lazy1', $options); - Assert::assertArrayHasKey('lazy2', $options); - Assert::assertArrayNotHasKey('defined', $options); - - Assert::assertSame(0, $options['default1']); - Assert::assertSame(42, $options['default2']); - Assert::assertSame('value', $options['required']); - Assert::assertSame('lazy', $options['lazy1']); - - // Obviously $options['lazy'] and $options['defined'] cannot be - // accessed - }); - - $this->resolver->resolve(['default2' => 42, 'required' => 'value']); - } - - public function testArrayAccessGetFailsOutsideResolve() - { - $this->expectException('Symfony\Component\OptionsResolver\Exception\AccessException'); - $this->resolver->setDefault('default', 0); - - $this->resolver['default']; - } - - public function testArrayAccessExistsFailsOutsideResolve() - { - $this->expectException('Symfony\Component\OptionsResolver\Exception\AccessException'); - $this->resolver->setDefault('default', 0); - - isset($this->resolver['default']); - } - - public function testArrayAccessSetNotSupported() - { - $this->expectException('Symfony\Component\OptionsResolver\Exception\AccessException'); - $this->resolver['default'] = 0; - } - - public function testArrayAccessUnsetNotSupported() - { - $this->expectException('Symfony\Component\OptionsResolver\Exception\AccessException'); - $this->resolver->setDefault('default', 0); - - unset($this->resolver['default']); - } - - public function testFailIfGetNonExisting() - { - $this->expectException('Symfony\Component\OptionsResolver\Exception\NoSuchOptionException'); - $this->expectExceptionMessage('The option "undefined" does not exist. Defined options are: "foo", "lazy".'); - $this->resolver->setDefault('foo', 'bar'); - - $this->resolver->setDefault('lazy', function (Options $options) { - $options['undefined']; - }); - - $this->resolver->resolve(); - } - - public function testFailIfGetDefinedButUnset() - { - $this->expectException('Symfony\Component\OptionsResolver\Exception\NoSuchOptionException'); - $this->expectExceptionMessage('The optional option "defined" has no value set. You should make sure it is set with "isset" before reading it.'); - $this->resolver->setDefined('defined'); - - $this->resolver->setDefault('lazy', function (Options $options) { - $options['defined']; - }); - - $this->resolver->resolve(); - } - - public function testFailIfCyclicDependency() - { - $this->expectException('Symfony\Component\OptionsResolver\Exception\OptionDefinitionException'); - $this->resolver->setDefault('lazy1', function (Options $options) { - $options['lazy2']; - }); - - $this->resolver->setDefault('lazy2', function (Options $options) { - $options['lazy1']; - }); - - $this->resolver->resolve(); - } - - public function testCount() - { - $this->resolver->setDefault('default', 0); - $this->resolver->setRequired('required'); - $this->resolver->setDefined('defined'); - $this->resolver->setDefault('lazy1', function () {}); - - $this->resolver->setDefault('lazy2', function (Options $options) { - Assert::assertCount(4, $options); - }); - - $this->assertCount(4, $this->resolver->resolve(['required' => 'value'])); - } - - /** - * In resolve() we count the options that are actually set (which may be - * only a subset of the defined options). Outside of resolve(), it's not - * clear what is counted. - */ - public function testCountFailsOutsideResolve() - { - $this->expectException('Symfony\Component\OptionsResolver\Exception\AccessException'); - $this->resolver->setDefault('foo', 0); - $this->resolver->setRequired('bar'); - $this->resolver->setDefined('bar'); - $this->resolver->setDefault('lazy1', function () {}); - - \count($this->resolver); - } - - public function testNestedArrays() - { - $this->resolver->setDefined('foo'); - $this->resolver->setAllowedTypes('foo', 'int[][]'); - - $this->assertEquals([ - 'foo' => [ - [ - 1, 2, - ], - ], - ], $this->resolver->resolve([ - 'foo' => [ - [1, 2], - ], - ])); - } - - public function testNested2Arrays() - { - $this->resolver->setDefined('foo'); - $this->resolver->setAllowedTypes('foo', 'int[][][][]'); - - $this->assertEquals([ - 'foo' => [ - [ - [ - [ - 1, 2, - ], - ], - ], - ], - ], $this->resolver->resolve( - [ - 'foo' => [ - [ - [ - [1, 2], - ], - ], - ], - ] - )); - } - - public function testNestedArraysException() - { - $this->expectException('Symfony\Component\OptionsResolver\Exception\InvalidOptionsException'); - $this->expectExceptionMessage('The option "foo" with value array is expected to be of type "float[][][][]", but one of the elements is of type "integer".'); - $this->resolver->setDefined('foo'); - $this->resolver->setAllowedTypes('foo', 'float[][][][]'); - - $this->resolver->resolve([ - 'foo' => [ - [ - [ - [1, 2], - ], - ], - ], - ]); - } - - public function testNestedArrayException1() - { - $this->expectException('Symfony\Component\OptionsResolver\Exception\InvalidOptionsException'); - $this->expectExceptionMessage('The option "foo" with value array is expected to be of type "int[][]", but one of the elements is of type "boolean|string|array".'); - $this->resolver->setDefined('foo'); - $this->resolver->setAllowedTypes('foo', 'int[][]'); - $this->resolver->resolve([ - 'foo' => [ - [1, true, 'str', [2, 3]], - ], - ]); - } - - public function testNestedArrayException2() - { - $this->expectException('Symfony\Component\OptionsResolver\Exception\InvalidOptionsException'); - $this->expectExceptionMessage('The option "foo" with value array is expected to be of type "int[][]", but one of the elements is of type "boolean|string|array".'); - $this->resolver->setDefined('foo'); - $this->resolver->setAllowedTypes('foo', 'int[][]'); - $this->resolver->resolve([ - 'foo' => [ - [true, 'str', [2, 3]], - ], - ]); - } - - public function testNestedArrayException3() - { - $this->expectException('Symfony\Component\OptionsResolver\Exception\InvalidOptionsException'); - $this->expectExceptionMessage('The option "foo" with value array is expected to be of type "string[][][]", but one of the elements is of type "string|integer".'); - $this->resolver->setDefined('foo'); - $this->resolver->setAllowedTypes('foo', 'string[][][]'); - $this->resolver->resolve([ - 'foo' => [ - ['str', [1, 2]], - ], - ]); - } - - public function testNestedArrayException4() - { - $this->expectException('Symfony\Component\OptionsResolver\Exception\InvalidOptionsException'); - $this->expectExceptionMessage('The option "foo" with value array is expected to be of type "string[][][]", but one of the elements is of type "integer".'); - $this->resolver->setDefined('foo'); - $this->resolver->setAllowedTypes('foo', 'string[][][]'); - $this->resolver->resolve([ - 'foo' => [ - [ - ['str'], [1, 2], ], - ], - ]); - } - - public function testNestedArrayException5() - { - $this->expectException('Symfony\Component\OptionsResolver\Exception\InvalidOptionsException'); - $this->expectExceptionMessage('The option "foo" with value array is expected to be of type "string[]", but one of the elements is of type "array".'); - $this->resolver->setDefined('foo'); - $this->resolver->setAllowedTypes('foo', 'string[]'); - $this->resolver->resolve([ - 'foo' => [ - [ - ['str'], [1, 2], ], - ], - ]); - } -} diff --git a/vendor/symfony/polyfill-intl-idn/Idn.php b/vendor/symfony/polyfill-intl-idn/Idn.php index bccea3ec..fee3026d 100644 --- a/vendor/symfony/polyfill-intl-idn/Idn.php +++ b/vendor/symfony/polyfill-intl-idn/Idn.php @@ -23,45 +23,45 @@ */ final class Idn { - const ERROR_EMPTY_LABEL = 1; - const ERROR_LABEL_TOO_LONG = 2; - const ERROR_DOMAIN_NAME_TOO_LONG = 4; - const ERROR_LEADING_HYPHEN = 8; - const ERROR_TRAILING_HYPHEN = 0x10; - const ERROR_HYPHEN_3_4 = 0x20; - const ERROR_LEADING_COMBINING_MARK = 0x40; - const ERROR_DISALLOWED = 0x80; - const ERROR_PUNYCODE = 0x100; - const ERROR_LABEL_HAS_DOT = 0x200; - const ERROR_INVALID_ACE_LABEL = 0x400; - const ERROR_BIDI = 0x800; - const ERROR_CONTEXTJ = 0x1000; - const ERROR_CONTEXTO_PUNCTUATION = 0x2000; - const ERROR_CONTEXTO_DIGITS = 0x4000; - - const INTL_IDNA_VARIANT_2003 = 0; - const INTL_IDNA_VARIANT_UTS46 = 1; - - const IDNA_DEFAULT = 0; - const IDNA_ALLOW_UNASSIGNED = 1; - const IDNA_USE_STD3_RULES = 2; - const IDNA_CHECK_BIDI = 4; - const IDNA_CHECK_CONTEXTJ = 8; - const IDNA_NONTRANSITIONAL_TO_ASCII = 16; - const IDNA_NONTRANSITIONAL_TO_UNICODE = 32; - - const MAX_DOMAIN_SIZE = 253; - const MAX_LABEL_SIZE = 63; - - const BASE = 36; - const TMIN = 1; - const TMAX = 26; - const SKEW = 38; - const DAMP = 700; - const INITIAL_BIAS = 72; - const INITIAL_N = 128; - const DELIMITER = '-'; - const MAX_INT = 2147483647; + public const ERROR_EMPTY_LABEL = 1; + public const ERROR_LABEL_TOO_LONG = 2; + public const ERROR_DOMAIN_NAME_TOO_LONG = 4; + public const ERROR_LEADING_HYPHEN = 8; + public const ERROR_TRAILING_HYPHEN = 0x10; + public const ERROR_HYPHEN_3_4 = 0x20; + public const ERROR_LEADING_COMBINING_MARK = 0x40; + public const ERROR_DISALLOWED = 0x80; + public const ERROR_PUNYCODE = 0x100; + public const ERROR_LABEL_HAS_DOT = 0x200; + public const ERROR_INVALID_ACE_LABEL = 0x400; + public const ERROR_BIDI = 0x800; + public const ERROR_CONTEXTJ = 0x1000; + public const ERROR_CONTEXTO_PUNCTUATION = 0x2000; + public const ERROR_CONTEXTO_DIGITS = 0x4000; + + public const INTL_IDNA_VARIANT_2003 = 0; + public const INTL_IDNA_VARIANT_UTS46 = 1; + + public const IDNA_DEFAULT = 0; + public const IDNA_ALLOW_UNASSIGNED = 1; + public const IDNA_USE_STD3_RULES = 2; + public const IDNA_CHECK_BIDI = 4; + public const IDNA_CHECK_CONTEXTJ = 8; + public const IDNA_NONTRANSITIONAL_TO_ASCII = 16; + public const IDNA_NONTRANSITIONAL_TO_UNICODE = 32; + + public const MAX_DOMAIN_SIZE = 253; + public const MAX_LABEL_SIZE = 63; + + public const BASE = 36; + public const TMIN = 1; + public const TMAX = 26; + public const SKEW = 38; + public const DAMP = 700; + public const INITIAL_BIAS = 72; + public const INITIAL_N = 128; + public const DELIMITER = '-'; + public const MAX_INT = 2147483647; /** * Contains the numeric value of a basic code point (for use in representing integers) in the @@ -69,7 +69,7 @@ final class Idn * * @var array */ - private static $basicToDigit = array( + private static $basicToDigit = [ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, @@ -93,7 +93,7 @@ final class Idn -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - ); + ]; /** * @var array @@ -145,20 +145,20 @@ final class Idn * * @return string|false */ - public static function idn_to_ascii($domainName, $options = self::IDNA_DEFAULT, $variant = self::INTL_IDNA_VARIANT_UTS46, &$idna_info = array()) + public static function idn_to_ascii($domainName, $options = self::IDNA_DEFAULT, $variant = self::INTL_IDNA_VARIANT_UTS46, &$idna_info = []) { if (\PHP_VERSION_ID >= 70200 && self::INTL_IDNA_VARIANT_2003 === $variant) { - @trigger_error('idn_to_ascii(): INTL_IDNA_VARIANT_2003 is deprecated', E_USER_DEPRECATED); + @trigger_error('idn_to_ascii(): INTL_IDNA_VARIANT_2003 is deprecated', \E_USER_DEPRECATED); } - $options = array( + $options = [ 'CheckHyphens' => true, 'CheckBidi' => self::INTL_IDNA_VARIANT_2003 === $variant || 0 !== ($options & self::IDNA_CHECK_BIDI), 'CheckJoiners' => self::INTL_IDNA_VARIANT_UTS46 === $variant && 0 !== ($options & self::IDNA_CHECK_CONTEXTJ), 'UseSTD3ASCIIRules' => 0 !== ($options & self::IDNA_USE_STD3_RULES), 'Transitional_Processing' => self::INTL_IDNA_VARIANT_2003 === $variant || 0 === ($options & self::IDNA_NONTRANSITIONAL_TO_ASCII), 'VerifyDnsLength' => true, - ); + ]; $info = new Info(); $labels = self::process((string) $domainName, $options, $info); @@ -179,11 +179,11 @@ public static function idn_to_ascii($domainName, $options = self::IDNA_DEFAULT, self::validateDomainAndLabelLength($labels, $info); } - $idna_info = array( + $idna_info = [ 'result' => implode('.', $labels), 'isTransitionalDifferent' => $info->transitionalDifferent, 'errors' => $info->errors, - ); + ]; return 0 === $info->errors ? $idna_info['result'] : false; } @@ -198,25 +198,25 @@ public static function idn_to_ascii($domainName, $options = self::IDNA_DEFAULT, * * @return string|false */ - public static function idn_to_utf8($domainName, $options = self::IDNA_DEFAULT, $variant = self::INTL_IDNA_VARIANT_UTS46, &$idna_info = array()) + public static function idn_to_utf8($domainName, $options = self::IDNA_DEFAULT, $variant = self::INTL_IDNA_VARIANT_UTS46, &$idna_info = []) { if (\PHP_VERSION_ID >= 70200 && self::INTL_IDNA_VARIANT_2003 === $variant) { - @trigger_error('idn_to_utf8(): INTL_IDNA_VARIANT_2003 is deprecated', E_USER_DEPRECATED); + @trigger_error('idn_to_utf8(): INTL_IDNA_VARIANT_2003 is deprecated', \E_USER_DEPRECATED); } $info = new Info(); - $labels = self::process((string) $domainName, array( + $labels = self::process((string) $domainName, [ 'CheckHyphens' => true, 'CheckBidi' => self::INTL_IDNA_VARIANT_2003 === $variant || 0 !== ($options & self::IDNA_CHECK_BIDI), 'CheckJoiners' => self::INTL_IDNA_VARIANT_UTS46 === $variant && 0 !== ($options & self::IDNA_CHECK_CONTEXTJ), 'UseSTD3ASCIIRules' => 0 !== ($options & self::IDNA_USE_STD3_RULES), 'Transitional_Processing' => self::INTL_IDNA_VARIANT_2003 === $variant || 0 === ($options & self::IDNA_NONTRANSITIONAL_TO_UNICODE), - ), $info); - $idna_info = array( + ], $info); + $idna_info = [ 'result' => implode('.', $labels), 'isTransitionalDifferent' => $info->transitionalDifferent, 'errors' => $info->errors, - ); + ]; return 0 === $info->errors ? $idna_info['result'] : false; } @@ -251,7 +251,7 @@ private static function isValidContextJ(array $codePoints, $label) // If RegExpMatch((Joining_Type:{L,D})(Joining_Type:T)*\u200C(Joining_Type:T)*(Joining_Type:{R,D})) Then // True; // Generated RegExp = ([Joining_Type:{L,D}][Joining_Type:T]*\u200C[Joining_Type:T]*)[Joining_Type:{R,D}] - if (0x200C === $codePoint && 1 === preg_match(Regex::ZWNJ, $label, $matches, PREG_OFFSET_CAPTURE, $offset)) { + if (0x200C === $codePoint && 1 === preg_match(Regex::ZWNJ, $label, $matches, \PREG_OFFSET_CAPTURE, $offset)) { $offset += \strlen($matches[1][0]); continue; @@ -328,7 +328,7 @@ private static function process($domain, array $options, Info $info) if ($checkForEmptyLabels && '' === $domain) { $info->errors |= self::ERROR_EMPTY_LABEL; - return array($domain); + return [$domain]; } // Step 1. Map each code point in the domain name string @@ -578,7 +578,7 @@ private static function punycodeDecode($input) $lastDelimIndex = strrpos($input, self::DELIMITER); $b = false === $lastDelimIndex ? 0 : $lastDelimIndex; $inputLength = \strlen($input); - $output = array(); + $output = []; $bytes = array_map('ord', str_split($input)); for ($j = 0; $j < $b; ++$j) { @@ -644,7 +644,7 @@ private static function punycodeDecode($input) $n += intdiv($i, $outPlusOne); $i %= $outPlusOne; - array_splice($output, $i++, 0, array(mb_chr($n, 'utf-8'))); + array_splice($output, $i++, 0, [mb_chr($n, 'utf-8')]); } return implode('', $output); @@ -703,7 +703,9 @@ private static function punycodeEncode($input) foreach ($iter as $codePoint) { if ($codePoint < $n && 0 === ++$delta) { throw new Exception('Integer overflow'); - } elseif ($codePoint === $n) { + } + + if ($codePoint === $n) { $q = $delta; for ($k = self::BASE; /* no condition */; $k += self::BASE) { @@ -793,7 +795,7 @@ private static function utf8Decode($input) $lowerBoundary = 0x80; $upperBoundary = 0xBF; $codePoint = 0; - $codePoints = array(); + $codePoints = []; $length = \strlen($input); for ($i = 0; $i < $length; ++$i) { @@ -887,19 +889,19 @@ private static function lookupCodePointStatus($codePoint, $useSTD3ASCIIRules) } if (isset(self::$mapped[$codePoint])) { - return array('status' => 'mapped', 'mapping' => self::$mapped[$codePoint]); + return ['status' => 'mapped', 'mapping' => self::$mapped[$codePoint]]; } if (isset(self::$ignored[$codePoint])) { - return array('status' => 'ignored'); + return ['status' => 'ignored']; } if (isset(self::$deviation[$codePoint])) { - return array('status' => 'deviation', 'mapping' => self::$deviation[$codePoint]); + return ['status' => 'deviation', 'mapping' => self::$deviation[$codePoint]]; } if (isset(self::$disallowed[$codePoint]) || DisallowedRanges::inRange($codePoint)) { - return array('status' => 'disallowed'); + return ['status' => 'disallowed']; } $isDisallowedMapped = isset(self::$disallowed_STD3_mapped[$codePoint]); @@ -912,12 +914,12 @@ private static function lookupCodePointStatus($codePoint, $useSTD3ASCIIRules) } if ($isDisallowedMapped) { - return array('status' => $status, 'mapping' => self::$disallowed_STD3_mapped[$codePoint]); + return ['status' => $status, 'mapping' => self::$disallowed_STD3_mapped[$codePoint]]; } - return array('status' => $status); + return ['status' => $status]; } - return array('status' => 'valid'); + return ['status' => 'valid']; } } diff --git a/vendor/symfony/polyfill-intl-idn/bootstrap.php b/vendor/symfony/polyfill-intl-idn/bootstrap.php index f02d5de7..57c78356 100644 --- a/vendor/symfony/polyfill-intl-idn/bootstrap.php +++ b/vendor/symfony/polyfill-intl-idn/bootstrap.php @@ -15,6 +15,10 @@ return; } +if (\PHP_VERSION_ID >= 80000) { + return require __DIR__.'/bootstrap80.php'; +} + if (!defined('U_IDNA_PROHIBITED_ERROR')) { define('U_IDNA_PROHIBITED_ERROR', 66560); } @@ -124,18 +128,18 @@ define('IDNA_ERROR_CONTEXTJ', 4096); } -if (PHP_VERSION_ID < 70400) { +if (\PHP_VERSION_ID < 70400) { if (!function_exists('idn_to_ascii')) { - function idn_to_ascii($domain, $options = IDNA_DEFAULT, $variant = INTL_IDNA_VARIANT_2003, &$idna_info = array()) { return p\Idn::idn_to_ascii($domain, $options, $variant, $idna_info); } + function idn_to_ascii($domain, $flags = 0, $variant = \INTL_IDNA_VARIANT_2003, &$idna_info = null) { return p\Idn::idn_to_ascii($domain, $flags, $variant, $idna_info); } } if (!function_exists('idn_to_utf8')) { - function idn_to_utf8($domain, $options = IDNA_DEFAULT, $variant = INTL_IDNA_VARIANT_2003, &$idna_info = array()) { return p\Idn::idn_to_utf8($domain, $options, $variant, $idna_info); } + function idn_to_utf8($domain, $flags = 0, $variant = \INTL_IDNA_VARIANT_2003, &$idna_info = null) { return p\Idn::idn_to_utf8($domain, $flags, $variant, $idna_info); } } } else { if (!function_exists('idn_to_ascii')) { - function idn_to_ascii($domain, $options = IDNA_DEFAULT, $variant = INTL_IDNA_VARIANT_UTS46, &$idna_info = array()) { return p\Idn::idn_to_ascii($domain, $options, $variant, $idna_info); } + function idn_to_ascii($domain, $flags = 0, $variant = \INTL_IDNA_VARIANT_UTS46, &$idna_info = null) { return p\Idn::idn_to_ascii($domain, $flags, $variant, $idna_info); } } if (!function_exists('idn_to_utf8')) { - function idn_to_utf8($domain, $options = IDNA_DEFAULT, $variant = INTL_IDNA_VARIANT_UTS46, &$idna_info = array()) { return p\Idn::idn_to_utf8($domain, $options, $variant, $idna_info); } + function idn_to_utf8($domain, $flags = 0, $variant = \INTL_IDNA_VARIANT_UTS46, &$idna_info = null) { return p\Idn::idn_to_utf8($domain, $flags, $variant, $idna_info); } } } diff --git a/vendor/symfony/polyfill-intl-idn/bootstrap80.php b/vendor/symfony/polyfill-intl-idn/bootstrap80.php new file mode 100644 index 00000000..a62c2d69 --- /dev/null +++ b/vendor/symfony/polyfill-intl-idn/bootstrap80.php @@ -0,0 +1,125 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Intl\Idn as p; + +if (!defined('U_IDNA_PROHIBITED_ERROR')) { + define('U_IDNA_PROHIBITED_ERROR', 66560); +} +if (!defined('U_IDNA_ERROR_START')) { + define('U_IDNA_ERROR_START', 66560); +} +if (!defined('U_IDNA_UNASSIGNED_ERROR')) { + define('U_IDNA_UNASSIGNED_ERROR', 66561); +} +if (!defined('U_IDNA_CHECK_BIDI_ERROR')) { + define('U_IDNA_CHECK_BIDI_ERROR', 66562); +} +if (!defined('U_IDNA_STD3_ASCII_RULES_ERROR')) { + define('U_IDNA_STD3_ASCII_RULES_ERROR', 66563); +} +if (!defined('U_IDNA_ACE_PREFIX_ERROR')) { + define('U_IDNA_ACE_PREFIX_ERROR', 66564); +} +if (!defined('U_IDNA_VERIFICATION_ERROR')) { + define('U_IDNA_VERIFICATION_ERROR', 66565); +} +if (!defined('U_IDNA_LABEL_TOO_LONG_ERROR')) { + define('U_IDNA_LABEL_TOO_LONG_ERROR', 66566); +} +if (!defined('U_IDNA_ZERO_LENGTH_LABEL_ERROR')) { + define('U_IDNA_ZERO_LENGTH_LABEL_ERROR', 66567); +} +if (!defined('U_IDNA_DOMAIN_NAME_TOO_LONG_ERROR')) { + define('U_IDNA_DOMAIN_NAME_TOO_LONG_ERROR', 66568); +} +if (!defined('U_IDNA_ERROR_LIMIT')) { + define('U_IDNA_ERROR_LIMIT', 66569); +} +if (!defined('U_STRINGPREP_PROHIBITED_ERROR')) { + define('U_STRINGPREP_PROHIBITED_ERROR', 66560); +} +if (!defined('U_STRINGPREP_UNASSIGNED_ERROR')) { + define('U_STRINGPREP_UNASSIGNED_ERROR', 66561); +} +if (!defined('U_STRINGPREP_CHECK_BIDI_ERROR')) { + define('U_STRINGPREP_CHECK_BIDI_ERROR', 66562); +} +if (!defined('IDNA_DEFAULT')) { + define('IDNA_DEFAULT', 0); +} +if (!defined('IDNA_ALLOW_UNASSIGNED')) { + define('IDNA_ALLOW_UNASSIGNED', 1); +} +if (!defined('IDNA_USE_STD3_RULES')) { + define('IDNA_USE_STD3_RULES', 2); +} +if (!defined('IDNA_CHECK_BIDI')) { + define('IDNA_CHECK_BIDI', 4); +} +if (!defined('IDNA_CHECK_CONTEXTJ')) { + define('IDNA_CHECK_CONTEXTJ', 8); +} +if (!defined('IDNA_NONTRANSITIONAL_TO_ASCII')) { + define('IDNA_NONTRANSITIONAL_TO_ASCII', 16); +} +if (!defined('IDNA_NONTRANSITIONAL_TO_UNICODE')) { + define('IDNA_NONTRANSITIONAL_TO_UNICODE', 32); +} +if (!defined('INTL_IDNA_VARIANT_UTS46')) { + define('INTL_IDNA_VARIANT_UTS46', 1); +} +if (!defined('IDNA_ERROR_EMPTY_LABEL')) { + define('IDNA_ERROR_EMPTY_LABEL', 1); +} +if (!defined('IDNA_ERROR_LABEL_TOO_LONG')) { + define('IDNA_ERROR_LABEL_TOO_LONG', 2); +} +if (!defined('IDNA_ERROR_DOMAIN_NAME_TOO_LONG')) { + define('IDNA_ERROR_DOMAIN_NAME_TOO_LONG', 4); +} +if (!defined('IDNA_ERROR_LEADING_HYPHEN')) { + define('IDNA_ERROR_LEADING_HYPHEN', 8); +} +if (!defined('IDNA_ERROR_TRAILING_HYPHEN')) { + define('IDNA_ERROR_TRAILING_HYPHEN', 16); +} +if (!defined('IDNA_ERROR_HYPHEN_3_4')) { + define('IDNA_ERROR_HYPHEN_3_4', 32); +} +if (!defined('IDNA_ERROR_LEADING_COMBINING_MARK')) { + define('IDNA_ERROR_LEADING_COMBINING_MARK', 64); +} +if (!defined('IDNA_ERROR_DISALLOWED')) { + define('IDNA_ERROR_DISALLOWED', 128); +} +if (!defined('IDNA_ERROR_PUNYCODE')) { + define('IDNA_ERROR_PUNYCODE', 256); +} +if (!defined('IDNA_ERROR_LABEL_HAS_DOT')) { + define('IDNA_ERROR_LABEL_HAS_DOT', 512); +} +if (!defined('IDNA_ERROR_INVALID_ACE_LABEL')) { + define('IDNA_ERROR_INVALID_ACE_LABEL', 1024); +} +if (!defined('IDNA_ERROR_BIDI')) { + define('IDNA_ERROR_BIDI', 2048); +} +if (!defined('IDNA_ERROR_CONTEXTJ')) { + define('IDNA_ERROR_CONTEXTJ', 4096); +} + +if (!function_exists('idn_to_ascii')) { + function idn_to_ascii(?string $domain, ?int $flags = IDNA_DEFAULT, ?int $variant = INTL_IDNA_VARIANT_UTS46, &$idna_info = null): string|false { return p\Idn::idn_to_ascii((string) $domain, (int) $flags, (int) $variant, $idna_info); } +} +if (!function_exists('idn_to_utf8')) { + function idn_to_utf8(?string $domain, ?int $flags = IDNA_DEFAULT, ?int $variant = INTL_IDNA_VARIANT_UTS46, &$idna_info = null): string|false { return p\Idn::idn_to_utf8((string) $domain, (int) $flags, (int) $variant, $idna_info); } +} diff --git a/vendor/symfony/polyfill-intl-normalizer/Normalizer.php b/vendor/symfony/polyfill-intl-normalizer/Normalizer.php index a60fae62..4443c232 100644 --- a/vendor/symfony/polyfill-intl-normalizer/Normalizer.php +++ b/vendor/symfony/polyfill-intl-normalizer/Normalizer.php @@ -23,28 +23,27 @@ */ class Normalizer { - const FORM_D = \Normalizer::FORM_D; - const FORM_KD = \Normalizer::FORM_KD; - const FORM_C = \Normalizer::FORM_C; - const FORM_KC = \Normalizer::FORM_KC; - const NFD = \Normalizer::NFD; - const NFKD = \Normalizer::NFKD; - const NFC = \Normalizer::NFC; - const NFKC = \Normalizer::NFKC; + public const FORM_D = \Normalizer::FORM_D; + public const FORM_KD = \Normalizer::FORM_KD; + public const FORM_C = \Normalizer::FORM_C; + public const FORM_KC = \Normalizer::FORM_KC; + public const NFD = \Normalizer::NFD; + public const NFKD = \Normalizer::NFKD; + public const NFC = \Normalizer::NFC; + public const NFKC = \Normalizer::NFKC; private static $C; private static $D; private static $KD; private static $cC; - private static $ulenMask = array("\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4); + private static $ulenMask = ["\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4]; private static $ASCII = "\x20\x65\x69\x61\x73\x6E\x74\x72\x6F\x6C\x75\x64\x5D\x5B\x63\x6D\x70\x27\x0A\x67\x7C\x68\x76\x2E\x66\x62\x2C\x3A\x3D\x2D\x71\x31\x30\x43\x32\x2A\x79\x78\x29\x28\x4C\x39\x41\x53\x2F\x50\x22\x45\x6A\x4D\x49\x6B\x33\x3E\x35\x54\x3C\x44\x34\x7D\x42\x7B\x38\x46\x77\x52\x36\x37\x55\x47\x4E\x3B\x4A\x7A\x56\x23\x48\x4F\x57\x5F\x26\x21\x4B\x3F\x58\x51\x25\x59\x5C\x09\x5A\x2B\x7E\x5E\x24\x40\x60\x7F\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F"; - public static function isNormalized($s, $form = self::NFC) + public static function isNormalized(string $s, int $form = self::FORM_C) { - if (!\in_array($form, array(self::NFD, self::NFKD, self::NFC, self::NFKC))) { + if (!\in_array($form, [self::NFD, self::NFKD, self::NFC, self::NFKC])) { return false; } - $s = (string) $s; if (!isset($s[strspn($s, self::$ASCII)])) { return true; } @@ -55,9 +54,8 @@ public static function isNormalized($s, $form = self::NFC) return self::normalize($s, $form) === $s; } - public static function normalize($s, $form = self::NFC) + public static function normalize(string $s, int $form = self::FORM_C) { - $s = (string) $s; if (!preg_match('//u', $s)) { return false; } @@ -72,7 +70,11 @@ public static function normalize($s, $form = self::NFC) return $s; } - return false; + if (80000 > \PHP_VERSION_ID) { + return false; + } + + throw new \ValueError('normalizer_normalize(): Argument #2 ($form) must be a a valid normalization form'); } if ('' === $s) { @@ -152,7 +154,7 @@ private static function recompose($s) || $lastUcls) { // Table lookup and combining chars composition - $ucls = isset($combClass[$uchr]) ? $combClass[$uchr] : 0; + $ucls = $combClass[$uchr] ?? 0; if (isset($compMap[$lastUchr.$uchr]) && (!$lastUcls || $lastUcls < $ucls)) { $lastUchr = $compMap[$lastUchr.$uchr]; @@ -204,7 +206,7 @@ private static function decompose($s, $c) $compatMap = self::$KD; } - $c = array(); + $c = []; $i = 0; $len = \strlen($s); @@ -215,7 +217,7 @@ private static function decompose($s, $c) if ($c) { ksort($c); $result .= implode('', $c); - $c = array(); + $c = []; } $j = 1 + strspn($s, $ASCII, $i + 1); @@ -231,7 +233,7 @@ private static function decompose($s, $c) if ($uchr < "\xEA\xB0\x80" || "\xED\x9E\xA3" < $uchr) { // Table lookup - if ($uchr !== $j = isset($compatMap[$uchr]) ? $compatMap[$uchr] : (isset($decompMap[$uchr]) ? $decompMap[$uchr] : $uchr)) { + if ($uchr !== $j = $compatMap[$uchr] ?? ($decompMap[$uchr] ?? $uchr)) { $uchr = $j; $j = \strlen($uchr); @@ -283,7 +285,7 @@ private static function decompose($s, $c) if ($c) { ksort($c); $result .= implode('', $c); - $c = array(); + $c = []; } $result .= $uchr; diff --git a/vendor/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php b/vendor/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php index ca18eff3..0fdfc890 100644 --- a/vendor/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php +++ b/vendor/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php @@ -5,13 +5,13 @@ class Normalizer extends Symfony\Polyfill\Intl\Normalizer\Normalizer /** * @deprecated since ICU 56 and removed in PHP 8 */ - const NONE = 1; - const FORM_D = 2; - const FORM_KD = 3; - const FORM_C = 4; - const FORM_KC = 5; - const NFD = 2; - const NFKD = 3; - const NFC = 4; - const NFKC = 5; + public const NONE = 2; + public const FORM_D = 4; + public const FORM_KD = 8; + public const FORM_C = 16; + public const FORM_KC = 32; + public const NFD = 4; + public const NFKD = 8; + public const NFC = 16; + public const NFKC = 32; } diff --git a/vendor/symfony/polyfill-intl-normalizer/bootstrap.php b/vendor/symfony/polyfill-intl-normalizer/bootstrap.php index bac4318c..3608e5c0 100644 --- a/vendor/symfony/polyfill-intl-normalizer/bootstrap.php +++ b/vendor/symfony/polyfill-intl-normalizer/bootstrap.php @@ -11,9 +11,13 @@ use Symfony\Polyfill\Intl\Normalizer as p; +if (\PHP_VERSION_ID >= 80000) { + return require __DIR__.'/bootstrap80.php'; +} + if (!function_exists('normalizer_is_normalized')) { - function normalizer_is_normalized($input, $form = p\Normalizer::NFC) { return p\Normalizer::isNormalized($input, $form); } + function normalizer_is_normalized($string, $form = p\Normalizer::FORM_C) { return p\Normalizer::isNormalized($string, $form); } } if (!function_exists('normalizer_normalize')) { - function normalizer_normalize($input, $form = p\Normalizer::NFC) { return p\Normalizer::normalize($input, $form); } + function normalizer_normalize($string, $form = p\Normalizer::FORM_C) { return p\Normalizer::normalize($string, $form); } } diff --git a/vendor/symfony/polyfill-intl-normalizer/bootstrap80.php b/vendor/symfony/polyfill-intl-normalizer/bootstrap80.php new file mode 100644 index 00000000..e36d1a94 --- /dev/null +++ b/vendor/symfony/polyfill-intl-normalizer/bootstrap80.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Intl\Normalizer as p; + +if (!function_exists('normalizer_is_normalized')) { + function normalizer_is_normalized(?string $string, ?int $form = p\Normalizer::FORM_C): bool { return p\Normalizer::isNormalized((string) $string, (int) $form); } +} +if (!function_exists('normalizer_normalize')) { + function normalizer_normalize(?string $string, ?int $form = p\Normalizer::FORM_C): string|false { return p\Normalizer::normalize((string) $string, (int) $form); } +} diff --git a/vendor/symfony/polyfill-mbstring/Mbstring.php b/vendor/symfony/polyfill-mbstring/Mbstring.php index dc7801f4..693749f2 100644 --- a/vendor/symfony/polyfill-mbstring/Mbstring.php +++ b/vendor/symfony/polyfill-mbstring/Mbstring.php @@ -67,19 +67,20 @@ */ final class Mbstring { - const MB_CASE_FOLD = PHP_INT_MAX; + public const MB_CASE_FOLD = \PHP_INT_MAX; - private static $encodingList = array('ASCII', 'UTF-8'); + private const CASE_FOLD = [ + ['µ', 'ſ', "\xCD\x85", 'ς', "\xCF\x90", "\xCF\x91", "\xCF\x95", "\xCF\x96", "\xCF\xB0", "\xCF\xB1", "\xCF\xB5", "\xE1\xBA\x9B", "\xE1\xBE\xBE"], + ['μ', 's', 'ι', 'σ', 'β', 'θ', 'φ', 'π', 'κ', 'ρ', 'ε', "\xE1\xB9\xA1", 'ι'], + ]; + + private static $encodingList = ['ASCII', 'UTF-8']; private static $language = 'neutral'; private static $internalEncoding = 'UTF-8'; - private static $caseFold = array( - array('µ', 'ſ', "\xCD\x85", 'ς', "\xCF\x90", "\xCF\x91", "\xCF\x95", "\xCF\x96", "\xCF\xB0", "\xCF\xB1", "\xCF\xB5", "\xE1\xBA\x9B", "\xE1\xBE\xBE"), - array('μ', 's', 'ι', 'σ', 'β', 'θ', 'φ', 'π', 'κ', 'ρ', 'ε', "\xE1\xB9\xA1", 'ι'), - ); public static function mb_convert_encoding($s, $toEncoding, $fromEncoding = null) { - if (\is_array($fromEncoding) || false !== strpos($fromEncoding, ',')) { + if (\is_array($fromEncoding) || ($fromEncoding !== null && false !== strpos($fromEncoding, ','))) { $fromEncoding = self::mb_detect_encoding($s, $fromEncoding); } else { $fromEncoding = self::getEncoding($fromEncoding); @@ -101,27 +102,25 @@ public static function mb_convert_encoding($s, $toEncoding, $fromEncoding = null $fromEncoding = 'Windows-1252'; } if ('UTF-8' !== $fromEncoding) { - $s = iconv($fromEncoding, 'UTF-8//IGNORE', $s); + $s = \iconv($fromEncoding, 'UTF-8//IGNORE', $s); } - return preg_replace_callback('/[\x80-\xFF]+/', array(__CLASS__, 'html_encoding_callback'), $s); + return preg_replace_callback('/[\x80-\xFF]+/', [__CLASS__, 'html_encoding_callback'], $s); } if ('HTML-ENTITIES' === $fromEncoding) { - $s = html_entity_decode($s, ENT_COMPAT, 'UTF-8'); + $s = html_entity_decode($s, \ENT_COMPAT, 'UTF-8'); $fromEncoding = 'UTF-8'; } - return iconv($fromEncoding, $toEncoding.'//IGNORE', $s); + return \iconv($fromEncoding, $toEncoding.'//IGNORE', $s); } - public static function mb_convert_variables($toEncoding, $fromEncoding, &$a = null, &$b = null, &$c = null, &$d = null, &$e = null, &$f = null) + public static function mb_convert_variables($toEncoding, $fromEncoding, &...$vars) { - $vars = array(&$a, &$b, &$c, &$d, &$e, &$f); - $ok = true; array_walk_recursive($vars, function (&$v) use (&$ok, $toEncoding, $fromEncoding) { - if (false === $v = Mbstring::mb_convert_encoding($v, $toEncoding, $fromEncoding)) { + if (false === $v = self::mb_convert_encoding($v, $toEncoding, $fromEncoding)) { $ok = false; } }); @@ -131,28 +130,28 @@ public static function mb_convert_variables($toEncoding, $fromEncoding, &$a = nu public static function mb_decode_mimeheader($s) { - return iconv_mime_decode($s, 2, self::$internalEncoding); + return \iconv_mime_decode($s, 2, self::$internalEncoding); } public static function mb_encode_mimeheader($s, $charset = null, $transferEncoding = null, $linefeed = null, $indent = null) { - trigger_error('mb_encode_mimeheader() is bugged. Please use iconv_mime_encode() instead', E_USER_WARNING); + trigger_error('mb_encode_mimeheader() is bugged. Please use iconv_mime_encode() instead', \E_USER_WARNING); } public static function mb_decode_numericentity($s, $convmap, $encoding = null) { - if (null !== $s && !\is_scalar($s) && !(\is_object($s) && \method_exists($s, '__toString'))) { - trigger_error('mb_decode_numericentity() expects parameter 1 to be string, '.\gettype($s).' given', E_USER_WARNING); + if (null !== $s && !is_scalar($s) && !(\is_object($s) && method_exists($s, '__toString'))) { + trigger_error('mb_decode_numericentity() expects parameter 1 to be string, '.\gettype($s).' given', \E_USER_WARNING); return null; } - if (!\is_array($convmap) || !$convmap) { + if (!\is_array($convmap) || (80000 > \PHP_VERSION_ID && !$convmap)) { return false; } - if (null !== $encoding && !\is_scalar($encoding)) { - trigger_error('mb_decode_numericentity() expects parameter 3 to be string, '.\gettype($s).' given', E_USER_WARNING); + if (null !== $encoding && !is_scalar($encoding)) { + trigger_error('mb_decode_numericentity() expects parameter 3 to be string, '.\gettype($s).' given', \E_USER_WARNING); return ''; // Instead of null (cf. mb_encode_numericentity). } @@ -167,10 +166,10 @@ public static function mb_decode_numericentity($s, $convmap, $encoding = null) if ('UTF-8' === $encoding) { $encoding = null; if (!preg_match('//u', $s)) { - $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s); + $s = @\iconv('UTF-8', 'UTF-8//IGNORE', $s); } } else { - $s = iconv($encoding, 'UTF-8//IGNORE', $s); + $s = \iconv($encoding, 'UTF-8//IGNORE', $s); } $cnt = floor(\count($convmap) / 4) * 4; @@ -185,7 +184,7 @@ public static function mb_decode_numericentity($s, $convmap, $encoding = null) $c = isset($m[2]) ? (int) hexdec($m[2]) : $m[1]; for ($i = 0; $i < $cnt; $i += 4) { if ($c >= $convmap[$i] && $c <= $convmap[$i + 1]) { - return Mbstring::mb_chr($c - $convmap[$i + 2]); + return self::mb_chr($c - $convmap[$i + 2]); } } @@ -196,29 +195,29 @@ public static function mb_decode_numericentity($s, $convmap, $encoding = null) return $s; } - return iconv('UTF-8', $encoding.'//IGNORE', $s); + return \iconv('UTF-8', $encoding.'//IGNORE', $s); } public static function mb_encode_numericentity($s, $convmap, $encoding = null, $is_hex = false) { - if (null !== $s && !\is_scalar($s) && !(\is_object($s) && \method_exists($s, '__toString'))) { - trigger_error('mb_encode_numericentity() expects parameter 1 to be string, '.\gettype($s).' given', E_USER_WARNING); + if (null !== $s && !is_scalar($s) && !(\is_object($s) && method_exists($s, '__toString'))) { + trigger_error('mb_encode_numericentity() expects parameter 1 to be string, '.\gettype($s).' given', \E_USER_WARNING); return null; } - if (!\is_array($convmap) || !$convmap) { + if (!\is_array($convmap) || (80000 > \PHP_VERSION_ID && !$convmap)) { return false; } - if (null !== $encoding && !\is_scalar($encoding)) { - trigger_error('mb_encode_numericentity() expects parameter 3 to be string, '.\gettype($s).' given', E_USER_WARNING); + if (null !== $encoding && !is_scalar($encoding)) { + trigger_error('mb_encode_numericentity() expects parameter 3 to be string, '.\gettype($s).' given', \E_USER_WARNING); return null; // Instead of '' (cf. mb_decode_numericentity). } - if (null !== $is_hex && !\is_scalar($is_hex)) { - trigger_error('mb_encode_numericentity() expects parameter 4 to be boolean, '.\gettype($s).' given', E_USER_WARNING); + if (null !== $is_hex && !is_scalar($is_hex)) { + trigger_error('mb_encode_numericentity() expects parameter 4 to be boolean, '.\gettype($s).' given', \E_USER_WARNING); return null; } @@ -233,13 +232,13 @@ public static function mb_encode_numericentity($s, $convmap, $encoding = null, $ if ('UTF-8' === $encoding) { $encoding = null; if (!preg_match('//u', $s)) { - $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s); + $s = @\iconv('UTF-8', 'UTF-8//IGNORE', $s); } } else { - $s = iconv($encoding, 'UTF-8//IGNORE', $s); + $s = \iconv($encoding, 'UTF-8//IGNORE', $s); } - static $ulenMask = array("\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4); + static $ulenMask = ["\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4]; $cnt = floor(\count($convmap) / 4) * 4; $i = 0; @@ -266,7 +265,7 @@ public static function mb_encode_numericentity($s, $convmap, $encoding = null, $ return $result; } - return iconv('UTF-8', $encoding.'//IGNORE', $result); + return \iconv('UTF-8', $encoding.'//IGNORE', $result); } public static function mb_convert_case($s, $mode, $encoding = null) @@ -281,20 +280,20 @@ public static function mb_convert_case($s, $mode, $encoding = null) if ('UTF-8' === $encoding) { $encoding = null; if (!preg_match('//u', $s)) { - $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s); + $s = @\iconv('UTF-8', 'UTF-8//IGNORE', $s); } } else { - $s = iconv($encoding, 'UTF-8//IGNORE', $s); + $s = \iconv($encoding, 'UTF-8//IGNORE', $s); } - if (MB_CASE_TITLE == $mode) { + if (\MB_CASE_TITLE == $mode) { static $titleRegexp = null; if (null === $titleRegexp) { $titleRegexp = self::getData('titleCaseRegexp'); } - $s = preg_replace_callback($titleRegexp, array(__CLASS__, 'title_case'), $s); + $s = preg_replace_callback($titleRegexp, [__CLASS__, 'title_case'], $s); } else { - if (MB_CASE_UPPER == $mode) { + if (\MB_CASE_UPPER == $mode) { static $upper = null; if (null === $upper) { $upper = self::getData('upperCase'); @@ -302,7 +301,7 @@ public static function mb_convert_case($s, $mode, $encoding = null) $map = $upper; } else { if (self::MB_CASE_FOLD === $mode) { - $s = str_replace(self::$caseFold[0], self::$caseFold[1], $s); + $s = str_replace(self::CASE_FOLD[0], self::CASE_FOLD[1], $s); } static $lower = null; @@ -312,7 +311,7 @@ public static function mb_convert_case($s, $mode, $encoding = null) $map = $lower; } - static $ulenMask = array("\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4); + static $ulenMask = ["\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4]; $i = 0; $len = \strlen($s); @@ -344,7 +343,7 @@ public static function mb_convert_case($s, $mode, $encoding = null) return $s; } - return iconv('UTF-8', $encoding.'//IGNORE', $s); + return \iconv('UTF-8', $encoding.'//IGNORE', $s); } public static function mb_internal_encoding($encoding = null) @@ -353,15 +352,19 @@ public static function mb_internal_encoding($encoding = null) return self::$internalEncoding; } - $encoding = self::getEncoding($encoding); + $normalizedEncoding = self::getEncoding($encoding); - if ('UTF-8' === $encoding || false !== @iconv($encoding, $encoding, ' ')) { - self::$internalEncoding = $encoding; + if ('UTF-8' === $normalizedEncoding || false !== @\iconv($normalizedEncoding, $normalizedEncoding, ' ')) { + self::$internalEncoding = $normalizedEncoding; return true; } - return false; + if (80000 > \PHP_VERSION_ID) { + return false; + } + + throw new \ValueError(sprintf('Argument #1 ($encoding) must be a valid encoding, "%s" given', $encoding)); } public static function mb_language($lang = null) @@ -370,20 +373,24 @@ public static function mb_language($lang = null) return self::$language; } - switch ($lang = strtolower($lang)) { + switch ($normalizedLang = strtolower($lang)) { case 'uni': case 'neutral': - self::$language = $lang; + self::$language = $normalizedLang; return true; } - return false; + if (80000 > \PHP_VERSION_ID) { + return false; + } + + throw new \ValueError(sprintf('Argument #1 ($language) must be a valid language, "%s" given', $lang)); } public static function mb_list_encodings() { - return array('UTF-8'); + return ['UTF-8']; } public static function mb_encoding_aliases($encoding) @@ -391,7 +398,7 @@ public static function mb_encoding_aliases($encoding) switch (strtoupper($encoding)) { case 'UTF8': case 'UTF-8': - return array('utf8'); + return ['utf8']; } return false; @@ -406,7 +413,7 @@ public static function mb_check_encoding($var = null, $encoding = null) $encoding = self::$internalEncoding; } - return self::mb_detect_encoding($var, array($encoding)) || false !== @iconv($encoding, $encoding, $var); + return self::mb_detect_encoding($var, [$encoding]) || false !== @\iconv($encoding, $encoding, $var); } public static function mb_detect_encoding($str, $encodingList = null, $strict = false) @@ -481,7 +488,7 @@ public static function mb_strlen($s, $encoding = null) return \strlen($s); } - return @iconv_strlen($s, $encoding); + return @\iconv_strlen($s, $encoding); } public static function mb_strpos($haystack, $needle, $offset = 0, $encoding = null) @@ -493,12 +500,16 @@ public static function mb_strpos($haystack, $needle, $offset = 0, $encoding = nu $needle = (string) $needle; if ('' === $needle) { - trigger_error(__METHOD__.': Empty delimiter', E_USER_WARNING); + if (80000 > \PHP_VERSION_ID) { + trigger_error(__METHOD__.': Empty delimiter', \E_USER_WARNING); - return false; + return false; + } + + return 0; } - return iconv_strpos($haystack, $needle, $offset, $encoding); + return \iconv_strpos($haystack, $needle, $offset, $encoding); } public static function mb_strrpos($haystack, $needle, $offset = 0, $encoding = null) @@ -521,23 +532,28 @@ public static function mb_strrpos($haystack, $needle, $offset = 0, $encoding = n } } - $pos = iconv_strrpos($haystack, $needle, $encoding); + $pos = '' !== $needle || 80000 > \PHP_VERSION_ID + ? \iconv_strrpos($haystack, $needle, $encoding) + : self::mb_strlen($haystack, $encoding); return false !== $pos ? $offset + $pos : false; } public static function mb_str_split($string, $split_length = 1, $encoding = null) { - if (null !== $string && !\is_scalar($string) && !(\is_object($string) && \method_exists($string, '__toString'))) { - trigger_error('mb_str_split() expects parameter 1 to be string, '.\gettype($string).' given', E_USER_WARNING); + if (null !== $string && !is_scalar($string) && !(\is_object($string) && method_exists($string, '__toString'))) { + trigger_error('mb_str_split() expects parameter 1 to be string, '.\gettype($string).' given', \E_USER_WARNING); return null; } if (1 > $split_length = (int) $split_length) { - trigger_error('The length of each segment must be greater than zero', E_USER_WARNING); + if (80000 > \PHP_VERSION_ID) { + trigger_error('The length of each segment must be greater than zero', \E_USER_WARNING); + return false; + } - return false; + throw new \ValueError('Argument #2 ($length) must be greater than 0'); } if (null === $encoding) { @@ -552,10 +568,10 @@ public static function mb_str_split($string, $split_length = 1, $encoding = null } $rx .= '.{'.$split_length.'})/us'; - return preg_split($rx, $string, null, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); + return preg_split($rx, $string, -1, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY); } - $result = array(); + $result = []; $length = mb_strlen($string, $encoding); for ($i = 0; $i < $length; $i += $split_length) { @@ -567,21 +583,30 @@ public static function mb_str_split($string, $split_length = 1, $encoding = null public static function mb_strtolower($s, $encoding = null) { - return self::mb_convert_case($s, MB_CASE_LOWER, $encoding); + return self::mb_convert_case($s, \MB_CASE_LOWER, $encoding); } public static function mb_strtoupper($s, $encoding = null) { - return self::mb_convert_case($s, MB_CASE_UPPER, $encoding); + return self::mb_convert_case($s, \MB_CASE_UPPER, $encoding); } public static function mb_substitute_character($c = null) { + if (null === $c) { + return 'none'; + } if (0 === strcasecmp($c, 'none')) { return true; } + if (80000 > \PHP_VERSION_ID) { + return false; + } + if (\is_int($c) || 'long' === $c || 'entity' === $c) { + return false; + } - return null !== $c ? false : 'none'; + throw new \ValueError('Argument #1 ($substitute_character) must be "none", "long", "entity" or a valid codepoint'); } public static function mb_substr($s, $start, $length = null, $encoding = null) @@ -592,7 +617,7 @@ public static function mb_substr($s, $start, $length = null, $encoding = null) } if ($start < 0) { - $start = iconv_strlen($s, $encoding) + $start; + $start = \iconv_strlen($s, $encoding) + $start; if ($start < 0) { $start = 0; } @@ -601,13 +626,13 @@ public static function mb_substr($s, $start, $length = null, $encoding = null) if (null === $length) { $length = 2147483647; } elseif ($length < 0) { - $length = iconv_strlen($s, $encoding) + $length - $start; + $length = \iconv_strlen($s, $encoding) + $length - $start; if ($length < 0) { return ''; } } - return (string) iconv_substr($s, $start, $length, $encoding); + return (string) \iconv_substr($s, $start, $length, $encoding); } public static function mb_stripos($haystack, $needle, $offset = 0, $encoding = null) @@ -632,7 +657,7 @@ public static function mb_strrchr($haystack, $needle, $part = false, $encoding = $pos = strrpos($haystack, $needle); } else { $needle = self::mb_substr($needle, 0, 1, $encoding); - $pos = iconv_strrpos($haystack, $needle, $encoding); + $pos = \iconv_strrpos($haystack, $needle, $encoding); } return self::getSubpart($pos, $part, $haystack, $encoding); @@ -669,7 +694,7 @@ public static function mb_strstr($haystack, $needle, $part = false, $encoding = public static function mb_get_info($type = 'all') { - $info = array( + $info = [ 'internal_encoding' => self::$internalEncoding, 'http_output' => 'pass', 'http_output_conv_mimetypes' => '^(text/|application/xhtml\+xml)', @@ -684,7 +709,7 @@ public static function mb_get_info($type = 'all') 'detect_order' => self::$encodingList, 'substitute_character' => 'none', 'strict_detection' => 'Off', - ); + ]; if ('all' === $type) { return $info; @@ -711,12 +736,12 @@ public static function mb_strwidth($s, $encoding = null) $encoding = self::getEncoding($encoding); if ('UTF-8' !== $encoding) { - $s = iconv($encoding, 'UTF-8//IGNORE', $s); + $s = \iconv($encoding, 'UTF-8//IGNORE', $s); } $s = preg_replace('/[\x{1100}-\x{115F}\x{2329}\x{232A}\x{2E80}-\x{303E}\x{3040}-\x{A4CF}\x{AC00}-\x{D7A3}\x{F900}-\x{FAFF}\x{FE10}-\x{FE19}\x{FE30}-\x{FE6F}\x{FF00}-\x{FF60}\x{FFE0}-\x{FFE6}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}]/u', '', $s, -1, $wide); - return ($wide << 1) + iconv_strlen($s, 'UTF-8'); + return ($wide << 1) + \iconv_strlen($s, 'UTF-8'); } public static function mb_substr_count($haystack, $needle, $encoding = null) @@ -788,7 +813,7 @@ private static function html_encoding_callback(array $m) { $i = 1; $entities = ''; - $m = unpack('C*', htmlentities($m[0], ENT_COMPAT, 'UTF-8')); + $m = unpack('C*', htmlentities($m[0], \ENT_COMPAT, 'UTF-8')); while (isset($m[$i])) { if (0x80 > $m[$i]) { @@ -811,7 +836,7 @@ private static function html_encoding_callback(array $m) private static function title_case(array $s) { - return self::mb_convert_case($s[1], MB_CASE_UPPER, 'UTF-8').self::mb_convert_case($s[2], MB_CASE_LOWER, 'UTF-8'); + return self::mb_convert_case($s[1], \MB_CASE_UPPER, 'UTF-8').self::mb_convert_case($s[2], \MB_CASE_LOWER, 'UTF-8'); } private static function getData($file) diff --git a/vendor/symfony/polyfill-mbstring/Resources/mb_convert_variables.php8 b/vendor/symfony/polyfill-mbstring/Resources/mb_convert_variables.php8 deleted file mode 100644 index d22d058e..00000000 --- a/vendor/symfony/polyfill-mbstring/Resources/mb_convert_variables.php8 +++ /dev/null @@ -1,31 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -use Symfony\Polyfill\Mbstring as p; - -if (!function_exists('mb_convert_variables')) { - /** - * Convert character code in variable(s) - */ - function mb_convert_variables($to_encoding, $from_encoding, &$var, &...$vars) - { - $vars = [&$var, ...$vars]; - - $ok = true; - array_walk_recursive($vars, function (&$v) use (&$ok, $to_encoding, $from_encoding) { - if (false === $v = p\Mbstring::mb_convert_encoding($v, $to_encoding, $from_encoding)) { - $ok = false; - } - }); - - return $ok ? $from_encoding : false; - } -} diff --git a/vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php b/vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php index a22eca57..fac60b08 100644 --- a/vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php +++ b/vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php @@ -81,7 +81,7 @@ 'Ī' => 'ī', 'Ĭ' => 'ĭ', 'Į' => 'į', - 'İ' => 'i', + 'İ' => 'i̇', 'IJ' => 'ij', 'Ĵ' => 'ĵ', 'Ķ' => 'ķ', diff --git a/vendor/symfony/polyfill-mbstring/Resources/unidata/upperCase.php b/vendor/symfony/polyfill-mbstring/Resources/unidata/upperCase.php index ecbc1589..56b9cb85 100644 --- a/vendor/symfony/polyfill-mbstring/Resources/unidata/upperCase.php +++ b/vendor/symfony/polyfill-mbstring/Resources/unidata/upperCase.php @@ -746,41 +746,41 @@ 'ύ' => 'Ύ', 'ὼ' => 'Ὼ', 'ώ' => 'Ώ', - 'ᾀ' => 'ᾈ', - 'ᾁ' => 'ᾉ', - 'ᾂ' => 'ᾊ', - 'ᾃ' => 'ᾋ', - 'ᾄ' => 'ᾌ', - 'ᾅ' => 'ᾍ', - 'ᾆ' => 'ᾎ', - 'ᾇ' => 'ᾏ', - 'ᾐ' => 'ᾘ', - 'ᾑ' => 'ᾙ', - 'ᾒ' => 'ᾚ', - 'ᾓ' => 'ᾛ', - 'ᾔ' => 'ᾜ', - 'ᾕ' => 'ᾝ', - 'ᾖ' => 'ᾞ', - 'ᾗ' => 'ᾟ', - 'ᾠ' => 'ᾨ', - 'ᾡ' => 'ᾩ', - 'ᾢ' => 'ᾪ', - 'ᾣ' => 'ᾫ', - 'ᾤ' => 'ᾬ', - 'ᾥ' => 'ᾭ', - 'ᾦ' => 'ᾮ', - 'ᾧ' => 'ᾯ', + 'ᾀ' => 'ἈΙ', + 'ᾁ' => 'ἉΙ', + 'ᾂ' => 'ἊΙ', + 'ᾃ' => 'ἋΙ', + 'ᾄ' => 'ἌΙ', + 'ᾅ' => 'ἍΙ', + 'ᾆ' => 'ἎΙ', + 'ᾇ' => 'ἏΙ', + 'ᾐ' => 'ἨΙ', + 'ᾑ' => 'ἩΙ', + 'ᾒ' => 'ἪΙ', + 'ᾓ' => 'ἫΙ', + 'ᾔ' => 'ἬΙ', + 'ᾕ' => 'ἭΙ', + 'ᾖ' => 'ἮΙ', + 'ᾗ' => 'ἯΙ', + 'ᾠ' => 'ὨΙ', + 'ᾡ' => 'ὩΙ', + 'ᾢ' => 'ὪΙ', + 'ᾣ' => 'ὫΙ', + 'ᾤ' => 'ὬΙ', + 'ᾥ' => 'ὭΙ', + 'ᾦ' => 'ὮΙ', + 'ᾧ' => 'ὯΙ', 'ᾰ' => 'Ᾰ', 'ᾱ' => 'Ᾱ', - 'ᾳ' => 'ᾼ', + 'ᾳ' => 'ΑΙ', 'ι' => 'Ι', - 'ῃ' => 'ῌ', + 'ῃ' => 'ΗΙ', 'ῐ' => 'Ῐ', 'ῑ' => 'Ῑ', 'ῠ' => 'Ῠ', 'ῡ' => 'Ῡ', 'ῥ' => 'Ῥ', - 'ῳ' => 'ῼ', + 'ῳ' => 'ΩΙ', 'ⅎ' => 'Ⅎ', 'ⅰ' => 'Ⅰ', 'ⅱ' => 'Ⅱ', @@ -1411,4 +1411,79 @@ '𞥁' => '𞤟', '𞥂' => '𞤠', '𞥃' => '𞤡', + 'ß' => 'SS', + 'ff' => 'FF', + 'fi' => 'FI', + 'fl' => 'FL', + 'ffi' => 'FFI', + 'ffl' => 'FFL', + 'ſt' => 'ST', + 'st' => 'ST', + 'և' => 'ԵՒ', + 'ﬓ' => 'ՄՆ', + 'ﬔ' => 'ՄԵ', + 'ﬕ' => 'ՄԻ', + 'ﬖ' => 'ՎՆ', + 'ﬗ' => 'ՄԽ', + 'ʼn' => 'ʼN', + 'ΐ' => 'Ϊ́', + 'ΰ' => 'Ϋ́', + 'ǰ' => 'J̌', + 'ẖ' => 'H̱', + 'ẗ' => 'T̈', + 'ẘ' => 'W̊', + 'ẙ' => 'Y̊', + 'ẚ' => 'Aʾ', + 'ὐ' => 'Υ̓', + 'ὒ' => 'Υ̓̀', + 'ὔ' => 'Υ̓́', + 'ὖ' => 'Υ̓͂', + 'ᾶ' => 'Α͂', + 'ῆ' => 'Η͂', + 'ῒ' => 'Ϊ̀', + 'ΐ' => 'Ϊ́', + 'ῖ' => 'Ι͂', + 'ῗ' => 'Ϊ͂', + 'ῢ' => 'Ϋ̀', + 'ΰ' => 'Ϋ́', + 'ῤ' => 'Ρ̓', + 'ῦ' => 'Υ͂', + 'ῧ' => 'Ϋ͂', + 'ῶ' => 'Ω͂', + 'ᾈ' => 'ἈΙ', + 'ᾉ' => 'ἉΙ', + 'ᾊ' => 'ἊΙ', + 'ᾋ' => 'ἋΙ', + 'ᾌ' => 'ἌΙ', + 'ᾍ' => 'ἍΙ', + 'ᾎ' => 'ἎΙ', + 'ᾏ' => 'ἏΙ', + 'ᾘ' => 'ἨΙ', + 'ᾙ' => 'ἩΙ', + 'ᾚ' => 'ἪΙ', + 'ᾛ' => 'ἫΙ', + 'ᾜ' => 'ἬΙ', + 'ᾝ' => 'ἭΙ', + 'ᾞ' => 'ἮΙ', + 'ᾟ' => 'ἯΙ', + 'ᾨ' => 'ὨΙ', + 'ᾩ' => 'ὩΙ', + 'ᾪ' => 'ὪΙ', + 'ᾫ' => 'ὫΙ', + 'ᾬ' => 'ὬΙ', + 'ᾭ' => 'ὭΙ', + 'ᾮ' => 'ὮΙ', + 'ᾯ' => 'ὯΙ', + 'ᾼ' => 'ΑΙ', + 'ῌ' => 'ΗΙ', + 'ῼ' => 'ΩΙ', + 'ᾲ' => 'ᾺΙ', + 'ᾴ' => 'ΆΙ', + 'ῂ' => 'ῊΙ', + 'ῄ' => 'ΉΙ', + 'ῲ' => 'ῺΙ', + 'ῴ' => 'ΏΙ', + 'ᾷ' => 'Α͂Ι', + 'ῇ' => 'Η͂Ι', + 'ῷ' => 'Ω͂Ι', ); diff --git a/vendor/symfony/polyfill-mbstring/bootstrap.php b/vendor/symfony/polyfill-mbstring/bootstrap.php index a48f7e62..1fedd1f7 100644 --- a/vendor/symfony/polyfill-mbstring/bootstrap.php +++ b/vendor/symfony/polyfill-mbstring/bootstrap.php @@ -11,6 +11,10 @@ use Symfony\Polyfill\Mbstring as p; +if (\PHP_VERSION_ID >= 80000) { + return require __DIR__.'/bootstrap80.php'; +} + if (!function_exists('mb_convert_encoding')) { function mb_convert_encoding($string, $to_encoding, $from_encoding = null) { return p\Mbstring::mb_convert_encoding($string, $to_encoding, $from_encoding); } } @@ -18,7 +22,7 @@ function mb_convert_encoding($string, $to_encoding, $from_encoding = null) { ret function mb_decode_mimeheader($string) { return p\Mbstring::mb_decode_mimeheader($string); } } if (!function_exists('mb_encode_mimeheader')) { - function mb_encode_mimeheader($string, $charset = null, $transfer_encoding = null, $newline = null, $indent = null) { return p\Mbstring::mb_encode_mimeheader($string, $charset, $transfer_encoding, $newline, $indent); } + function mb_encode_mimeheader($string, $charset = null, $transfer_encoding = null, $newline = "\r\n", $indent = 0) { return p\Mbstring::mb_encode_mimeheader($string, $charset, $transfer_encoding, $newline, $indent); } } if (!function_exists('mb_decode_numericentity')) { function mb_decode_numericentity($string, $map, $encoding = null) { return p\Mbstring::mb_decode_numericentity($string, $map, $encoding); } @@ -51,7 +55,7 @@ function mb_detect_encoding($string, $encodings = null, $strict = false) { retur function mb_detect_order($encoding = null) { return p\Mbstring::mb_detect_order($encoding); } } if (!function_exists('mb_parse_str')) { - function mb_parse_str($string, &$result = array()) { parse_str($string, $result); } + function mb_parse_str($string, &$result = []) { parse_str($string, $result); return (bool) $result; } } if (!function_exists('mb_strlen')) { function mb_strlen($string, $encoding = null) { return p\Mbstring::mb_strlen($string, $encoding); } @@ -108,13 +112,11 @@ function mb_substr_count($haystack, $needle, $encoding = null) { return p\Mbstri function mb_output_handler($string, $status) { return p\Mbstring::mb_output_handler($string, $status); } } if (!function_exists('mb_http_input')) { - function mb_http_input($type = '') { return p\Mbstring::mb_http_input($type); } + function mb_http_input($type = null) { return p\Mbstring::mb_http_input($type); } } -if (PHP_VERSION_ID >= 80000) { - require_once __DIR__.'/Resources/mb_convert_variables.php8'; -} elseif (!function_exists('mb_convert_variables')) { - function mb_convert_variables($toEncoding, $fromEncoding, &$a = null, &$b = null, &$c = null, &$d = null, &$e = null, &$f = null) { return p\Mbstring::mb_convert_variables($toEncoding, $fromEncoding, $a, $b, $c, $d, $e, $f); } +if (!function_exists('mb_convert_variables')) { + function mb_convert_variables($to_encoding, $from_encoding, &...$vars) { return p\Mbstring::mb_convert_variables($to_encoding, $from_encoding, ...$vars); } } if (!function_exists('mb_ord')) { diff --git a/vendor/symfony/polyfill-mbstring/bootstrap80.php b/vendor/symfony/polyfill-mbstring/bootstrap80.php new file mode 100644 index 00000000..82f5ac4d --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/bootstrap80.php @@ -0,0 +1,143 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Mbstring as p; + +if (!function_exists('mb_convert_encoding')) { + function mb_convert_encoding(array|string|null $string, ?string $to_encoding, array|string|null $from_encoding = null): array|string|false { return p\Mbstring::mb_convert_encoding($string ?? '', (string) $to_encoding, $from_encoding); } +} +if (!function_exists('mb_decode_mimeheader')) { + function mb_decode_mimeheader(?string $string): string { return p\Mbstring::mb_decode_mimeheader((string) $string); } +} +if (!function_exists('mb_encode_mimeheader')) { + function mb_encode_mimeheader(?string $string, ?string $charset = null, ?string $transfer_encoding = null, ?string $newline = "\r\n", ?int $indent = 0): string { return p\Mbstring::mb_encode_mimeheader((string) $string, $charset, $transfer_encoding, (string) $newline, (int) $indent); } +} +if (!function_exists('mb_decode_numericentity')) { + function mb_decode_numericentity(?string $string, array $map, ?string $encoding = null): string { return p\Mbstring::mb_decode_numericentity((string) $string, $map, $encoding); } +} +if (!function_exists('mb_encode_numericentity')) { + function mb_encode_numericentity(?string $string, array $map, ?string $encoding = null, ?bool $hex = false): string { return p\Mbstring::mb_encode_numericentity((string) $string, $map, $encoding, (bool) $hex); } +} +if (!function_exists('mb_convert_case')) { + function mb_convert_case(?string $string, ?int $mode, ?string $encoding = null): string { return p\Mbstring::mb_convert_case((string) $string, (int) $mode, $encoding); } +} +if (!function_exists('mb_internal_encoding')) { + function mb_internal_encoding(?string $encoding = null): string|bool { return p\Mbstring::mb_internal_encoding($encoding); } +} +if (!function_exists('mb_language')) { + function mb_language(?string $language = null): string|bool { return p\Mbstring::mb_language($language); } +} +if (!function_exists('mb_list_encodings')) { + function mb_list_encodings(): array { return p\Mbstring::mb_list_encodings(); } +} +if (!function_exists('mb_encoding_aliases')) { + function mb_encoding_aliases(?string $encoding): array { return p\Mbstring::mb_encoding_aliases((string) $encoding); } +} +if (!function_exists('mb_check_encoding')) { + function mb_check_encoding(array|string|null $value = null, ?string $encoding = null): bool { return p\Mbstring::mb_check_encoding($value, $encoding); } +} +if (!function_exists('mb_detect_encoding')) { + function mb_detect_encoding(?string $string, array|string|null $encodings = null, ?bool $strict = false): string|false { return p\Mbstring::mb_detect_encoding((string) $string, $encodings, (bool) $strict); } +} +if (!function_exists('mb_detect_order')) { + function mb_detect_order(array|string|null $encoding = null): array|bool { return p\Mbstring::mb_detect_order($encoding); } +} +if (!function_exists('mb_parse_str')) { + function mb_parse_str(?string $string, &$result = []): bool { parse_str((string) $string, $result); return (bool) $result; } +} +if (!function_exists('mb_strlen')) { + function mb_strlen(?string $string, ?string $encoding = null): int { return p\Mbstring::mb_strlen((string) $string, $encoding); } +} +if (!function_exists('mb_strpos')) { + function mb_strpos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_strpos((string) $haystack, (string) $needle, (int) $offset, $encoding); } +} +if (!function_exists('mb_strtolower')) { + function mb_strtolower(?string $string, ?string $encoding = null): string { return p\Mbstring::mb_strtolower((string) $string, $encoding); } +} +if (!function_exists('mb_strtoupper')) { + function mb_strtoupper(?string $string, ?string $encoding = null): string { return p\Mbstring::mb_strtoupper((string) $string, $encoding); } +} +if (!function_exists('mb_substitute_character')) { + function mb_substitute_character(string|int|null $substitute_character = null): string|int|bool { return p\Mbstring::mb_substitute_character($substitute_character); } +} +if (!function_exists('mb_substr')) { + function mb_substr(?string $string, ?int $start, ?int $length = null, ?string $encoding = null): string { return p\Mbstring::mb_substr((string) $string, (int) $start, $length, $encoding); } +} +if (!function_exists('mb_stripos')) { + function mb_stripos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_stripos((string) $haystack, (string) $needle, (int) $offset, $encoding); } +} +if (!function_exists('mb_stristr')) { + function mb_stristr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_stristr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); } +} +if (!function_exists('mb_strrchr')) { + function mb_strrchr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_strrchr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); } +} +if (!function_exists('mb_strrichr')) { + function mb_strrichr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_strrichr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); } +} +if (!function_exists('mb_strripos')) { + function mb_strripos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_strripos((string) $haystack, (string) $needle, (int) $offset, $encoding); } +} +if (!function_exists('mb_strrpos')) { + function mb_strrpos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_strrpos((string) $haystack, (string) $needle, (int) $offset, $encoding); } +} +if (!function_exists('mb_strstr')) { + function mb_strstr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_strstr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); } +} +if (!function_exists('mb_get_info')) { + function mb_get_info(?string $type = 'all'): array|string|int|false { return p\Mbstring::mb_get_info((string) $type); } +} +if (!function_exists('mb_http_output')) { + function mb_http_output(?string $encoding = null): string|bool { return p\Mbstring::mb_http_output($encoding); } +} +if (!function_exists('mb_strwidth')) { + function mb_strwidth(?string $string, ?string $encoding = null): int { return p\Mbstring::mb_strwidth((string) $string, $encoding); } +} +if (!function_exists('mb_substr_count')) { + function mb_substr_count(?string $haystack, ?string $needle, ?string $encoding = null): int { return p\Mbstring::mb_substr_count((string) $haystack, (string) $needle, $encoding); } +} +if (!function_exists('mb_output_handler')) { + function mb_output_handler(?string $string, ?int $status): string { return p\Mbstring::mb_output_handler((string) $string, (int) $status); } +} +if (!function_exists('mb_http_input')) { + function mb_http_input(?string $type = null): array|string|false { return p\Mbstring::mb_http_input($type); } +} + +if (!function_exists('mb_convert_variables')) { + function mb_convert_variables(?string $to_encoding, array|string|null $from_encoding, mixed &$var, mixed &...$vars): string|false { return p\Mbstring::mb_convert_variables((string) $to_encoding, $from_encoding ?? '', $var, ...$vars); } +} + +if (!function_exists('mb_ord')) { + function mb_ord(?string $string, ?string $encoding = null): int|false { return p\Mbstring::mb_ord((string) $string, $encoding); } +} +if (!function_exists('mb_chr')) { + function mb_chr(?int $codepoint, ?string $encoding = null): string|false { return p\Mbstring::mb_chr((int) $codepoint, $encoding); } +} +if (!function_exists('mb_scrub')) { + function mb_scrub(?string $string, ?string $encoding = null): string { $encoding ??= mb_internal_encoding(); return mb_convert_encoding((string) $string, $encoding, $encoding); } +} +if (!function_exists('mb_str_split')) { + function mb_str_split(?string $string, ?int $length = 1, ?string $encoding = null): array { return p\Mbstring::mb_str_split((string) $string, (int) $length, $encoding); } +} + +if (extension_loaded('mbstring')) { + return; +} + +if (!defined('MB_CASE_UPPER')) { + define('MB_CASE_UPPER', 0); +} +if (!defined('MB_CASE_LOWER')) { + define('MB_CASE_LOWER', 1); +} +if (!defined('MB_CASE_TITLE')) { + define('MB_CASE_TITLE', 2); +} diff --git a/vendor/symfony/polyfill-php70/LICENSE b/vendor/symfony/polyfill-php70/LICENSE deleted file mode 100644 index 4cd8bdd3..00000000 --- a/vendor/symfony/polyfill-php70/LICENSE +++ /dev/null @@ -1,19 +0,0 @@ -Copyright (c) 2015-2019 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 -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is furnished -to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/vendor/symfony/polyfill-php70/Php70.php b/vendor/symfony/polyfill-php70/Php70.php deleted file mode 100644 index 7f1ad08a..00000000 --- a/vendor/symfony/polyfill-php70/Php70.php +++ /dev/null @@ -1,74 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Polyfill\Php70; - -/** - * @author Nicolas Grekas - * - * @internal - */ -final class Php70 -{ - public static function intdiv($dividend, $divisor) - { - $dividend = self::intArg($dividend, __FUNCTION__, 1); - $divisor = self::intArg($divisor, __FUNCTION__, 2); - - if (0 === $divisor) { - throw new \DivisionByZeroError('Division by zero'); - } - if (-1 === $divisor && ~PHP_INT_MAX === $dividend) { - throw new \ArithmeticError('Division of PHP_INT_MIN by -1 is not an integer'); - } - - return ($dividend - ($dividend % $divisor)) / $divisor; - } - - public static function preg_replace_callback_array(array $patterns, $subject, $limit = -1, &$count = 0) - { - $count = 0; - $result = (string) $subject; - if (0 === $limit = self::intArg($limit, __FUNCTION__, 3)) { - return $result; - } - - foreach ($patterns as $pattern => $callback) { - $result = preg_replace_callback($pattern, $callback, $result, $limit, $c); - $count += $c; - } - - return $result; - } - - public static function error_clear_last() - { - static $handler; - if (!$handler) { - $handler = function () { return false; }; - } - set_error_handler($handler); - @trigger_error(''); - restore_error_handler(); - } - - private static function intArg($value, $caller, $pos) - { - if (\is_int($value)) { - return $value; - } - if (!\is_numeric($value) || PHP_INT_MAX <= ($value += 0) || ~PHP_INT_MAX >= $value) { - throw new \TypeError(sprintf('%s() expects parameter %d to be integer, %s given', $caller, $pos, \gettype($value))); - } - - return (int) $value; - } -} diff --git a/vendor/symfony/polyfill-php70/Resources/stubs/ArithmeticError.php b/vendor/symfony/polyfill-php70/Resources/stubs/ArithmeticError.php deleted file mode 100644 index 68191244..00000000 --- a/vendor/symfony/polyfill-php70/Resources/stubs/ArithmeticError.php +++ /dev/null @@ -1,5 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -use Symfony\Polyfill\Php70 as p; - -if (PHP_VERSION_ID >= 70000) { - return; -} - -if (!defined('PHP_INT_MIN')) { - define('PHP_INT_MIN', ~PHP_INT_MAX); -} - -if (!function_exists('intdiv')) { - function intdiv($num1, $num2) { return p\Php70::intdiv($num1, $num2); } -} -if (!function_exists('preg_replace_callback_array')) { - function preg_replace_callback_array(array $pattern, $subject, $limit = -1, &$count = 0, $flags = null) { return p\Php70::preg_replace_callback_array($pattern, $subject, $limit, $count); } -} -if (!function_exists('error_clear_last')) { - function error_clear_last() { return p\Php70::error_clear_last(); } -} diff --git a/vendor/symfony/polyfill-php72/Php72.php b/vendor/symfony/polyfill-php72/Php72.php index 1e36d5e6..5e20d5bf 100644 --- a/vendor/symfony/polyfill-php72/Php72.php +++ b/vendor/symfony/polyfill-php72/Php72.php @@ -73,7 +73,7 @@ public static function php_os_family() return 'Windows'; } - $map = array( + $map = [ 'Darwin' => 'Darwin', 'DragonFly' => 'BSD', 'FreeBSD' => 'BSD', @@ -81,9 +81,9 @@ public static function php_os_family() 'OpenBSD' => 'BSD', 'Linux' => 'Linux', 'SunOS' => 'Solaris', - ); + ]; - return isset($map[PHP_OS]) ? $map[PHP_OS] : 'Unknown'; + return isset($map[\PHP_OS]) ? $map[\PHP_OS] : 'Unknown'; } public static function spl_object_id($object) @@ -102,7 +102,7 @@ public static function spl_object_id($object) public static function sapi_windows_vt100_support($stream, $enable = null) { if (!\is_resource($stream)) { - trigger_error('sapi_windows_vt100_support() expects parameter 1 to be resource, '.\gettype($stream).' given', E_USER_WARNING); + trigger_error('sapi_windows_vt100_support() expects parameter 1 to be resource, '.\gettype($stream).' given', \E_USER_WARNING); return false; } @@ -110,7 +110,7 @@ public static function sapi_windows_vt100_support($stream, $enable = null) $meta = stream_get_meta_data($stream); if ('STDIO' !== $meta['stream_type']) { - trigger_error('sapi_windows_vt100_support() was not able to analyze the specified stream', E_USER_WARNING); + trigger_error('sapi_windows_vt100_support() was not able to analyze the specified stream', \E_USER_WARNING); return false; } @@ -134,7 +134,7 @@ public static function sapi_windows_vt100_support($stream, $enable = null) public static function stream_isatty($stream) { if (!\is_resource($stream)) { - trigger_error('stream_isatty() expects parameter 1 to be resource, '.\gettype($stream).' given', E_USER_WARNING); + trigger_error('stream_isatty() expects parameter 1 to be resource, '.\gettype($stream).' given', \E_USER_WARNING); return false; } @@ -150,12 +150,12 @@ public static function stream_isatty($stream) private static function initHashMask() { - $obj = (object) array(); + $obj = (object) []; self::$hashMask = -1; // check if we are nested in an output buffering handler to prevent a fatal error with ob_start() below - $obFuncs = array('ob_clean', 'ob_end_clean', 'ob_flush', 'ob_end_flush', 'ob_get_contents', 'ob_get_flush'); - foreach (debug_backtrace(\PHP_VERSION_ID >= 50400 ? DEBUG_BACKTRACE_IGNORE_ARGS : false) as $frame) { + $obFuncs = ['ob_clean', 'ob_end_clean', 'ob_flush', 'ob_end_flush', 'ob_get_contents', 'ob_get_flush']; + foreach (debug_backtrace(\PHP_VERSION_ID >= 50400 ? \DEBUG_BACKTRACE_IGNORE_ARGS : false) as $frame) { if (isset($frame['function'][0]) && !isset($frame['class']) && 'o' === $frame['function'][0] && \in_array($frame['function'], $obFuncs)) { $frame['line'] = 0; break; @@ -182,7 +182,7 @@ public static function mb_chr($code, $encoding = null) $s = \chr(0xF0 | $code >> 18).\chr(0x80 | $code >> 12 & 0x3F).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F); } - if ('UTF-8' !== $encoding) { + if ('UTF-8' !== $encoding = $encoding ?? mb_internal_encoding()) { $s = mb_convert_encoding($s, $encoding, 'UTF-8'); } diff --git a/vendor/symfony/polyfill-php72/bootstrap.php b/vendor/symfony/polyfill-php72/bootstrap.php index 3154b2c3..b5c92d4c 100644 --- a/vendor/symfony/polyfill-php72/bootstrap.php +++ b/vendor/symfony/polyfill-php72/bootstrap.php @@ -11,7 +11,7 @@ use Symfony\Polyfill\Php72 as p; -if (PHP_VERSION_ID >= 70200) { +if (\PHP_VERSION_ID >= 70200) { return; } @@ -31,7 +31,7 @@ define('PHP_OS_FAMILY', p\Php72::php_os_family()); } -if ('\\' === DIRECTORY_SEPARATOR && !function_exists('sapi_windows_vt100_support')) { +if ('\\' === \DIRECTORY_SEPARATOR && !function_exists('sapi_windows_vt100_support')) { function sapi_windows_vt100_support($stream, $enable = null) { return p\Php72::sapi_windows_vt100_support($stream, $enable); } } if (!function_exists('stream_isatty')) { diff --git a/vendor/symfony/polyfill-php80/LICENSE b/vendor/symfony/polyfill-php80/LICENSE new file mode 100644 index 00000000..5593b1d8 --- /dev/null +++ b/vendor/symfony/polyfill-php80/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2020 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 +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/polyfill-php80/Php80.php b/vendor/symfony/polyfill-php80/Php80.php new file mode 100644 index 00000000..362dd1a9 --- /dev/null +++ b/vendor/symfony/polyfill-php80/Php80.php @@ -0,0 +1,115 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Php80; + +/** + * @author Ion Bazan + * @author Nico Oelgart + * @author Nicolas Grekas + * + * @internal + */ +final class Php80 +{ + public static function fdiv(float $dividend, float $divisor): float + { + return @($dividend / $divisor); + } + + public static function get_debug_type($value): string + { + switch (true) { + case null === $value: return 'null'; + case \is_bool($value): return 'bool'; + case \is_string($value): return 'string'; + case \is_array($value): return 'array'; + case \is_int($value): return 'int'; + case \is_float($value): return 'float'; + case \is_object($value): break; + case $value instanceof \__PHP_Incomplete_Class: return '__PHP_Incomplete_Class'; + default: + if (null === $type = @get_resource_type($value)) { + return 'unknown'; + } + + if ('Unknown' === $type) { + $type = 'closed'; + } + + return "resource ($type)"; + } + + $class = \get_class($value); + + if (false === strpos($class, '@')) { + return $class; + } + + return (get_parent_class($class) ?: key(class_implements($class)) ?: 'class').'@anonymous'; + } + + public static function get_resource_id($res): int + { + if (!\is_resource($res) && null === @get_resource_type($res)) { + throw new \TypeError(sprintf('Argument 1 passed to get_resource_id() must be of the type resource, %s given', get_debug_type($res))); + } + + return (int) $res; + } + + public static function preg_last_error_msg(): string + { + switch (preg_last_error()) { + case \PREG_INTERNAL_ERROR: + return 'Internal error'; + case \PREG_BAD_UTF8_ERROR: + return 'Malformed UTF-8 characters, possibly incorrectly encoded'; + case \PREG_BAD_UTF8_OFFSET_ERROR: + return 'The offset did not correspond to the beginning of a valid UTF-8 code point'; + case \PREG_BACKTRACK_LIMIT_ERROR: + return 'Backtrack limit exhausted'; + case \PREG_RECURSION_LIMIT_ERROR: + return 'Recursion limit exhausted'; + case \PREG_JIT_STACKLIMIT_ERROR: + return 'JIT stack limit exhausted'; + case \PREG_NO_ERROR: + return 'No error'; + default: + return 'Unknown error'; + } + } + + public static function str_contains(string $haystack, string $needle): bool + { + return '' === $needle || false !== strpos($haystack, $needle); + } + + public static function str_starts_with(string $haystack, string $needle): bool + { + return 0 === strncmp($haystack, $needle, \strlen($needle)); + } + + public static function str_ends_with(string $haystack, string $needle): bool + { + if ('' === $needle || $needle === $haystack) { + return true; + } + + if ('' === $haystack) { + return false; + } + + $needleLength = \strlen($needle); + + return $needleLength <= \strlen($haystack) && 0 === substr_compare($haystack, $needle, -$needleLength); + } +} diff --git a/vendor/symfony/polyfill-php80/PhpToken.php b/vendor/symfony/polyfill-php80/PhpToken.php new file mode 100644 index 00000000..fe6e6910 --- /dev/null +++ b/vendor/symfony/polyfill-php80/PhpToken.php @@ -0,0 +1,103 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Php80; + +/** + * @author Fedonyuk Anton + * + * @internal + */ +class PhpToken implements \Stringable +{ + /** + * @var int + */ + public $id; + + /** + * @var string + */ + public $text; + + /** + * @var int + */ + public $line; + + /** + * @var int + */ + public $pos; + + public function __construct(int $id, string $text, int $line = -1, int $position = -1) + { + $this->id = $id; + $this->text = $text; + $this->line = $line; + $this->pos = $position; + } + + public function getTokenName(): ?string + { + if ('UNKNOWN' === $name = token_name($this->id)) { + $name = \strlen($this->text) > 1 || \ord($this->text) < 32 ? null : $this->text; + } + + return $name; + } + + /** + * @param int|string|array $kind + */ + public function is($kind): bool + { + foreach ((array) $kind as $value) { + if (\in_array($value, [$this->id, $this->text], true)) { + return true; + } + } + + return false; + } + + public function isIgnorable(): bool + { + return \in_array($this->id, [\T_WHITESPACE, \T_COMMENT, \T_DOC_COMMENT, \T_OPEN_TAG], true); + } + + public function __toString(): string + { + return (string) $this->text; + } + + /** + * @return static[] + */ + public static function tokenize(string $code, int $flags = 0): array + { + $line = 1; + $position = 0; + $tokens = token_get_all($code, $flags); + foreach ($tokens as $index => $token) { + if (\is_string($token)) { + $id = \ord($token); + $text = $token; + } else { + [$id, $text, $line] = $token; + } + $tokens[$index] = new static($id, $text, $line, $position); + $position += \strlen($text); + } + + return $tokens; + } +} diff --git a/vendor/symfony/polyfill-php80/Resources/stubs/Attribute.php b/vendor/symfony/polyfill-php80/Resources/stubs/Attribute.php new file mode 100644 index 00000000..7ea6d277 --- /dev/null +++ b/vendor/symfony/polyfill-php80/Resources/stubs/Attribute.php @@ -0,0 +1,22 @@ +flags = $flags; + } +} diff --git a/vendor/symfony/polyfill-php80/Resources/stubs/PhpToken.php b/vendor/symfony/polyfill-php80/Resources/stubs/PhpToken.php new file mode 100644 index 00000000..72f10812 --- /dev/null +++ b/vendor/symfony/polyfill-php80/Resources/stubs/PhpToken.php @@ -0,0 +1,7 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Php80 as p; + +if (\PHP_VERSION_ID >= 80000) { + return; +} + +if (!defined('FILTER_VALIDATE_BOOL') && defined('FILTER_VALIDATE_BOOLEAN')) { + define('FILTER_VALIDATE_BOOL', \FILTER_VALIDATE_BOOLEAN); +} + +if (!function_exists('fdiv')) { + function fdiv(float $num1, float $num2): float { return p\Php80::fdiv($num1, $num2); } +} +if (!function_exists('preg_last_error_msg')) { + function preg_last_error_msg(): string { return p\Php80::preg_last_error_msg(); } +} +if (!function_exists('str_contains')) { + function str_contains(?string $haystack, ?string $needle): bool { return p\Php80::str_contains($haystack ?? '', $needle ?? ''); } +} +if (!function_exists('str_starts_with')) { + function str_starts_with(?string $haystack, ?string $needle): bool { return p\Php80::str_starts_with($haystack ?? '', $needle ?? ''); } +} +if (!function_exists('str_ends_with')) { + function str_ends_with(?string $haystack, ?string $needle): bool { return p\Php80::str_ends_with($haystack ?? '', $needle ?? ''); } +} +if (!function_exists('get_debug_type')) { + function get_debug_type($value): string { return p\Php80::get_debug_type($value); } +} +if (!function_exists('get_resource_id')) { + function get_resource_id($resource): int { return p\Php80::get_resource_id($resource); } +} diff --git a/vendor/symfony/translation-contracts/LICENSE b/vendor/symfony/translation-contracts/LICENSE new file mode 100644 index 00000000..74cdc2db --- /dev/null +++ b/vendor/symfony/translation-contracts/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018-2022 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 +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/translation-contracts/LocaleAwareInterface.php b/vendor/symfony/translation-contracts/LocaleAwareInterface.php new file mode 100644 index 00000000..dbd8894f --- /dev/null +++ b/vendor/symfony/translation-contracts/LocaleAwareInterface.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Translation; + +interface LocaleAwareInterface +{ + /** + * Sets the current locale. + * + * @param string $locale The locale + * + * @throws \InvalidArgumentException If the locale contains invalid characters + */ + public function setLocale($locale); + + /** + * Returns the current locale. + * + * @return string The locale + */ + public function getLocale(); +} diff --git a/vendor/symfony/translation-contracts/Test/TranslatorTest.php b/vendor/symfony/translation-contracts/Test/TranslatorTest.php new file mode 100644 index 00000000..83551cb8 --- /dev/null +++ b/vendor/symfony/translation-contracts/Test/TranslatorTest.php @@ -0,0 +1,376 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Translation\Test; + +use PHPUnit\Framework\TestCase; +use Symfony\Contracts\Translation\TranslatorInterface; +use Symfony\Contracts\Translation\TranslatorTrait; + +/** + * Test should cover all languages mentioned on http://translate.sourceforge.net/wiki/l10n/pluralforms + * and Plural forms mentioned on http://www.gnu.org/software/gettext/manual/gettext.html#Plural-forms. + * + * See also https://developer.mozilla.org/en/Localization_and_Plurals which mentions 15 rules having a maximum of 6 forms. + * The mozilla code is also interesting to check for. + * + * As mentioned by chx http://drupal.org/node/1273968 we can cover all by testing number from 0 to 199 + * + * The goal to cover all languages is to far fetched so this test case is smaller. + * + * @author Clemens Tolboom clemens@build2be.nl + */ +class TranslatorTest extends TestCase +{ + private $defaultLocale; + + protected function setUp(): void + { + $this->defaultLocale = \Locale::getDefault(); + \Locale::setDefault('en'); + } + + protected function tearDown(): void + { + \Locale::setDefault($this->defaultLocale); + } + + /** + * @return TranslatorInterface + */ + public function getTranslator() + { + return new class() implements TranslatorInterface { + use TranslatorTrait; + }; + } + + /** + * @dataProvider getTransTests + */ + public function testTrans($expected, $id, $parameters) + { + $translator = $this->getTranslator(); + + $this->assertEquals($expected, $translator->trans($id, $parameters)); + } + + /** + * @dataProvider getTransChoiceTests + */ + public function testTransChoiceWithExplicitLocale($expected, $id, $number) + { + $translator = $this->getTranslator(); + + $this->assertEquals($expected, $translator->trans($id, ['%count%' => $number])); + } + + /** + * @dataProvider getTransChoiceTests + */ + public function testTransChoiceWithDefaultLocale($expected, $id, $number) + { + $translator = $this->getTranslator(); + + $this->assertEquals($expected, $translator->trans($id, ['%count%' => $number])); + } + + /** + * @dataProvider getTransChoiceTests + */ + public function testTransChoiceWithEnUsPosix($expected, $id, $number) + { + $translator = $this->getTranslator(); + $translator->setLocale('en_US_POSIX'); + + $this->assertEquals($expected, $translator->trans($id, ['%count%' => $number])); + } + + public function testGetSetLocale() + { + $translator = $this->getTranslator(); + + $this->assertEquals('en', $translator->getLocale()); + } + + /** + * @requires extension intl + */ + public function testGetLocaleReturnsDefaultLocaleIfNotSet() + { + $translator = $this->getTranslator(); + + \Locale::setDefault('pt_BR'); + $this->assertEquals('pt_BR', $translator->getLocale()); + + \Locale::setDefault('en'); + $this->assertEquals('en', $translator->getLocale()); + } + + public function getTransTests() + { + return [ + ['Symfony is great!', 'Symfony is great!', []], + ['Symfony is awesome!', 'Symfony is %what%!', ['%what%' => 'awesome']], + ]; + } + + public function getTransChoiceTests() + { + return [ + ['There are no apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 0], + ['There is one apple', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 1], + ['There are 10 apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 10], + ['There are 0 apples', 'There is 1 apple|There are %count% apples', 0], + ['There is 1 apple', 'There is 1 apple|There are %count% apples', 1], + ['There are 10 apples', 'There is 1 apple|There are %count% apples', 10], + // custom validation messages may be coded with a fixed value + ['There are 2 apples', 'There are 2 apples', 2], + ]; + } + + /** + * @dataProvider getInternal + */ + public function testInterval($expected, $number, $interval) + { + $translator = $this->getTranslator(); + + $this->assertEquals($expected, $translator->trans($interval.' foo|[1,Inf[ bar', ['%count%' => $number])); + } + + public function getInternal() + { + return [ + ['foo', 3, '{1,2, 3 ,4}'], + ['bar', 10, '{1,2, 3 ,4}'], + ['bar', 3, '[1,2]'], + ['foo', 1, '[1,2]'], + ['foo', 2, '[1,2]'], + ['bar', 1, ']1,2['], + ['bar', 2, ']1,2['], + ['foo', log(0), '[-Inf,2['], + ['foo', -log(0), '[-2,+Inf]'], + ]; + } + + /** + * @dataProvider getChooseTests + */ + public function testChoose($expected, $id, $number) + { + $translator = $this->getTranslator(); + + $this->assertEquals($expected, $translator->trans($id, ['%count%' => $number])); + } + + public function testReturnMessageIfExactlyOneStandardRuleIsGiven() + { + $translator = $this->getTranslator(); + + $this->assertEquals('There are two apples', $translator->trans('There are two apples', ['%count%' => 2])); + } + + /** + * @dataProvider getNonMatchingMessages + */ + public function testThrowExceptionIfMatchingMessageCannotBeFound($id, $number) + { + $this->expectException(\InvalidArgumentException::class); + $translator = $this->getTranslator(); + + $translator->trans($id, ['%count%' => $number]); + } + + public function getNonMatchingMessages() + { + return [ + ['{0} There are no apples|{1} There is one apple', 2], + ['{1} There is one apple|]1,Inf] There are %count% apples', 0], + ['{1} There is one apple|]2,Inf] There are %count% apples', 2], + ['{0} There are no apples|There is one apple', 2], + ]; + } + + public function getChooseTests() + { + return [ + ['There are no apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 0], + ['There are no apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 0], + ['There are no apples', '{0}There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 0], + + ['There is one apple', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 1], + + ['There are 10 apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 10], + ['There are 10 apples', '{0} There are no apples|{1} There is one apple|]1,Inf]There are %count% apples', 10], + ['There are 10 apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 10], + + ['There are 0 apples', 'There is one apple|There are %count% apples', 0], + ['There is one apple', 'There is one apple|There are %count% apples', 1], + ['There are 10 apples', 'There is one apple|There are %count% apples', 10], + + ['There are 0 apples', 'one: There is one apple|more: There are %count% apples', 0], + ['There is one apple', 'one: There is one apple|more: There are %count% apples', 1], + ['There are 10 apples', 'one: There is one apple|more: There are %count% apples', 10], + + ['There are no apples', '{0} There are no apples|one: There is one apple|more: There are %count% apples', 0], + ['There is one apple', '{0} There are no apples|one: There is one apple|more: There are %count% apples', 1], + ['There are 10 apples', '{0} There are no apples|one: There is one apple|more: There are %count% apples', 10], + + ['', '{0}|{1} There is one apple|]1,Inf] There are %count% apples', 0], + ['', '{0} There are no apples|{1}|]1,Inf] There are %count% apples', 1], + + // Indexed only tests which are Gettext PoFile* compatible strings. + ['There are 0 apples', 'There is one apple|There are %count% apples', 0], + ['There is one apple', 'There is one apple|There are %count% apples', 1], + ['There are 2 apples', 'There is one apple|There are %count% apples', 2], + + // Tests for float numbers + ['There is almost one apple', '{0} There are no apples|]0,1[ There is almost one apple|{1} There is one apple|[1,Inf] There is more than one apple', 0.7], + ['There is one apple', '{0} There are no apples|]0,1[There are %count% apples|{1} There is one apple|[1,Inf] There is more than one apple', 1], + ['There is more than one apple', '{0} There are no apples|]0,1[There are %count% apples|{1} There is one apple|[1,Inf] There is more than one apple', 1.7], + ['There are no apples', '{0} There are no apples|]0,1[There are %count% apples|{1} There is one apple|[1,Inf] There is more than one apple', 0], + ['There are no apples', '{0} There are no apples|]0,1[There are %count% apples|{1} There is one apple|[1,Inf] There is more than one apple', 0.0], + ['There are no apples', '{0.0} There are no apples|]0,1[There are %count% apples|{1} There is one apple|[1,Inf] There is more than one apple', 0], + + // Test texts with new-lines + // with double-quotes and \n in id & double-quotes and actual newlines in text + ["This is a text with a\n new-line in it. Selector = 0.", '{0}This is a text with a + new-line in it. Selector = 0.|{1}This is a text with a + new-line in it. Selector = 1.|[1,Inf]This is a text with a + new-line in it. Selector > 1.', 0], + // with double-quotes and \n in id and single-quotes and actual newlines in text + ["This is a text with a\n new-line in it. Selector = 1.", '{0}This is a text with a + new-line in it. Selector = 0.|{1}This is a text with a + new-line in it. Selector = 1.|[1,Inf]This is a text with a + new-line in it. Selector > 1.', 1], + ["This is a text with a\n new-line in it. Selector > 1.", '{0}This is a text with a + new-line in it. Selector = 0.|{1}This is a text with a + new-line in it. Selector = 1.|[1,Inf]This is a text with a + new-line in it. Selector > 1.', 5], + // with double-quotes and id split accros lines + ['This is a text with a + new-line in it. Selector = 1.', '{0}This is a text with a + new-line in it. Selector = 0.|{1}This is a text with a + new-line in it. Selector = 1.|[1,Inf]This is a text with a + new-line in it. Selector > 1.', 1], + // with single-quotes and id split accros lines + ['This is a text with a + new-line in it. Selector > 1.', '{0}This is a text with a + new-line in it. Selector = 0.|{1}This is a text with a + new-line in it. Selector = 1.|[1,Inf]This is a text with a + new-line in it. Selector > 1.', 5], + // with single-quotes and \n in text + ['This is a text with a\nnew-line in it. Selector = 0.', '{0}This is a text with a\nnew-line in it. Selector = 0.|{1}This is a text with a\nnew-line in it. Selector = 1.|[1,Inf]This is a text with a\nnew-line in it. Selector > 1.', 0], + // with double-quotes and id split accros lines + ["This is a text with a\nnew-line in it. Selector = 1.", "{0}This is a text with a\nnew-line in it. Selector = 0.|{1}This is a text with a\nnew-line in it. Selector = 1.|[1,Inf]This is a text with a\nnew-line in it. Selector > 1.", 1], + // esacape pipe + ['This is a text with | in it. Selector = 0.', '{0}This is a text with || in it. Selector = 0.|{1}This is a text with || in it. Selector = 1.', 0], + // Empty plural set (2 plural forms) from a .PO file + ['', '|', 1], + // Empty plural set (3 plural forms) from a .PO file + ['', '||', 1], + ]; + } + + /** + * @dataProvider failingLangcodes + */ + public function testFailedLangcodes($nplural, $langCodes) + { + $matrix = $this->generateTestData($langCodes); + $this->validateMatrix($nplural, $matrix, false); + } + + /** + * @dataProvider successLangcodes + */ + public function testLangcodes($nplural, $langCodes) + { + $matrix = $this->generateTestData($langCodes); + $this->validateMatrix($nplural, $matrix); + } + + /** + * This array should contain all currently known langcodes. + * + * As it is impossible to have this ever complete we should try as hard as possible to have it almost complete. + * + * @return array + */ + public function successLangcodes() + { + return [ + ['1', ['ay', 'bo', 'cgg', 'dz', 'id', 'ja', 'jbo', 'ka', 'kk', 'km', 'ko', 'ky']], + ['2', ['nl', 'fr', 'en', 'de', 'de_GE', 'hy', 'hy_AM', 'en_US_POSIX']], + ['3', ['be', 'bs', 'cs', 'hr']], + ['4', ['cy', 'mt', 'sl']], + ['6', ['ar']], + ]; + } + + /** + * This array should be at least empty within the near future. + * + * This both depends on a complete list trying to add above as understanding + * the plural rules of the current failing languages. + * + * @return array with nplural together with langcodes + */ + public function failingLangcodes() + { + return [ + ['1', ['fa']], + ['2', ['jbo']], + ['3', ['cbs']], + ['4', ['gd', 'kw']], + ['5', ['ga']], + ]; + } + + /** + * We validate only on the plural coverage. Thus the real rules is not tested. + * + * @param string $nplural Plural expected + * @param array $matrix Containing langcodes and their plural index values + * @param bool $expectSuccess + */ + protected function validateMatrix($nplural, $matrix, $expectSuccess = true) + { + foreach ($matrix as $langCode => $data) { + $indexes = array_flip($data); + if ($expectSuccess) { + $this->assertCount($nplural, $indexes, "Langcode '$langCode' has '$nplural' plural forms."); + } else { + $this->assertNotEquals((int) $nplural, \count($indexes), "Langcode '$langCode' has '$nplural' plural forms."); + } + } + } + + protected function generateTestData($langCodes) + { + $translator = new class() { + use TranslatorTrait { + getPluralizationRule as public; + } + }; + + $matrix = []; + foreach ($langCodes as $langCode) { + for ($count = 0; $count < 200; ++$count) { + $plural = $translator->getPluralizationRule($count, $langCode); + $matrix[$langCode][$count] = $plural; + } + } + + return $matrix; + } +} diff --git a/vendor/symfony/translation-contracts/TranslatorInterface.php b/vendor/symfony/translation-contracts/TranslatorInterface.php new file mode 100644 index 00000000..d8676373 --- /dev/null +++ b/vendor/symfony/translation-contracts/TranslatorInterface.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Translation; + +/** + * @author Fabien Potencier + */ +interface TranslatorInterface +{ + /** + * Translates the given message. + * + * When a number is provided as a parameter named "%count%", the message is parsed for plural + * forms and a translation is chosen according to this number using the following rules: + * + * Given a message with different plural translations separated by a + * pipe (|), this method returns the correct portion of the message based + * on the given number, locale and the pluralization rules in the message + * itself. + * + * The message supports two different types of pluralization rules: + * + * interval: {0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples + * indexed: There is one apple|There are %count% apples + * + * The indexed solution can also contain labels (e.g. one: There is one apple). + * This is purely for making the translations more clear - it does not + * affect the functionality. + * + * The two methods can also be mixed: + * {0} There are no apples|one: There is one apple|more: There are %count% apples + * + * An interval can represent a finite set of numbers: + * {1,2,3,4} + * + * An interval can represent numbers between two numbers: + * [1, +Inf] + * ]-1,2[ + * + * The left delimiter can be [ (inclusive) or ] (exclusive). + * The right delimiter can be [ (exclusive) or ] (inclusive). + * Beside numbers, you can use -Inf and +Inf for the infinite. + * + * @see https://en.wikipedia.org/wiki/ISO_31-11 + * + * @param string $id The message id (may also be an object that can be cast to string) + * @param array $parameters An array of parameters for the message + * @param string|null $domain The domain for the message or null to use the default + * @param string|null $locale The locale or null to use the default + * + * @return string The translated string + * + * @throws \InvalidArgumentException If the locale contains invalid characters + */ + public function trans($id, array $parameters = [], $domain = null, $locale = null); +} diff --git a/vendor/symfony/translation-contracts/TranslatorTrait.php b/vendor/symfony/translation-contracts/TranslatorTrait.php new file mode 100644 index 00000000..92a6a002 --- /dev/null +++ b/vendor/symfony/translation-contracts/TranslatorTrait.php @@ -0,0 +1,260 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Translation; + +use Symfony\Component\Translation\Exception\InvalidArgumentException; + +/** + * A trait to help implement TranslatorInterface and LocaleAwareInterface. + * + * @author Fabien Potencier + */ +trait TranslatorTrait +{ + private $locale; + + /** + * {@inheritdoc} + */ + public function setLocale($locale) + { + $this->locale = (string) $locale; + } + + /** + * {@inheritdoc} + */ + public function getLocale() + { + return $this->locale ?: (class_exists(\Locale::class) ? \Locale::getDefault() : 'en'); + } + + /** + * {@inheritdoc} + */ + public function trans($id, array $parameters = [], $domain = null, $locale = null) + { + if ('' === $id = (string) $id) { + return ''; + } + + if (!isset($parameters['%count%']) || !is_numeric($parameters['%count%'])) { + return strtr($id, $parameters); + } + + $number = (float) $parameters['%count%']; + $locale = (string) $locale ?: $this->getLocale(); + + $parts = []; + if (preg_match('/^\|++$/', $id)) { + $parts = explode('|', $id); + } elseif (preg_match_all('/(?:\|\||[^\|])++/', $id, $matches)) { + $parts = $matches[0]; + } + + $intervalRegexp = <<<'EOF' +/^(?P + ({\s* + (\-?\d+(\.\d+)?[\s*,\s*\-?\d+(\.\d+)?]*) + \s*}) + + | + + (?P[\[\]]) + \s* + (?P-Inf|\-?\d+(\.\d+)?) + \s*,\s* + (?P\+?Inf|\-?\d+(\.\d+)?) + \s* + (?P[\[\]]) +)\s*(?P.*?)$/xs +EOF; + + $standardRules = []; + foreach ($parts as $part) { + $part = trim(str_replace('||', '|', $part)); + + // try to match an explicit rule, then fallback to the standard ones + if (preg_match($intervalRegexp, $part, $matches)) { + if ($matches[2]) { + foreach (explode(',', $matches[3]) as $n) { + if ($number == $n) { + return strtr($matches['message'], $parameters); + } + } + } else { + $leftNumber = '-Inf' === $matches['left'] ? -\INF : (float) $matches['left']; + $rightNumber = is_numeric($matches['right']) ? (float) $matches['right'] : \INF; + + if (('[' === $matches['left_delimiter'] ? $number >= $leftNumber : $number > $leftNumber) + && (']' === $matches['right_delimiter'] ? $number <= $rightNumber : $number < $rightNumber) + ) { + return strtr($matches['message'], $parameters); + } + } + } elseif (preg_match('/^\w+\:\s*(.*?)$/', $part, $matches)) { + $standardRules[] = $matches[1]; + } else { + $standardRules[] = $part; + } + } + + $position = $this->getPluralizationRule($number, $locale); + + if (!isset($standardRules[$position])) { + // when there's exactly one rule given, and that rule is a standard + // rule, use this rule + if (1 === \count($parts) && isset($standardRules[0])) { + return strtr($standardRules[0], $parameters); + } + + $message = sprintf('Unable to choose a translation for "%s" with locale "%s" for value "%d". Double check that this translation has the correct plural options (e.g. "There is one apple|There are %%count%% apples").', $id, $locale, $number); + + if (class_exists(InvalidArgumentException::class)) { + throw new InvalidArgumentException($message); + } + + throw new \InvalidArgumentException($message); + } + + return strtr($standardRules[$position], $parameters); + } + + /** + * Returns the plural position to use for the given locale and number. + * + * The plural rules are derived from code of the Zend Framework (2010-09-25), + * which is subject to the new BSD license (http://framework.zend.com/license/new-bsd). + * Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com) + */ + private function getPluralizationRule(float $number, string $locale): int + { + $number = abs($number); + + switch ('pt_BR' !== $locale && 'en_US_POSIX' !== $locale && \strlen($locale) > 3 ? substr($locale, 0, strrpos($locale, '_')) : $locale) { + case 'af': + case 'bn': + case 'bg': + case 'ca': + case 'da': + case 'de': + case 'el': + case 'en': + case 'en_US_POSIX': + case 'eo': + case 'es': + case 'et': + case 'eu': + case 'fa': + case 'fi': + case 'fo': + case 'fur': + case 'fy': + case 'gl': + case 'gu': + case 'ha': + case 'he': + case 'hu': + case 'is': + case 'it': + case 'ku': + case 'lb': + case 'ml': + case 'mn': + case 'mr': + case 'nah': + case 'nb': + case 'ne': + case 'nl': + case 'nn': + case 'no': + case 'oc': + case 'om': + case 'or': + case 'pa': + case 'pap': + case 'ps': + case 'pt': + case 'so': + case 'sq': + case 'sv': + case 'sw': + case 'ta': + case 'te': + case 'tk': + case 'ur': + case 'zu': + return (1 == $number) ? 0 : 1; + + case 'am': + case 'bh': + case 'fil': + case 'fr': + case 'gun': + case 'hi': + case 'hy': + case 'ln': + case 'mg': + case 'nso': + case 'pt_BR': + case 'ti': + case 'wa': + return ($number < 2) ? 0 : 1; + + case 'be': + case 'bs': + case 'hr': + case 'ru': + case 'sh': + case 'sr': + case 'uk': + return ((1 == $number % 10) && (11 != $number % 100)) ? 0 : ((($number % 10 >= 2) && ($number % 10 <= 4) && (($number % 100 < 10) || ($number % 100 >= 20))) ? 1 : 2); + + case 'cs': + case 'sk': + return (1 == $number) ? 0 : ((($number >= 2) && ($number <= 4)) ? 1 : 2); + + case 'ga': + return (1 == $number) ? 0 : ((2 == $number) ? 1 : 2); + + case 'lt': + return ((1 == $number % 10) && (11 != $number % 100)) ? 0 : ((($number % 10 >= 2) && (($number % 100 < 10) || ($number % 100 >= 20))) ? 1 : 2); + + case 'sl': + return (1 == $number % 100) ? 0 : ((2 == $number % 100) ? 1 : (((3 == $number % 100) || (4 == $number % 100)) ? 2 : 3)); + + case 'mk': + return (1 == $number % 10) ? 0 : 1; + + case 'mt': + return (1 == $number) ? 0 : (((0 == $number) || (($number % 100 > 1) && ($number % 100 < 11))) ? 1 : ((($number % 100 > 10) && ($number % 100 < 20)) ? 2 : 3)); + + case 'lv': + return (0 == $number) ? 0 : (((1 == $number % 10) && (11 != $number % 100)) ? 1 : 2); + + case 'pl': + return (1 == $number) ? 0 : ((($number % 10 >= 2) && ($number % 10 <= 4) && (($number % 100 < 12) || ($number % 100 > 14))) ? 1 : 2); + + case 'cy': + return (1 == $number) ? 0 : ((2 == $number) ? 1 : (((8 == $number) || (11 == $number)) ? 2 : 3)); + + case 'ro': + return (1 == $number) ? 0 : (((0 == $number) || (($number % 100 > 0) && ($number % 100 < 20))) ? 1 : 2); + + case 'ar': + return (0 == $number) ? 0 : ((1 == $number) ? 1 : ((2 == $number) ? 2 : ((($number % 100 >= 3) && ($number % 100 <= 10)) ? 3 : ((($number % 100 >= 11) && ($number % 100 <= 99)) ? 4 : 5)))); + + default: + return 0; + } + } +} diff --git a/vendor/symfony/translation/Catalogue/AbstractOperation.php b/vendor/symfony/translation/Catalogue/AbstractOperation.php index 4f0e26fa..4953563d 100644 --- a/vendor/symfony/translation/Catalogue/AbstractOperation.php +++ b/vendor/symfony/translation/Catalogue/AbstractOperation.php @@ -11,6 +11,8 @@ namespace Symfony\Component\Translation\Catalogue; +use Symfony\Component\Translation\Exception\InvalidArgumentException; +use Symfony\Component\Translation\Exception\LogicException; use Symfony\Component\Translation\MessageCatalogue; use Symfony\Component\Translation\MessageCatalogueInterface; @@ -29,7 +31,7 @@ abstract class AbstractOperation implements OperationInterface protected $result; /** - * @var null|array The domains affected by this operation + * @var array|null The domains affected by this operation */ private $domains; @@ -37,39 +39,38 @@ abstract class AbstractOperation implements OperationInterface * This array stores 'all', 'new' and 'obsolete' messages for all valid domains. * * The data structure of this array is as follows: - * ```php - * array( - * 'domain 1' => array( - * 'all' => array(...), - * 'new' => array(...), - * 'obsolete' => array(...) - * ), - * 'domain 2' => array( - * 'all' => array(...), - * 'new' => array(...), - * 'obsolete' => array(...) - * ), - * ... - * ) - * ``` + * + * [ + * 'domain 1' => [ + * 'all' => [...], + * 'new' => [...], + * 'obsolete' => [...] + * ], + * 'domain 2' => [ + * 'all' => [...], + * 'new' => [...], + * 'obsolete' => [...] + * ], + * ... + * ] * * @var array The array that stores 'all', 'new' and 'obsolete' messages */ protected $messages; /** - * @throws \LogicException + * @throws LogicException */ public function __construct(MessageCatalogueInterface $source, MessageCatalogueInterface $target) { if ($source->getLocale() !== $target->getLocale()) { - throw new \LogicException('Operated catalogues must belong to the same locale.'); + throw new LogicException('Operated catalogues must belong to the same locale.'); } $this->source = $source; $this->target = $target; $this->result = new MessageCatalogue($source->getLocale()); - $this->messages = array(); + $this->messages = []; } /** @@ -90,7 +91,7 @@ public function getDomains() public function getMessages($domain) { if (!\in_array($domain, $this->getDomains())) { - throw new \InvalidArgumentException(sprintf('Invalid domain: %s.', $domain)); + throw new InvalidArgumentException(sprintf('Invalid domain: "%s".', $domain)); } if (!isset($this->messages[$domain]['all'])) { @@ -106,7 +107,7 @@ public function getMessages($domain) public function getNewMessages($domain) { if (!\in_array($domain, $this->getDomains())) { - throw new \InvalidArgumentException(sprintf('Invalid domain: %s.', $domain)); + throw new InvalidArgumentException(sprintf('Invalid domain: "%s".', $domain)); } if (!isset($this->messages[$domain]['new'])) { @@ -122,7 +123,7 @@ public function getNewMessages($domain) public function getObsoleteMessages($domain) { if (!\in_array($domain, $this->getDomains())) { - throw new \InvalidArgumentException(sprintf('Invalid domain: %s.', $domain)); + throw new InvalidArgumentException(sprintf('Invalid domain: "%s".', $domain)); } if (!isset($this->messages[$domain]['obsolete'])) { diff --git a/vendor/symfony/translation/Catalogue/DiffOperation.php b/vendor/symfony/translation/Catalogue/DiffOperation.php deleted file mode 100644 index f29447e6..00000000 --- a/vendor/symfony/translation/Catalogue/DiffOperation.php +++ /dev/null @@ -1,33 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Translation\Catalogue; - -@trigger_error('The '.__NAMESPACE__.'\DiffOperation class is deprecated since Symfony 2.8 and will be removed in 3.0. Use the TargetOperation class in the same namespace instead.', E_USER_DEPRECATED); - -/** - * Diff operation between two catalogues. - * - * The name of 'Diff' is misleading because the operation - * has nothing to do with diff: - * - * intersection = source ∩ target = {x: x ∈ source ∧ x ∈ target} - * all = intersection ∪ (target ∖ intersection) = target - * new = all ∖ source = {x: x ∈ target ∧ x ∉ source} - * obsolete = source ∖ all = source ∖ target = {x: x ∈ source ∧ x ∉ target} - * - * @author Jean-François Simon - * - * @deprecated since version 2.8, to be removed in 3.0. Use TargetOperation instead. - */ -class DiffOperation extends TargetOperation -{ -} diff --git a/vendor/symfony/translation/Catalogue/MergeOperation.php b/vendor/symfony/translation/Catalogue/MergeOperation.php index 6db3f801..d55542c0 100644 --- a/vendor/symfony/translation/Catalogue/MergeOperation.php +++ b/vendor/symfony/translation/Catalogue/MergeOperation.php @@ -11,6 +11,8 @@ namespace Symfony\Component\Translation\Catalogue; +use Symfony\Component\Translation\MessageCatalogueInterface; + /** * Merge operation between two catalogues as follows: * all = source ∪ target = {x: x ∈ source ∨ x ∈ target} @@ -27,17 +29,19 @@ class MergeOperation extends AbstractOperation */ protected function processDomain($domain) { - $this->messages[$domain] = array( - 'all' => array(), - 'new' => array(), - 'obsolete' => array(), - ); + $this->messages[$domain] = [ + 'all' => [], + 'new' => [], + 'obsolete' => [], + ]; + $intlDomain = $domain.MessageCatalogueInterface::INTL_DOMAIN_SUFFIX; foreach ($this->source->all($domain) as $id => $message) { $this->messages[$domain]['all'][$id] = $message; - $this->result->add(array($id => $message), $domain); - if (null !== $keyMetadata = $this->source->getMetadata($id, $domain)) { - $this->result->setMetadata($id, $keyMetadata, $domain); + $d = $this->source->defines($id, $intlDomain) ? $intlDomain : $domain; + $this->result->add([$id => $message], $d); + if (null !== $keyMetadata = $this->source->getMetadata($id, $d)) { + $this->result->setMetadata($id, $keyMetadata, $d); } } @@ -45,9 +49,10 @@ protected function processDomain($domain) if (!$this->source->has($id, $domain)) { $this->messages[$domain]['all'][$id] = $message; $this->messages[$domain]['new'][$id] = $message; - $this->result->add(array($id => $message), $domain); - if (null !== $keyMetadata = $this->target->getMetadata($id, $domain)) { - $this->result->setMetadata($id, $keyMetadata, $domain); + $d = $this->target->defines($id, $intlDomain) ? $intlDomain : $domain; + $this->result->add([$id => $message], $d); + if (null !== $keyMetadata = $this->target->getMetadata($id, $d)) { + $this->result->setMetadata($id, $keyMetadata, $d); } } } diff --git a/vendor/symfony/translation/Catalogue/TargetOperation.php b/vendor/symfony/translation/Catalogue/TargetOperation.php index f3b0a29d..c8b06551 100644 --- a/vendor/symfony/translation/Catalogue/TargetOperation.php +++ b/vendor/symfony/translation/Catalogue/TargetOperation.php @@ -11,6 +11,8 @@ namespace Symfony\Component\Translation\Catalogue; +use Symfony\Component\Translation\MessageCatalogueInterface; + /** * Target operation between two catalogues: * intersection = source ∩ target = {x: x ∈ source ∧ x ∈ target} @@ -28,27 +30,29 @@ class TargetOperation extends AbstractOperation */ protected function processDomain($domain) { - $this->messages[$domain] = array( - 'all' => array(), - 'new' => array(), - 'obsolete' => array(), - ); + $this->messages[$domain] = [ + 'all' => [], + 'new' => [], + 'obsolete' => [], + ]; + $intlDomain = $domain.MessageCatalogueInterface::INTL_DOMAIN_SUFFIX; // For 'all' messages, the code can't be simplified as ``$this->messages[$domain]['all'] = $target->all($domain);``, // because doing so will drop messages like {x: x ∈ source ∧ x ∉ target.all ∧ x ∈ target.fallback} // - // For 'new' messages, the code can't be simplied as ``array_diff_assoc($this->target->all($domain), $this->source->all($domain));`` + // For 'new' messages, the code can't be simplified as ``array_diff_assoc($this->target->all($domain), $this->source->all($domain));`` // because doing so will not exclude messages like {x: x ∈ target ∧ x ∉ source.all ∧ x ∈ source.fallback} // - // For 'obsolete' messages, the code can't be simplifed as ``array_diff_assoc($this->source->all($domain), $this->target->all($domain))`` + // For 'obsolete' messages, the code can't be simplified as ``array_diff_assoc($this->source->all($domain), $this->target->all($domain))`` // because doing so will not exclude messages like {x: x ∈ source ∧ x ∉ target.all ∧ x ∈ target.fallback} foreach ($this->source->all($domain) as $id => $message) { if ($this->target->has($id, $domain)) { $this->messages[$domain]['all'][$id] = $message; - $this->result->add(array($id => $message), $domain); - if (null !== $keyMetadata = $this->source->getMetadata($id, $domain)) { - $this->result->setMetadata($id, $keyMetadata, $domain); + $d = $this->source->defines($id, $intlDomain) ? $intlDomain : $domain; + $this->result->add([$id => $message], $d); + if (null !== $keyMetadata = $this->source->getMetadata($id, $d)) { + $this->result->setMetadata($id, $keyMetadata, $d); } } else { $this->messages[$domain]['obsolete'][$id] = $message; @@ -59,9 +63,10 @@ protected function processDomain($domain) if (!$this->source->has($id, $domain)) { $this->messages[$domain]['all'][$id] = $message; $this->messages[$domain]['new'][$id] = $message; - $this->result->add(array($id => $message), $domain); - if (null !== $keyMetadata = $this->target->getMetadata($id, $domain)) { - $this->result->setMetadata($id, $keyMetadata, $domain); + $d = $this->target->defines($id, $intlDomain) ? $intlDomain : $domain; + $this->result->add([$id => $message], $d); + if (null !== $keyMetadata = $this->target->getMetadata($id, $d)) { + $this->result->setMetadata($id, $keyMetadata, $d); } } } diff --git a/vendor/symfony/translation/Command/XliffLintCommand.php b/vendor/symfony/translation/Command/XliffLintCommand.php new file mode 100644 index 00000000..80d45bf5 --- /dev/null +++ b/vendor/symfony/translation/Command/XliffLintCommand.php @@ -0,0 +1,273 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Command; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Exception\RuntimeException; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\Translation\Exception\InvalidArgumentException; +use Symfony\Component\Translation\Util\XliffUtils; + +/** + * Validates XLIFF files syntax and outputs encountered errors. + * + * @author Grégoire Pineau + * @author Robin Chalas + * @author Javier Eguiluz + */ +class XliffLintCommand extends Command +{ + protected static $defaultName = 'lint:xliff'; + + private $format; + private $displayCorrectFiles; + private $directoryIteratorProvider; + private $isReadableProvider; + private $requireStrictFileNames; + + public function __construct(string $name = null, callable $directoryIteratorProvider = null, callable $isReadableProvider = null, bool $requireStrictFileNames = true) + { + parent::__construct($name); + + $this->directoryIteratorProvider = $directoryIteratorProvider; + $this->isReadableProvider = $isReadableProvider; + $this->requireStrictFileNames = $requireStrictFileNames; + } + + /** + * {@inheritdoc} + */ + protected function configure() + { + $this + ->setDescription('Lint an XLIFF file and outputs encountered errors') + ->addArgument('filename', InputArgument::IS_ARRAY, 'A file, a directory or "-" for reading from STDIN') + ->addOption('format', null, InputOption::VALUE_REQUIRED, 'The output format', 'txt') + ->setHelp(<<%command.name% command lints an XLIFF file and outputs to STDOUT +the first encountered syntax error. + +You can validates XLIFF contents passed from STDIN: + + cat filename | php %command.full_name% - + +You can also validate the syntax of a file: + + php %command.full_name% filename + +Or of a whole directory: + + php %command.full_name% dirname + php %command.full_name% dirname --format=json + +EOF + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $io = new SymfonyStyle($input, $output); + $filenames = (array) $input->getArgument('filename'); + $this->format = $input->getOption('format'); + $this->displayCorrectFiles = $output->isVerbose(); + + if (['-'] === $filenames) { + return $this->display($io, [$this->validate(file_get_contents('php://stdin'))]); + } + + // @deprecated to be removed in 5.0 + if (!$filenames) { + if (0 !== ftell(\STDIN)) { + throw new RuntimeException('Please provide a filename or pipe file content to STDIN.'); + } + + @trigger_error('Piping content from STDIN to the "lint:xliff" command without passing the dash symbol "-" as argument is deprecated since Symfony 4.4.', \E_USER_DEPRECATED); + + return $this->display($io, [$this->validate(file_get_contents('php://stdin'))]); + } + + $filesInfo = []; + foreach ($filenames as $filename) { + if (!$this->isReadable($filename)) { + throw new RuntimeException(sprintf('File or directory "%s" is not readable.', $filename)); + } + + foreach ($this->getFiles($filename) as $file) { + $filesInfo[] = $this->validate(file_get_contents($file), $file); + } + } + + return $this->display($io, $filesInfo); + } + + private function validate(string $content, string $file = null): array + { + $errors = []; + + // Avoid: Warning DOMDocument::loadXML(): Empty string supplied as input + if ('' === trim($content)) { + return ['file' => $file, 'valid' => true]; + } + + $internal = libxml_use_internal_errors(true); + + $document = new \DOMDocument(); + $document->loadXML($content); + + if (null !== $targetLanguage = $this->getTargetLanguageFromFile($document)) { + $normalizedLocalePattern = sprintf('(%s|%s)', preg_quote($targetLanguage, '/'), preg_quote(str_replace('-', '_', $targetLanguage), '/')); + // strict file names require translation files to be named '____.locale.xlf' + // otherwise, both '____.locale.xlf' and 'locale.____.xlf' are allowed + // also, the regexp matching must be case-insensitive, as defined for 'target-language' values + // http://docs.oasis-open.org/xliff/v1.2/os/xliff-core.html#target-language + $expectedFilenamePattern = $this->requireStrictFileNames ? sprintf('/^.*\.(?i:%s)\.(?:xlf|xliff)/', $normalizedLocalePattern) : sprintf('/^(?:.*\.(?i:%s)|(?i:%s)\..*)\.(?:xlf|xliff)/', $normalizedLocalePattern, $normalizedLocalePattern); + + if (0 === preg_match($expectedFilenamePattern, basename($file))) { + $errors[] = [ + 'line' => -1, + 'column' => -1, + 'message' => sprintf('There is a mismatch between the language included in the file name ("%s") and the "%s" value used in the "target-language" attribute of the file.', basename($file), $targetLanguage), + ]; + } + } + + foreach (XliffUtils::validateSchema($document) as $xmlError) { + $errors[] = [ + 'line' => $xmlError['line'], + 'column' => $xmlError['column'], + 'message' => $xmlError['message'], + ]; + } + + libxml_clear_errors(); + libxml_use_internal_errors($internal); + + return ['file' => $file, 'valid' => 0 === \count($errors), 'messages' => $errors]; + } + + private function display(SymfonyStyle $io, array $files) + { + switch ($this->format) { + case 'txt': + return $this->displayTxt($io, $files); + case 'json': + return $this->displayJson($io, $files); + default: + throw new InvalidArgumentException(sprintf('The format "%s" is not supported.', $this->format)); + } + } + + private function displayTxt(SymfonyStyle $io, array $filesInfo) + { + $countFiles = \count($filesInfo); + $erroredFiles = 0; + + foreach ($filesInfo as $info) { + if ($info['valid'] && $this->displayCorrectFiles) { + $io->comment('OK'.($info['file'] ? sprintf(' in %s', $info['file']) : '')); + } elseif (!$info['valid']) { + ++$erroredFiles; + $io->text(' ERROR '.($info['file'] ? sprintf(' in %s', $info['file']) : '')); + $io->listing(array_map(function ($error) { + // general document errors have a '-1' line number + return -1 === $error['line'] ? $error['message'] : sprintf('Line %d, Column %d: %s', $error['line'], $error['column'], $error['message']); + }, $info['messages'])); + } + } + + if (0 === $erroredFiles) { + $io->success(sprintf('All %d XLIFF files contain valid syntax.', $countFiles)); + } else { + $io->warning(sprintf('%d XLIFF files have valid syntax and %d contain errors.', $countFiles - $erroredFiles, $erroredFiles)); + } + + return min($erroredFiles, 1); + } + + private function displayJson(SymfonyStyle $io, array $filesInfo) + { + $errors = 0; + + array_walk($filesInfo, function (&$v) use (&$errors) { + $v['file'] = (string) $v['file']; + if (!$v['valid']) { + ++$errors; + } + }); + + $io->writeln(json_encode($filesInfo, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES)); + + return min($errors, 1); + } + + private function getFiles(string $fileOrDirectory) + { + if (is_file($fileOrDirectory)) { + yield new \SplFileInfo($fileOrDirectory); + + return; + } + + foreach ($this->getDirectoryIterator($fileOrDirectory) as $file) { + if (!\in_array($file->getExtension(), ['xlf', 'xliff'])) { + continue; + } + + yield $file; + } + } + + private function getDirectoryIterator(string $directory) + { + $default = function ($directory) { + return new \RecursiveIteratorIterator( + new \RecursiveDirectoryIterator($directory, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS), + \RecursiveIteratorIterator::LEAVES_ONLY + ); + }; + + if (null !== $this->directoryIteratorProvider) { + return ($this->directoryIteratorProvider)($directory, $default); + } + + return $default($directory); + } + + private function isReadable(string $fileOrDirectory) + { + $default = function ($fileOrDirectory) { + return is_readable($fileOrDirectory); + }; + + if (null !== $this->isReadableProvider) { + return ($this->isReadableProvider)($fileOrDirectory, $default); + } + + return $default($fileOrDirectory); + } + + private function getTargetLanguageFromFile(\DOMDocument $xliffContents): ?string + { + foreach ($xliffContents->getElementsByTagName('file')[0]->attributes ?? [] as $attribute) { + if ('target-language' === $attribute->nodeName) { + return $attribute->nodeValue; + } + } + + return null; + } +} diff --git a/vendor/symfony/translation/DataCollector/TranslationDataCollector.php b/vendor/symfony/translation/DataCollector/TranslationDataCollector.php index 8da0f77d..88894ec0 100644 --- a/vendor/symfony/translation/DataCollector/TranslationDataCollector.php +++ b/vendor/symfony/translation/DataCollector/TranslationDataCollector.php @@ -16,9 +16,12 @@ use Symfony\Component\HttpKernel\DataCollector\DataCollector; use Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface; use Symfony\Component\Translation\DataCollectorTranslator; +use Symfony\Component\VarDumper\Cloner\Data; /** * @author Abdellatif Ait boudad + * + * @final since Symfony 4.4 */ class TranslationDataCollector extends DataCollector implements LateDataCollectorInterface { @@ -36,23 +39,37 @@ public function lateCollect() { $messages = $this->sanitizeCollectedMessages($this->translator->getCollectedMessages()); - $this->data = $this->computeCount($messages); + $this->data += $this->computeCount($messages); $this->data['messages'] = $messages; + + $this->data = $this->cloneVar($this->data); + } + + /** + * {@inheritdoc} + * + * @param \Throwable|null $exception + */ + public function collect(Request $request, Response $response/* , \Throwable $exception = null */) + { + $this->data['locale'] = $this->translator->getLocale(); + $this->data['fallback_locales'] = $this->translator->getFallbackLocales(); } /** * {@inheritdoc} */ - public function collect(Request $request, Response $response, \Exception $exception = null) + public function reset() { + $this->data = []; } /** - * @return array + * @return array|Data */ public function getMessages() { - return isset($this->data['messages']) ? $this->data['messages'] : array(); + return $this->data['messages'] ?? []; } /** @@ -60,7 +77,7 @@ public function getMessages() */ public function getCountMissings() { - return isset($this->data[DataCollectorTranslator::MESSAGE_MISSING]) ? $this->data[DataCollectorTranslator::MESSAGE_MISSING] : 0; + return $this->data[DataCollectorTranslator::MESSAGE_MISSING] ?? 0; } /** @@ -68,7 +85,7 @@ public function getCountMissings() */ public function getCountFallbacks() { - return isset($this->data[DataCollectorTranslator::MESSAGE_EQUALS_FALLBACK]) ? $this->data[DataCollectorTranslator::MESSAGE_EQUALS_FALLBACK] : 0; + return $this->data[DataCollectorTranslator::MESSAGE_EQUALS_FALLBACK] ?? 0; } /** @@ -76,7 +93,20 @@ public function getCountFallbacks() */ public function getCountDefines() { - return isset($this->data[DataCollectorTranslator::MESSAGE_DEFINED]) ? $this->data[DataCollectorTranslator::MESSAGE_DEFINED] : 0; + return $this->data[DataCollectorTranslator::MESSAGE_DEFINED] ?? 0; + } + + public function getLocale() + { + return !empty($this->data['locale']) ? $this->data['locale'] : null; + } + + /** + * @internal since Symfony 4.2 + */ + public function getFallbackLocales() + { + return (isset($this->data['fallback_locales']) && \count($this->data['fallback_locales']) > 0) ? $this->data['fallback_locales'] : []; } /** @@ -87,15 +117,15 @@ public function getName() return 'translation'; } - private function sanitizeCollectedMessages($messages) + private function sanitizeCollectedMessages(array $messages) { - $result = array(); + $result = []; foreach ($messages as $key => $message) { $messageId = $message['locale'].$message['domain'].$message['id']; if (!isset($result[$messageId])) { $message['count'] = 1; - $message['parameters'] = !empty($message['parameters']) ? array($message['parameters']) : array(); + $message['parameters'] = !empty($message['parameters']) ? [$message['parameters']] : []; $messages[$key]['translation'] = $this->sanitizeString($message['translation']); $result[$messageId] = $message; } else { @@ -112,13 +142,13 @@ private function sanitizeCollectedMessages($messages) return $result; } - private function computeCount($messages) + private function computeCount(array $messages) { - $count = array( + $count = [ DataCollectorTranslator::MESSAGE_DEFINED => 0, DataCollectorTranslator::MESSAGE_MISSING => 0, DataCollectorTranslator::MESSAGE_EQUALS_FALLBACK => 0, - ); + ]; foreach ($messages as $message) { ++$count[$message['state']]; @@ -127,7 +157,7 @@ private function computeCount($messages) return $count; } - private function sanitizeString($string, $length = 80) + private function sanitizeString(string $string, int $length = 80) { $string = trim(preg_replace('/\s+/', ' ', $string)); diff --git a/vendor/symfony/translation/DataCollectorTranslator.php b/vendor/symfony/translation/DataCollectorTranslator.php index 2916eb96..68acf156 100644 --- a/vendor/symfony/translation/DataCollectorTranslator.php +++ b/vendor/symfony/translation/DataCollectorTranslator.php @@ -11,29 +11,38 @@ namespace Symfony\Component\Translation; +use Symfony\Component\HttpKernel\CacheWarmer\WarmableInterface; +use Symfony\Component\Translation\Exception\InvalidArgumentException; +use Symfony\Component\Translation\TranslatorInterface as LegacyTranslatorInterface; +use Symfony\Contracts\Translation\LocaleAwareInterface; +use Symfony\Contracts\Translation\TranslatorInterface; + /** * @author Abdellatif Ait boudad */ -class DataCollectorTranslator implements TranslatorInterface, TranslatorBagInterface +class DataCollectorTranslator implements LegacyTranslatorInterface, TranslatorInterface, TranslatorBagInterface, WarmableInterface { - const MESSAGE_DEFINED = 0; - const MESSAGE_MISSING = 1; - const MESSAGE_EQUALS_FALLBACK = 2; + public const MESSAGE_DEFINED = 0; + public const MESSAGE_MISSING = 1; + public const MESSAGE_EQUALS_FALLBACK = 2; /** * @var TranslatorInterface|TranslatorBagInterface */ private $translator; - private $messages = array(); + private $messages = []; /** * @param TranslatorInterface $translator The translator must implement TranslatorBagInterface */ - public function __construct(TranslatorInterface $translator) + public function __construct($translator) { - if (!$translator instanceof TranslatorBagInterface) { - throw new \InvalidArgumentException(sprintf('The Translator "%s" must implement TranslatorInterface and TranslatorBagInterface.', \get_class($translator))); + if (!$translator instanceof LegacyTranslatorInterface && !$translator instanceof TranslatorInterface) { + throw new \TypeError(sprintf('Argument 1 passed to "%s()" must be an instance of "%s", "%s" given.', __METHOD__, TranslatorInterface::class, \is_object($translator) ? \get_class($translator) : \gettype($translator))); + } + if (!$translator instanceof TranslatorBagInterface || !$translator instanceof LocaleAwareInterface) { + throw new InvalidArgumentException(sprintf('The Translator "%s" must implement TranslatorInterface, TranslatorBagInterface and LocaleAwareInterface.', \get_class($translator))); } $this->translator = $translator; @@ -42,7 +51,7 @@ public function __construct(TranslatorInterface $translator) /** * {@inheritdoc} */ - public function trans($id, array $parameters = array(), $domain = null, $locale = null) + public function trans($id, array $parameters = [], $domain = null, $locale = null) { $trans = $this->translator->trans($id, $parameters, $domain, $locale); $this->collectMessage($locale, $domain, $id, $trans, $parameters); @@ -52,11 +61,18 @@ public function trans($id, array $parameters = array(), $domain = null, $locale /** * {@inheritdoc} + * + * @deprecated since Symfony 4.2, use the trans() method instead with a %count% parameter */ - public function transChoice($id, $number, array $parameters = array(), $domain = null, $locale = null) + public function transChoice($id, $number, array $parameters = [], $domain = null, $locale = null) { - $trans = $this->translator->transChoice($id, $number, $parameters, $domain, $locale); - $this->collectMessage($locale, $domain, $id, $trans, $parameters, $number); + if ($this->translator instanceof TranslatorInterface) { + $trans = $this->translator->trans($id, ['%count%' => $number] + $parameters, $domain, $locale); + } else { + $trans = $this->translator->transChoice($id, $number, $parameters, $domain, $locale); + } + + $this->collectMessage($locale, $domain, $id, $trans, ['%count%' => $number] + $parameters); return $trans; } @@ -85,10 +101,20 @@ public function getCatalogue($locale = null) return $this->translator->getCatalogue($locale); } + /** + * {@inheritdoc} + */ + public function warmUp($cacheDir) + { + if ($this->translator instanceof WarmableInterface) { + $this->translator->warmUp($cacheDir); + } + } + /** * Gets the fallback locales. * - * @return array $locales The fallback locales + * @return array The fallback locales */ public function getFallbackLocales() { @@ -96,7 +122,7 @@ public function getFallbackLocales() return $this->translator->getFallbackLocales(); } - return array(); + return []; } /** @@ -104,7 +130,7 @@ public function getFallbackLocales() */ public function __call($method, $args) { - return \call_user_func_array(array($this->translator, $method), $args); + return $this->translator->{$method}(...$args); } /** @@ -115,15 +141,7 @@ public function getCollectedMessages() return $this->messages; } - /** - * @param string|null $locale - * @param string|null $domain - * @param string $id - * @param string $translation - * @param array|null $parameters - * @param int|null $number - */ - private function collectMessage($locale, $domain, $id, $translation, $parameters = array(), $number = null) + private function collectMessage(?string $locale, ?string $domain, ?string $id, string $translation, ?array $parameters = []) { if (null === $domain) { $domain = 'messages'; @@ -132,6 +150,7 @@ private function collectMessage($locale, $domain, $id, $translation, $parameters $id = (string) $id; $catalogue = $this->translator->getCatalogue($locale); $locale = $catalogue->getLocale(); + $fallbackLocale = null; if ($catalogue->defines($id, $domain)) { $state = self::MESSAGE_DEFINED; } elseif ($catalogue->has($id, $domain)) { @@ -140,24 +159,24 @@ private function collectMessage($locale, $domain, $id, $translation, $parameters $fallbackCatalogue = $catalogue->getFallbackCatalogue(); while ($fallbackCatalogue) { if ($fallbackCatalogue->defines($id, $domain)) { - $locale = $fallbackCatalogue->getLocale(); + $fallbackLocale = $fallbackCatalogue->getLocale(); break; } - $fallbackCatalogue = $fallbackCatalogue->getFallbackCatalogue(); } } else { $state = self::MESSAGE_MISSING; } - $this->messages[] = array( + $this->messages[] = [ 'locale' => $locale, + 'fallbackLocale' => $fallbackLocale, 'domain' => $domain, 'id' => $id, 'translation' => $translation, 'parameters' => $parameters, - 'transChoiceNumber' => $number, 'state' => $state, - ); + 'transChoiceNumber' => isset($parameters['%count%']) && is_numeric($parameters['%count%']) ? $parameters['%count%'] : null, + ]; } } diff --git a/vendor/symfony/translation/DependencyInjection/TranslationDumperPass.php b/vendor/symfony/translation/DependencyInjection/TranslationDumperPass.php new file mode 100644 index 00000000..930f36d7 --- /dev/null +++ b/vendor/symfony/translation/DependencyInjection/TranslationDumperPass.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\DependencyInjection; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; + +/** + * Adds tagged translation.formatter services to translation writer. + */ +class TranslationDumperPass implements CompilerPassInterface +{ + private $writerServiceId; + private $dumperTag; + + public function __construct(string $writerServiceId = 'translation.writer', string $dumperTag = 'translation.dumper') + { + $this->writerServiceId = $writerServiceId; + $this->dumperTag = $dumperTag; + } + + public function process(ContainerBuilder $container) + { + if (!$container->hasDefinition($this->writerServiceId)) { + return; + } + + $definition = $container->getDefinition($this->writerServiceId); + + foreach ($container->findTaggedServiceIds($this->dumperTag, true) as $id => $attributes) { + $definition->addMethodCall('addDumper', [$attributes[0]['alias'], new Reference($id)]); + } + } +} diff --git a/vendor/symfony/translation/DependencyInjection/TranslationExtractorPass.php b/vendor/symfony/translation/DependencyInjection/TranslationExtractorPass.php new file mode 100644 index 00000000..d08b2ba5 --- /dev/null +++ b/vendor/symfony/translation/DependencyInjection/TranslationExtractorPass.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\DependencyInjection; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Exception\RuntimeException; +use Symfony\Component\DependencyInjection\Reference; + +/** + * Adds tagged translation.extractor services to translation extractor. + */ +class TranslationExtractorPass implements CompilerPassInterface +{ + private $extractorServiceId; + private $extractorTag; + + public function __construct(string $extractorServiceId = 'translation.extractor', string $extractorTag = 'translation.extractor') + { + $this->extractorServiceId = $extractorServiceId; + $this->extractorTag = $extractorTag; + } + + public function process(ContainerBuilder $container) + { + if (!$container->hasDefinition($this->extractorServiceId)) { + return; + } + + $definition = $container->getDefinition($this->extractorServiceId); + + foreach ($container->findTaggedServiceIds($this->extractorTag, true) as $id => $attributes) { + if (!isset($attributes[0]['alias'])) { + throw new RuntimeException(sprintf('The alias for the tag "translation.extractor" of service "%s" must be set.', $id)); + } + + $definition->addMethodCall('addExtractor', [$attributes[0]['alias'], new Reference($id)]); + } + } +} diff --git a/vendor/symfony/translation/DependencyInjection/TranslatorPass.php b/vendor/symfony/translation/DependencyInjection/TranslatorPass.php new file mode 100644 index 00000000..ed4a840d --- /dev/null +++ b/vendor/symfony/translation/DependencyInjection/TranslatorPass.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\DependencyInjection; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; + +class TranslatorPass implements CompilerPassInterface +{ + private $translatorServiceId; + private $readerServiceId; + private $loaderTag; + private $debugCommandServiceId; + private $updateCommandServiceId; + + public function __construct(string $translatorServiceId = 'translator.default', string $readerServiceId = 'translation.reader', string $loaderTag = 'translation.loader', string $debugCommandServiceId = 'console.command.translation_debug', string $updateCommandServiceId = 'console.command.translation_update') + { + $this->translatorServiceId = $translatorServiceId; + $this->readerServiceId = $readerServiceId; + $this->loaderTag = $loaderTag; + $this->debugCommandServiceId = $debugCommandServiceId; + $this->updateCommandServiceId = $updateCommandServiceId; + } + + public function process(ContainerBuilder $container) + { + if (!$container->hasDefinition($this->translatorServiceId)) { + return; + } + + $loaders = []; + $loaderRefs = []; + foreach ($container->findTaggedServiceIds($this->loaderTag, true) as $id => $attributes) { + $loaderRefs[$id] = new Reference($id); + $loaders[$id][] = $attributes[0]['alias']; + if (isset($attributes[0]['legacy-alias'])) { + $loaders[$id][] = $attributes[0]['legacy-alias']; + } + } + + if ($container->hasDefinition($this->readerServiceId)) { + $definition = $container->getDefinition($this->readerServiceId); + foreach ($loaders as $id => $formats) { + foreach ($formats as $format) { + $definition->addMethodCall('addLoader', [$format, $loaderRefs[$id]]); + } + } + } + + $container + ->findDefinition($this->translatorServiceId) + ->replaceArgument(0, ServiceLocatorTagPass::register($container, $loaderRefs)) + ->replaceArgument(3, $loaders) + ; + + if (!$container->hasParameter('twig.default_path')) { + return; + } + + $paths = array_keys($container->getDefinition('twig.template_iterator')->getArgument(2)); + if ($container->hasDefinition($this->debugCommandServiceId)) { + $definition = $container->getDefinition($this->debugCommandServiceId); + $definition->replaceArgument(4, $container->getParameter('twig.default_path')); + + if (\count($definition->getArguments()) > 6) { + $definition->replaceArgument(6, $paths); + } + } + if ($container->hasDefinition($this->updateCommandServiceId)) { + $definition = $container->getDefinition($this->updateCommandServiceId); + $definition->replaceArgument(5, $container->getParameter('twig.default_path')); + + if (\count($definition->getArguments()) > 7) { + $definition->replaceArgument(7, $paths); + } + } + } +} diff --git a/vendor/symfony/translation/DependencyInjection/TranslatorPathsPass.php b/vendor/symfony/translation/DependencyInjection/TranslatorPathsPass.php new file mode 100644 index 00000000..37c8c571 --- /dev/null +++ b/vendor/symfony/translation/DependencyInjection/TranslatorPathsPass.php @@ -0,0 +1,147 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\DependencyInjection; + +use Symfony\Component\DependencyInjection\Compiler\AbstractRecursivePass; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\DependencyInjection\ServiceLocator; + +/** + * @author Yonel Ceruto + */ +class TranslatorPathsPass extends AbstractRecursivePass +{ + private $translatorServiceId; + private $debugCommandServiceId; + private $updateCommandServiceId; + private $resolverServiceId; + private $level = 0; + private $paths = []; + private $definitions = []; + private $controllers = []; + + public function __construct(string $translatorServiceId = 'translator', string $debugCommandServiceId = 'console.command.translation_debug', string $updateCommandServiceId = 'console.command.translation_update', string $resolverServiceId = 'argument_resolver.service') + { + $this->translatorServiceId = $translatorServiceId; + $this->debugCommandServiceId = $debugCommandServiceId; + $this->updateCommandServiceId = $updateCommandServiceId; + $this->resolverServiceId = $resolverServiceId; + } + + public function process(ContainerBuilder $container) + { + if (!$container->hasDefinition($this->translatorServiceId)) { + return; + } + + foreach ($this->findControllerArguments($container) as $controller => $argument) { + $id = substr($controller, 0, strpos($controller, ':') ?: \strlen($controller)); + if ($container->hasDefinition($id)) { + [$locatorRef] = $argument->getValues(); + $this->controllers[(string) $locatorRef][$container->getDefinition($id)->getClass()] = true; + } + } + + try { + parent::process($container); + + $paths = []; + foreach ($this->paths as $class => $_) { + if (($r = $container->getReflectionClass($class)) && !$r->isInterface()) { + $paths[] = $r->getFileName(); + foreach ($r->getTraits() as $trait) { + $paths[] = $trait->getFileName(); + } + } + } + if ($paths) { + if ($container->hasDefinition($this->debugCommandServiceId)) { + $definition = $container->getDefinition($this->debugCommandServiceId); + $definition->replaceArgument(6, array_merge($definition->getArgument(6), $paths)); + } + if ($container->hasDefinition($this->updateCommandServiceId)) { + $definition = $container->getDefinition($this->updateCommandServiceId); + $definition->replaceArgument(7, array_merge($definition->getArgument(7), $paths)); + } + } + } finally { + $this->level = 0; + $this->paths = []; + $this->definitions = []; + } + } + + protected function processValue($value, $isRoot = false) + { + if ($value instanceof Reference) { + if ((string) $value === $this->translatorServiceId) { + for ($i = $this->level - 1; $i >= 0; --$i) { + $class = $this->definitions[$i]->getClass(); + + if (ServiceLocator::class === $class) { + if (!isset($this->controllers[$this->currentId])) { + continue; + } + foreach ($this->controllers[$this->currentId] as $class => $_) { + $this->paths[$class] = true; + } + } else { + $this->paths[$class] = true; + } + + break; + } + } + + return $value; + } + + if ($value instanceof Definition) { + $this->definitions[$this->level++] = $value; + $value = parent::processValue($value, $isRoot); + unset($this->definitions[--$this->level]); + + return $value; + } + + return parent::processValue($value, $isRoot); + } + + private function findControllerArguments(ContainerBuilder $container): array + { + if ($container->hasDefinition($this->resolverServiceId)) { + $argument = $container->getDefinition($this->resolverServiceId)->getArgument(0); + if ($argument instanceof Reference) { + $argument = $container->getDefinition($argument); + } + + return $argument->getArgument(0); + } + + if ($container->hasDefinition('debug.'.$this->resolverServiceId)) { + $argument = $container->getDefinition('debug.'.$this->resolverServiceId)->getArgument(0); + if ($argument instanceof Reference) { + $argument = $container->getDefinition($argument); + } + $argument = $argument->getArgument(0); + if ($argument instanceof Reference) { + $argument = $container->getDefinition($argument); + } + + return $argument->getArgument(0); + } + + return []; + } +} diff --git a/vendor/symfony/translation/Dumper/CsvFileDumper.php b/vendor/symfony/translation/Dumper/CsvFileDumper.php index 18caa365..8f7b032f 100644 --- a/vendor/symfony/translation/Dumper/CsvFileDumper.php +++ b/vendor/symfony/translation/Dumper/CsvFileDumper.php @@ -26,22 +26,12 @@ class CsvFileDumper extends FileDumper /** * {@inheritdoc} */ - public function format(MessageCatalogue $messages, $domain = 'messages') + public function formatCatalogue(MessageCatalogue $messages, $domain, array $options = []) { - @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.8 and will be removed in 3.0. Use the formatCatalogue() method instead.', E_USER_DEPRECATED); - - return $this->formatCatalogue($messages, $domain); - } - - /** - * {@inheritdoc} - */ - public function formatCatalogue(MessageCatalogue $messages, $domain, array $options = array()) - { - $handle = fopen('php://memory', 'rb+'); + $handle = fopen('php://memory', 'r+'); foreach ($messages->all($domain) as $source => $target) { - fputcsv($handle, array($source, $target), $this->delimiter, $this->enclosure); + fputcsv($handle, [$source, $target], $this->delimiter, $this->enclosure); } rewind($handle); diff --git a/vendor/symfony/translation/Dumper/DumperInterface.php b/vendor/symfony/translation/Dumper/DumperInterface.php index cebc65ed..445b7018 100644 --- a/vendor/symfony/translation/Dumper/DumperInterface.php +++ b/vendor/symfony/translation/Dumper/DumperInterface.php @@ -24,8 +24,7 @@ interface DumperInterface /** * Dumps the message catalogue. * - * @param MessageCatalogue $messages The message catalogue - * @param array $options Options that are used by the dumper + * @param array $options Options that are used by the dumper */ - public function dump(MessageCatalogue $messages, $options = array()); + public function dump(MessageCatalogue $messages, $options = []); } diff --git a/vendor/symfony/translation/Dumper/FileDumper.php b/vendor/symfony/translation/Dumper/FileDumper.php index ad37cbee..87d0c69d 100644 --- a/vendor/symfony/translation/Dumper/FileDumper.php +++ b/vendor/symfony/translation/Dumper/FileDumper.php @@ -11,11 +11,12 @@ namespace Symfony\Component\Translation\Dumper; +use Symfony\Component\Translation\Exception\InvalidArgumentException; +use Symfony\Component\Translation\Exception\RuntimeException; use Symfony\Component\Translation\MessageCatalogue; /** * FileDumper is an implementation of DumperInterface that dump a message catalogue to file(s). - * Performs backup of already existing files. * * Options: * - path (mandatory): the directory where the files should be saved @@ -31,13 +32,6 @@ abstract class FileDumper implements DumperInterface */ protected $relativePathTemplate = '%domain%.%locale%.%extension%'; - /** - * Make file backup before the dump. - * - * @var bool - */ - private $backup = true; - /** * Sets the template for the relative paths to files. * @@ -51,73 +45,69 @@ public function setRelativePathTemplate($relativePathTemplate) /** * Sets backup flag. * - * @param bool + * @param bool $backup + * + * @deprecated since Symfony 4.1 */ public function setBackup($backup) { - $this->backup = $backup; + @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.1.', __METHOD__), \E_USER_DEPRECATED); + + if (false !== $backup) { + throw new \LogicException('The backup feature is no longer supported.'); + } } /** * {@inheritdoc} */ - public function dump(MessageCatalogue $messages, $options = array()) + public function dump(MessageCatalogue $messages, $options = []) { - if (!array_key_exists('path', $options)) { - throw new \InvalidArgumentException('The file dumper needs a path option.'); + if (!\array_key_exists('path', $options)) { + throw new InvalidArgumentException('The file dumper needs a path option.'); } // save a file for each domain foreach ($messages->getDomains() as $domain) { - // backup $fullpath = $options['path'].'/'.$this->getRelativePath($domain, $messages->getLocale()); - if (file_exists($fullpath)) { - if ($this->backup) { - copy($fullpath, $fullpath.'~'); - } - } else { + if (!file_exists($fullpath)) { $directory = \dirname($fullpath); if (!file_exists($directory) && !@mkdir($directory, 0777, true)) { - throw new \RuntimeException(sprintf('Unable to create directory "%s".', $directory)); + throw new RuntimeException(sprintf('Unable to create directory "%s".', $directory)); } } - // save file - file_put_contents($fullpath, $this->formatCatalogue($messages, $domain, $options)); - } - } - /** - * Transforms a domain of a message catalogue to its string representation. - * - * Override this function in child class if $options is used for message formatting. - * - * @param MessageCatalogue $messages - * @param string $domain - * @param array $options - * - * @return string representation - */ - public function formatCatalogue(MessageCatalogue $messages, $domain, array $options = array()) - { - @trigger_error('The '.__METHOD__.' method will replace the format method in 3.0. You should overwrite it instead of overwriting format instead.', E_USER_DEPRECATED); + $intlDomain = $domain.MessageCatalogue::INTL_DOMAIN_SUFFIX; + $intlMessages = $messages->all($intlDomain); + + if ($intlMessages) { + $intlPath = $options['path'].'/'.$this->getRelativePath($intlDomain, $messages->getLocale()); + file_put_contents($intlPath, $this->formatCatalogue($messages, $intlDomain, $options)); + + $messages->replace([], $intlDomain); + + try { + if ($messages->all($domain)) { + file_put_contents($fullpath, $this->formatCatalogue($messages, $domain, $options)); + } + continue; + } finally { + $messages->replace($intlMessages, $intlDomain); + } + } - return $this->format($messages, $domain); + file_put_contents($fullpath, $this->formatCatalogue($messages, $domain, $options)); + } } /** * Transforms a domain of a message catalogue to its string representation. * - * @param MessageCatalogue $messages - * @param string $domain + * @param string $domain * * @return string representation - * - * @deprecated since version 2.8, to be removed in 3.0. Overwrite formatCatalogue() instead. */ - protected function format(MessageCatalogue $messages, $domain) - { - throw new \LogicException('The "FileDumper::format" method needs to be overwritten, you should implement either "format" or "formatCatalogue".'); - } + abstract public function formatCatalogue(MessageCatalogue $messages, $domain, array $options = []); /** * Gets the file extension of the dumper. @@ -128,18 +118,13 @@ abstract protected function getExtension(); /** * Gets the relative file path using the template. - * - * @param string $domain The domain - * @param string $locale The locale - * - * @return string The relative file path */ - private function getRelativePath($domain, $locale) + private function getRelativePath(string $domain, string $locale): string { - return strtr($this->relativePathTemplate, array( + return strtr($this->relativePathTemplate, [ '%domain%' => $domain, '%locale%' => $locale, '%extension%' => $this->getExtension(), - )); + ]); } } diff --git a/vendor/symfony/translation/Dumper/IcuResFileDumper.php b/vendor/symfony/translation/Dumper/IcuResFileDumper.php index a1f4b90a..44104eb6 100644 --- a/vendor/symfony/translation/Dumper/IcuResFileDumper.php +++ b/vendor/symfony/translation/Dumper/IcuResFileDumper.php @@ -28,17 +28,7 @@ class IcuResFileDumper extends FileDumper /** * {@inheritdoc} */ - public function format(MessageCatalogue $messages, $domain = 'messages') - { - @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.8 and will be removed in 3.0. Use the formatCatalogue() method instead.', E_USER_DEPRECATED); - - return $this->formatCatalogue($messages, $domain); - } - - /** - * {@inheritdoc} - */ - public function formatCatalogue(MessageCatalogue $messages, $domain, array $options = array()) + public function formatCatalogue(MessageCatalogue $messages, $domain, array $options = []) { $data = $indexes = $resources = ''; @@ -57,7 +47,7 @@ public function formatCatalogue(MessageCatalogue $messages, $domain, array $opti $data .= pack('V', \strlen($target)) .mb_convert_encoding($target."\0", 'UTF-16LE', 'UTF-8') .$this->writePadding($data) - ; + ; } $resOffset = $this->getPosition($data); @@ -66,7 +56,7 @@ public function formatCatalogue(MessageCatalogue $messages, $domain, array $opti .$indexes .$this->writePadding($data) .$resources - ; + ; $bundleTop = $this->getPosition($data); @@ -92,16 +82,14 @@ public function formatCatalogue(MessageCatalogue $messages, $domain, array $opti return $header.$root.$data; } - private function writePadding($data) + private function writePadding(string $data): ?string { $padding = \strlen($data) % 4; - if ($padding) { - return str_repeat("\xAA", 4 - $padding); - } + return $padding ? str_repeat("\xAA", 4 - $padding) : null; } - private function getPosition($data) + private function getPosition(string $data) { return (\strlen($data) + 28) / 4; } diff --git a/vendor/symfony/translation/Dumper/IniFileDumper.php b/vendor/symfony/translation/Dumper/IniFileDumper.php index 3e7a91c3..45ff9614 100644 --- a/vendor/symfony/translation/Dumper/IniFileDumper.php +++ b/vendor/symfony/translation/Dumper/IniFileDumper.php @@ -23,17 +23,7 @@ class IniFileDumper extends FileDumper /** * {@inheritdoc} */ - public function format(MessageCatalogue $messages, $domain = 'messages') - { - @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.8 and will be removed in 3.0. Use the formatCatalogue() method instead.', E_USER_DEPRECATED); - - return $this->formatCatalogue($messages, $domain); - } - - /** - * {@inheritdoc} - */ - public function formatCatalogue(MessageCatalogue $messages, $domain, array $options = array()) + public function formatCatalogue(MessageCatalogue $messages, $domain, array $options = []) { $output = ''; diff --git a/vendor/symfony/translation/Dumper/JsonFileDumper.php b/vendor/symfony/translation/Dumper/JsonFileDumper.php index db9a2f82..c6c3163b 100644 --- a/vendor/symfony/translation/Dumper/JsonFileDumper.php +++ b/vendor/symfony/translation/Dumper/JsonFileDumper.php @@ -23,23 +23,9 @@ class JsonFileDumper extends FileDumper /** * {@inheritdoc} */ - public function format(MessageCatalogue $messages, $domain = 'messages') + public function formatCatalogue(MessageCatalogue $messages, $domain, array $options = []) { - @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.8 and will be removed in 3.0. Use the formatCatalogue() method instead.', E_USER_DEPRECATED); - - return $this->formatCatalogue($messages, $domain); - } - - /** - * {@inheritdoc} - */ - public function formatCatalogue(MessageCatalogue $messages, $domain, array $options = array()) - { - if (isset($options['json_encoding'])) { - $flags = $options['json_encoding']; - } else { - $flags = \defined('JSON_PRETTY_PRINT') ? JSON_PRETTY_PRINT : 0; - } + $flags = $options['json_encoding'] ?? \JSON_PRETTY_PRINT; return json_encode($messages->all($domain), $flags); } diff --git a/vendor/symfony/translation/Dumper/MoFileDumper.php b/vendor/symfony/translation/Dumper/MoFileDumper.php index 39dc2bad..9bed418f 100644 --- a/vendor/symfony/translation/Dumper/MoFileDumper.php +++ b/vendor/symfony/translation/Dumper/MoFileDumper.php @@ -24,30 +24,20 @@ class MoFileDumper extends FileDumper /** * {@inheritdoc} */ - public function format(MessageCatalogue $messages, $domain = 'messages') - { - @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.8 and will be removed in 3.0. Use the formatCatalogue() method instead.', E_USER_DEPRECATED); - - return $this->formatCatalogue($messages, $domain); - } - - /** - * {@inheritdoc} - */ - public function formatCatalogue(MessageCatalogue $messages, $domain, array $options = array()) + public function formatCatalogue(MessageCatalogue $messages, $domain, array $options = []) { $sources = $targets = $sourceOffsets = $targetOffsets = ''; - $offsets = array(); + $offsets = []; $size = 0; foreach ($messages->all($domain) as $source => $target) { - $offsets[] = array_map('strlen', array($sources, $source, $targets, $target)); + $offsets[] = array_map('strlen', [$sources, $source, $targets, $target]); $sources .= "\0".$source; $targets .= "\0".$target; ++$size; } - $header = array( + $header = [ 'magicNumber' => MoFileLoader::MO_LITTLE_ENDIAN_MAGIC, 'formatRevision' => 0, 'count' => $size, @@ -55,7 +45,7 @@ public function formatCatalogue(MessageCatalogue $messages, $domain, array $opti 'offsetTranslated' => MoFileLoader::MO_HEADER_SIZE + (8 * $size), 'sizeHashes' => 0, 'offsetHashes' => MoFileLoader::MO_HEADER_SIZE + (16 * $size), - ); + ]; $sourcesSize = \strlen($sources); $sourcesStart = $header['offsetHashes'] + 1; @@ -67,12 +57,12 @@ public function formatCatalogue(MessageCatalogue $messages, $domain, array $opti .$this->writeLong($offset[2] + $sourcesStart + $sourcesSize); } - $output = implode(array_map(array($this, 'writeLong'), $header)) + $output = implode('', array_map([$this, 'writeLong'], $header)) .$sourceOffsets .$targetOffsets .$sources .$targets - ; + ; return $output; } @@ -85,7 +75,7 @@ protected function getExtension() return 'mo'; } - private function writeLong($str) + private function writeLong($str): string { return pack('V*', $str); } diff --git a/vendor/symfony/translation/Dumper/PhpFileDumper.php b/vendor/symfony/translation/Dumper/PhpFileDumper.php index 1d2aaac7..e77afc2f 100644 --- a/vendor/symfony/translation/Dumper/PhpFileDumper.php +++ b/vendor/symfony/translation/Dumper/PhpFileDumper.php @@ -23,17 +23,7 @@ class PhpFileDumper extends FileDumper /** * {@inheritdoc} */ - protected function format(MessageCatalogue $messages, $domain) - { - @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.8 and will be removed in 3.0. Use the formatCatalogue() method instead.', E_USER_DEPRECATED); - - return $this->formatCatalogue($messages, $domain); - } - - /** - * {@inheritdoc} - */ - public function formatCatalogue(MessageCatalogue $messages, $domain, array $options = array()) + public function formatCatalogue(MessageCatalogue $messages, $domain, array $options = []) { return "all($domain), true).";\n"; } diff --git a/vendor/symfony/translation/Dumper/PoFileDumper.php b/vendor/symfony/translation/Dumper/PoFileDumper.php index b83ab3f6..1a723f2d 100644 --- a/vendor/symfony/translation/Dumper/PoFileDumper.php +++ b/vendor/symfony/translation/Dumper/PoFileDumper.php @@ -23,17 +23,7 @@ class PoFileDumper extends FileDumper /** * {@inheritdoc} */ - public function format(MessageCatalogue $messages, $domain = 'messages') - { - @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.8 and will be removed in 3.0. Use the formatCatalogue() method instead.', E_USER_DEPRECATED); - - return $this->formatCatalogue($messages, $domain); - } - - /** - * {@inheritdoc} - */ - public function formatCatalogue(MessageCatalogue $messages, $domain, array $options = array()) + public function formatCatalogue(MessageCatalogue $messages, $domain, array $options = []) { $output = 'msgid ""'."\n"; $output .= 'msgstr ""'."\n"; @@ -49,13 +39,78 @@ public function formatCatalogue(MessageCatalogue $messages, $domain, array $opti } else { $newLine = true; } - $output .= sprintf('msgid "%s"'."\n", $this->escape($source)); - $output .= sprintf('msgstr "%s"', $this->escape($target)); + $metadata = $messages->getMetadata($source, $domain); + + if (isset($metadata['comments'])) { + $output .= $this->formatComments($metadata['comments']); + } + if (isset($metadata['flags'])) { + $output .= $this->formatComments(implode(',', (array) $metadata['flags']), ','); + } + if (isset($metadata['sources'])) { + $output .= $this->formatComments(implode(' ', (array) $metadata['sources']), ':'); + } + + $sourceRules = $this->getStandardRules($source); + $targetRules = $this->getStandardRules($target); + if (2 == \count($sourceRules) && [] !== $targetRules) { + $output .= sprintf('msgid "%s"'."\n", $this->escape($sourceRules[0])); + $output .= sprintf('msgid_plural "%s"'."\n", $this->escape($sourceRules[1])); + foreach ($targetRules as $i => $targetRule) { + $output .= sprintf('msgstr[%d] "%s"'."\n", $i, $this->escape($targetRule)); + } + } else { + $output .= sprintf('msgid "%s"'."\n", $this->escape($source)); + $output .= sprintf('msgstr "%s"'."\n", $this->escape($target)); + } } return $output; } + private function getStandardRules(string $id) + { + // Partly copied from TranslatorTrait::trans. + $parts = []; + if (preg_match('/^\|++$/', $id)) { + $parts = explode('|', $id); + } elseif (preg_match_all('/(?:\|\||[^\|])++/', $id, $matches)) { + $parts = $matches[0]; + } + + $intervalRegexp = <<<'EOF' +/^(?P + ({\s* + (\-?\d+(\.\d+)?[\s*,\s*\-?\d+(\.\d+)?]*) + \s*}) + + | + + (?P[\[\]]) + \s* + (?P-Inf|\-?\d+(\.\d+)?) + \s*,\s* + (?P\+?Inf|\-?\d+(\.\d+)?) + \s* + (?P[\[\]]) +)\s*(?P.*?)$/xs +EOF; + + $standardRules = []; + foreach ($parts as $part) { + $part = trim(str_replace('||', '|', $part)); + + if (preg_match($intervalRegexp, $part)) { + // Explicit rule is not a standard rule. + return []; + } else { + $standardRules[] = $part; + } + } + + return $standardRules; + } + /** * {@inheritdoc} */ @@ -64,8 +119,19 @@ protected function getExtension() return 'po'; } - private function escape($str) + private function escape(string $str): string { return addcslashes($str, "\0..\37\42\134"); } + + private function formatComments($comments, string $prefix = ''): ?string + { + $output = null; + + foreach ((array) $comments as $comment) { + $output .= sprintf('#%s %s'."\n", $prefix, $comment); + } + + return $output; + } } diff --git a/vendor/symfony/translation/Dumper/QtFileDumper.php b/vendor/symfony/translation/Dumper/QtFileDumper.php index d8a794ee..79a64b24 100644 --- a/vendor/symfony/translation/Dumper/QtFileDumper.php +++ b/vendor/symfony/translation/Dumper/QtFileDumper.php @@ -23,17 +23,7 @@ class QtFileDumper extends FileDumper /** * {@inheritdoc} */ - public function format(MessageCatalogue $messages, $domain) - { - @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.8 and will be removed in 3.0. Use the formatCatalogue() method instead.', E_USER_DEPRECATED); - - return $this->formatCatalogue($messages, $domain); - } - - /** - * {@inheritdoc} - */ - public function formatCatalogue(MessageCatalogue $messages, $domain, array $options = array()) + public function formatCatalogue(MessageCatalogue $messages, $domain, array $options = []) { $dom = new \DOMDocument('1.0', 'utf-8'); $dom->formatOutput = true; @@ -43,6 +33,17 @@ public function formatCatalogue(MessageCatalogue $messages, $domain, array $opti foreach ($messages->all($domain) as $source => $target) { $message = $context->appendChild($dom->createElement('message')); + $metadata = $messages->getMetadata($source, $domain); + if (isset($metadata['sources'])) { + foreach ((array) $metadata['sources'] as $location) { + $loc = explode(':', $location, 2); + $location = $message->appendChild($dom->createElement('location')); + $location->setAttribute('filename', $loc[0]); + if (isset($loc[1])) { + $location->setAttribute('line', $loc[1]); + } + } + } $message->appendChild($dom->createElement('source', $source)); $message->appendChild($dom->createElement('translation', $target)); } diff --git a/vendor/symfony/translation/Dumper/XliffFileDumper.php b/vendor/symfony/translation/Dumper/XliffFileDumper.php index e0cbe5d8..37e162aa 100644 --- a/vendor/symfony/translation/Dumper/XliffFileDumper.php +++ b/vendor/symfony/translation/Dumper/XliffFileDumper.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Translation\Dumper; +use Symfony\Component\Translation\Exception\InvalidArgumentException; use Symfony\Component\Translation\MessageCatalogue; /** @@ -23,14 +24,14 @@ class XliffFileDumper extends FileDumper /** * {@inheritdoc} */ - public function formatCatalogue(MessageCatalogue $messages, $domain, array $options = array()) + public function formatCatalogue(MessageCatalogue $messages, $domain, array $options = []) { $xliffVersion = '1.2'; - if (array_key_exists('xliff_version', $options)) { + if (\array_key_exists('xliff_version', $options)) { $xliffVersion = $options['xliff_version']; } - if (array_key_exists('default_locale', $options)) { + if (\array_key_exists('default_locale', $options)) { $defaultLocale = $options['default_locale']; } else { $defaultLocale = \Locale::getDefault(); @@ -40,20 +41,10 @@ public function formatCatalogue(MessageCatalogue $messages, $domain, array $opti return $this->dumpXliff1($defaultLocale, $messages, $domain, $options); } if ('2.0' === $xliffVersion) { - return $this->dumpXliff2($defaultLocale, $messages, $domain, $options); + return $this->dumpXliff2($defaultLocale, $messages, $domain); } - throw new \InvalidArgumentException(sprintf('No support implemented for dumping XLIFF version "%s".', $xliffVersion)); - } - - /** - * {@inheritdoc} - */ - protected function format(MessageCatalogue $messages, $domain) - { - @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.8 and will be removed in 3.0. Use the formatCatalogue() method instead.', E_USER_DEPRECATED); - - return $this->formatCatalogue($messages, $domain); + throw new InvalidArgumentException(sprintf('No support implemented for dumping XLIFF version "%s".', $xliffVersion)); } /** @@ -64,10 +55,10 @@ protected function getExtension() return 'xlf'; } - private function dumpXliff1($defaultLocale, MessageCatalogue $messages, $domain, array $options = array()) + private function dumpXliff1(string $defaultLocale, MessageCatalogue $messages, ?string $domain, array $options = []) { - $toolInfo = array('tool-id' => 'symfony', 'tool-name' => 'Symfony'); - if (array_key_exists('tool_info', $options)) { + $toolInfo = ['tool-id' => 'symfony', 'tool-name' => 'Symfony']; + if (\array_key_exists('tool_info', $options)) { $toolInfo = array_merge($toolInfo, $options['tool_info']); } @@ -94,7 +85,7 @@ private function dumpXliff1($defaultLocale, MessageCatalogue $messages, $domain, foreach ($messages->all($domain) as $source => $target) { $translation = $dom->createElement('trans-unit'); - $translation->setAttribute('id', md5($source)); + $translation->setAttribute('id', strtr(substr(base64_encode(hash('sha256', $source, true)), 0, 7), '/+', '._')); $translation->setAttribute('resname', $source); $s = $translation->appendChild($dom->createElement('source')); @@ -138,7 +129,7 @@ private function dumpXliff1($defaultLocale, MessageCatalogue $messages, $domain, return $dom->saveXML(); } - private function dumpXliff2($defaultLocale, MessageCatalogue $messages, $domain, array $options = array()) + private function dumpXliff2(string $defaultLocale, MessageCatalogue $messages, ?string $domain) { $dom = new \DOMDocument('1.0', 'utf-8'); $dom->formatOutput = true; @@ -150,11 +141,37 @@ private function dumpXliff2($defaultLocale, MessageCatalogue $messages, $domain, $xliff->setAttribute('trgLang', str_replace('_', '-', $messages->getLocale())); $xliffFile = $xliff->appendChild($dom->createElement('file')); - $xliffFile->setAttribute('id', $domain.'.'.$messages->getLocale()); + if (str_ends_with($domain, MessageCatalogue::INTL_DOMAIN_SUFFIX)) { + $xliffFile->setAttribute('id', substr($domain, 0, -\strlen(MessageCatalogue::INTL_DOMAIN_SUFFIX)).'.'.$messages->getLocale()); + } else { + $xliffFile->setAttribute('id', $domain.'.'.$messages->getLocale()); + } foreach ($messages->all($domain) as $source => $target) { $translation = $dom->createElement('unit'); - $translation->setAttribute('id', md5($source)); + $translation->setAttribute('id', strtr(substr(base64_encode(hash('sha256', $source, true)), 0, 7), '/+', '._')); + + if (\strlen($source) <= 80) { + $translation->setAttribute('name', $source); + } + + $metadata = $messages->getMetadata($source, $domain); + + // Add notes section + if ($this->hasMetadataArrayInfo('notes', $metadata)) { + $notesElement = $dom->createElement('notes'); + foreach ($metadata['notes'] as $note) { + $n = $dom->createElement('note'); + $n->appendChild($dom->createTextNode($note['content'] ?? '')); + unset($note['content']); + + foreach ($note as $name => $value) { + $n->setAttribute($name, $value); + } + $notesElement->appendChild($n); + } + $translation->appendChild($notesElement); + } $segment = $translation->appendChild($dom->createElement('segment')); @@ -165,7 +182,6 @@ private function dumpXliff2($defaultLocale, MessageCatalogue $messages, $domain, $text = 1 === preg_match('/[&<>]/', $target) ? $dom->createCDATASection($target) : $dom->createTextNode($target); $targetElement = $dom->createElement('target'); - $metadata = $messages->getMetadata($source, $domain); if ($this->hasMetadataArrayInfo('target-attributes', $metadata)) { foreach ($metadata['target-attributes'] as $name => $value) { $targetElement->setAttribute($name, $value); @@ -180,14 +196,8 @@ private function dumpXliff2($defaultLocale, MessageCatalogue $messages, $domain, return $dom->saveXML(); } - /** - * @param string $key - * @param array|null $metadata - * - * @return bool - */ - private function hasMetadataArrayInfo($key, $metadata = null) + private function hasMetadataArrayInfo(string $key, array $metadata = null): bool { - return null !== $metadata && array_key_exists($key, $metadata) && ($metadata[$key] instanceof \Traversable || \is_array($metadata[$key])); + return is_iterable($metadata[$key] ?? null); } } diff --git a/vendor/symfony/translation/Dumper/YamlFileDumper.php b/vendor/symfony/translation/Dumper/YamlFileDumper.php index b2164214..520ae334 100644 --- a/vendor/symfony/translation/Dumper/YamlFileDumper.php +++ b/vendor/symfony/translation/Dumper/YamlFileDumper.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Translation\Dumper; +use Symfony\Component\Translation\Exception\LogicException; use Symfony\Component\Translation\MessageCatalogue; use Symfony\Component\Translation\Util\ArrayConverter; use Symfony\Component\Yaml\Yaml; @@ -22,13 +23,20 @@ */ class YamlFileDumper extends FileDumper { + private $extension; + + public function __construct(string $extension = 'yml') + { + $this->extension = $extension; + } + /** * {@inheritdoc} */ - public function formatCatalogue(MessageCatalogue $messages, $domain, array $options = array()) + public function formatCatalogue(MessageCatalogue $messages, $domain, array $options = []) { - if (!class_exists('Symfony\Component\Yaml\Yaml')) { - throw new \LogicException('Dumping translations in the YAML format requires the Symfony Yaml component.'); + if (!class_exists(Yaml::class)) { + throw new LogicException('Dumping translations in the YAML format requires the Symfony Yaml component.'); } $data = $messages->all($domain); @@ -44,21 +52,11 @@ public function formatCatalogue(MessageCatalogue $messages, $domain, array $opti return Yaml::dump($data); } - /** - * {@inheritdoc} - */ - protected function format(MessageCatalogue $messages, $domain) - { - @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.8 and will be removed in 3.0. Use the formatCatalogue() method instead.', E_USER_DEPRECATED); - - return $this->formatCatalogue($messages, $domain); - } - /** * {@inheritdoc} */ protected function getExtension() { - return 'yml'; + return $this->extension; } } diff --git a/vendor/symfony/translation/Exception/ExceptionInterface.php b/vendor/symfony/translation/Exception/ExceptionInterface.php index c85fb93c..8f9c54ef 100644 --- a/vendor/symfony/translation/Exception/ExceptionInterface.php +++ b/vendor/symfony/translation/Exception/ExceptionInterface.php @@ -16,6 +16,6 @@ * * @author Fabien Potencier */ -interface ExceptionInterface +interface ExceptionInterface extends \Throwable { } diff --git a/vendor/symfony/translation/Exception/InvalidArgumentException.php b/vendor/symfony/translation/Exception/InvalidArgumentException.php new file mode 100644 index 00000000..90d06690 --- /dev/null +++ b/vendor/symfony/translation/Exception/InvalidArgumentException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Exception; + +/** + * Base InvalidArgumentException for the Translation component. + * + * @author Abdellatif Ait boudad + */ +class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/translation/Exception/LogicException.php b/vendor/symfony/translation/Exception/LogicException.php new file mode 100644 index 00000000..9019c7e7 --- /dev/null +++ b/vendor/symfony/translation/Exception/LogicException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Exception; + +/** + * Base LogicException for Translation component. + * + * @author Abdellatif Ait boudad + */ +class LogicException extends \LogicException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/translation/Exception/RuntimeException.php b/vendor/symfony/translation/Exception/RuntimeException.php new file mode 100644 index 00000000..dcd79408 --- /dev/null +++ b/vendor/symfony/translation/Exception/RuntimeException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Exception; + +/** + * Base RuntimeException for the Translation component. + * + * @author Abdellatif Ait boudad + */ +class RuntimeException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/translation/Extractor/AbstractFileExtractor.php b/vendor/symfony/translation/Extractor/AbstractFileExtractor.php index 0ae16844..618df732 100644 --- a/vendor/symfony/translation/Extractor/AbstractFileExtractor.php +++ b/vendor/symfony/translation/Extractor/AbstractFileExtractor.php @@ -11,6 +11,8 @@ namespace Symfony\Component\Translation\Extractor; +use Symfony\Component\Translation\Exception\InvalidArgumentException; + /** * Base class used by classes that extract translation messages from files. * @@ -19,21 +21,21 @@ abstract class AbstractFileExtractor { /** - * @param string|array $resource Files, a file or a directory + * @param string|iterable $resource Files, a file or a directory * - * @return array + * @return iterable */ protected function extractFiles($resource) { - if (\is_array($resource) || $resource instanceof \Traversable) { - $files = array(); + if (is_iterable($resource)) { + $files = []; foreach ($resource as $file) { if ($this->canBeExtracted($file)) { $files[] = $this->toSplFileInfo($file); } } } elseif (is_file($resource)) { - $files = $this->canBeExtracted($resource) ? array($this->toSplFileInfo($resource)) : array(); + $files = $this->canBeExtracted($resource) ? [$this->toSplFileInfo($resource)] : []; } else { $files = $this->extractFromDirectory($resource); } @@ -41,14 +43,9 @@ protected function extractFiles($resource) return $files; } - /** - * @param string $file - * - * @return \SplFileInfo - */ - private function toSplFileInfo($file) + private function toSplFileInfo(string $file): \SplFileInfo { - return ($file instanceof \SplFileInfo) ? $file : new \SplFileInfo($file); + return new \SplFileInfo($file); } /** @@ -56,12 +53,12 @@ private function toSplFileInfo($file) * * @return bool * - * @throws \InvalidArgumentException + * @throws InvalidArgumentException */ protected function isFile($file) { if (!is_file($file)) { - throw new \InvalidArgumentException(sprintf('The "%s" file does not exist.', $file)); + throw new InvalidArgumentException(sprintf('The "%s" file does not exist.', $file)); } return true; @@ -77,7 +74,7 @@ abstract protected function canBeExtracted($file); /** * @param string|array $resource Files, a file or a directory * - * @return array files to be extracted + * @return iterable files to be extracted */ abstract protected function extractFromDirectory($resource); } diff --git a/vendor/symfony/translation/Extractor/ChainExtractor.php b/vendor/symfony/translation/Extractor/ChainExtractor.php index 50e3c845..2683f5d2 100644 --- a/vendor/symfony/translation/Extractor/ChainExtractor.php +++ b/vendor/symfony/translation/Extractor/ChainExtractor.php @@ -25,13 +25,12 @@ class ChainExtractor implements ExtractorInterface * * @var ExtractorInterface[] */ - private $extractors = array(); + private $extractors = []; /** * Adds a loader to the translation extractor. * - * @param string $format The format of the loader - * @param ExtractorInterface $extractor The loader + * @param string $format The format of the loader */ public function addExtractor($format, ExtractorInterface $extractor) { diff --git a/vendor/symfony/translation/Extractor/ExtractorInterface.php b/vendor/symfony/translation/Extractor/ExtractorInterface.php index b4534fae..34e6b2d6 100644 --- a/vendor/symfony/translation/Extractor/ExtractorInterface.php +++ b/vendor/symfony/translation/Extractor/ExtractorInterface.php @@ -24,8 +24,7 @@ interface ExtractorInterface /** * Extracts translation messages from files, a file or a directory to the catalogue. * - * @param string|array $resource Files, a file or a directory - * @param MessageCatalogue $catalogue The catalogue + * @param string|iterable $resource Files, a file or a directory */ public function extract($resource, MessageCatalogue $catalogue); diff --git a/vendor/symfony/translation/Extractor/PhpExtractor.php b/vendor/symfony/translation/Extractor/PhpExtractor.php new file mode 100644 index 00000000..e0622e6a --- /dev/null +++ b/vendor/symfony/translation/Extractor/PhpExtractor.php @@ -0,0 +1,288 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Extractor; + +use Symfony\Component\Finder\Finder; +use Symfony\Component\Translation\MessageCatalogue; + +/** + * PhpExtractor extracts translation messages from a PHP template. + * + * @author Michel Salib + */ +class PhpExtractor extends AbstractFileExtractor implements ExtractorInterface +{ + public const MESSAGE_TOKEN = 300; + public const METHOD_ARGUMENTS_TOKEN = 1000; + public const DOMAIN_TOKEN = 1001; + + /** + * Prefix for new found message. + * + * @var string + */ + private $prefix = ''; + + /** + * The sequence that captures translation messages. + * + * @var array + */ + protected $sequences = [ + [ + '->', + 'trans', + '(', + self::MESSAGE_TOKEN, + ',', + self::METHOD_ARGUMENTS_TOKEN, + ',', + self::DOMAIN_TOKEN, + ], + [ + '->', + 'transChoice', + '(', + self::MESSAGE_TOKEN, + ',', + self::METHOD_ARGUMENTS_TOKEN, + ',', + self::METHOD_ARGUMENTS_TOKEN, + ',', + self::DOMAIN_TOKEN, + ], + [ + '->', + 'trans', + '(', + self::MESSAGE_TOKEN, + ], + [ + '->', + 'transChoice', + '(', + self::MESSAGE_TOKEN, + ], + ]; + + /** + * {@inheritdoc} + */ + public function extract($resource, MessageCatalogue $catalog) + { + $files = $this->extractFiles($resource); + foreach ($files as $file) { + $this->parseTokens(token_get_all(file_get_contents($file)), $catalog, $file); + + gc_mem_caches(); + } + } + + /** + * {@inheritdoc} + */ + public function setPrefix($prefix) + { + $this->prefix = $prefix; + } + + /** + * Normalizes a token. + * + * @param mixed $token + * + * @return string|null + */ + protected function normalizeToken($token) + { + if (isset($token[1]) && 'b"' !== $token) { + return $token[1]; + } + + return $token; + } + + /** + * Seeks to a non-whitespace token. + */ + private function seekToNextRelevantToken(\Iterator $tokenIterator) + { + for (; $tokenIterator->valid(); $tokenIterator->next()) { + $t = $tokenIterator->current(); + if (\T_WHITESPACE !== $t[0]) { + break; + } + } + } + + private function skipMethodArgument(\Iterator $tokenIterator) + { + $openBraces = 0; + + for (; $tokenIterator->valid(); $tokenIterator->next()) { + $t = $tokenIterator->current(); + + if ('[' === $t[0] || '(' === $t[0]) { + ++$openBraces; + } + + if (']' === $t[0] || ')' === $t[0]) { + --$openBraces; + } + + if ((0 === $openBraces && ',' === $t[0]) || (-1 === $openBraces && ')' === $t[0])) { + break; + } + } + } + + /** + * Extracts the message from the iterator while the tokens + * match allowed message tokens. + */ + private function getValue(\Iterator $tokenIterator) + { + $message = ''; + $docToken = ''; + $docPart = ''; + + for (; $tokenIterator->valid(); $tokenIterator->next()) { + $t = $tokenIterator->current(); + if ('.' === $t) { + // Concatenate with next token + continue; + } + if (!isset($t[1])) { + break; + } + + switch ($t[0]) { + case \T_START_HEREDOC: + $docToken = $t[1]; + break; + case \T_ENCAPSED_AND_WHITESPACE: + case \T_CONSTANT_ENCAPSED_STRING: + if ('' === $docToken) { + $message .= PhpStringTokenParser::parse($t[1]); + } else { + $docPart = $t[1]; + } + break; + case \T_END_HEREDOC: + if ($indentation = strspn($t[1], ' ')) { + $docPartWithLineBreaks = $docPart; + $docPart = ''; + + foreach (preg_split('~(\r\n|\n|\r)~', $docPartWithLineBreaks, -1, \PREG_SPLIT_DELIM_CAPTURE) as $str) { + if (\in_array($str, ["\r\n", "\n", "\r"], true)) { + $docPart .= $str; + } else { + $docPart .= substr($str, $indentation); + } + } + } + + $message .= PhpStringTokenParser::parseDocString($docToken, $docPart); + $docToken = ''; + $docPart = ''; + break; + case \T_WHITESPACE: + break; + default: + break 2; + } + } + + return $message; + } + + /** + * Extracts trans message from PHP tokens. + * + * @param array $tokens + * @param string $filename + */ + protected function parseTokens($tokens, MessageCatalogue $catalog/* , string $filename */) + { + if (\func_num_args() < 3 && __CLASS__ !== static::class && __CLASS__ !== (new \ReflectionMethod($this, __FUNCTION__))->getDeclaringClass()->getName() && !$this instanceof \PHPUnit\Framework\MockObject\MockObject && !$this instanceof \Prophecy\Prophecy\ProphecySubjectInterface && !$this instanceof \Mockery\MockInterface) { + @trigger_error(sprintf('The "%s()" method will have a new "string $filename" argument in version 5.0, not defining it is deprecated since Symfony 4.3.', __METHOD__), \E_USER_DEPRECATED); + } + $filename = 2 < \func_num_args() ? func_get_arg(2) : ''; + + $tokenIterator = new \ArrayIterator($tokens); + + for ($key = 0; $key < $tokenIterator->count(); ++$key) { + foreach ($this->sequences as $sequence) { + $message = ''; + $domain = 'messages'; + $tokenIterator->seek($key); + + foreach ($sequence as $sequenceKey => $item) { + $this->seekToNextRelevantToken($tokenIterator); + + if ($this->normalizeToken($tokenIterator->current()) === $item) { + $tokenIterator->next(); + continue; + } elseif (self::MESSAGE_TOKEN === $item) { + $message = $this->getValue($tokenIterator); + + if (\count($sequence) === ($sequenceKey + 1)) { + break; + } + } elseif (self::METHOD_ARGUMENTS_TOKEN === $item) { + $this->skipMethodArgument($tokenIterator); + } elseif (self::DOMAIN_TOKEN === $item) { + $domainToken = $this->getValue($tokenIterator); + if ('' !== $domainToken) { + $domain = $domainToken; + } + + break; + } else { + break; + } + } + + if ($message) { + $catalog->set($message, $this->prefix.$message, $domain); + $metadata = $catalog->getMetadata($message, $domain) ?? []; + $normalizedFilename = preg_replace('{[\\\\/]+}', '/', $filename); + $metadata['sources'][] = $normalizedFilename.':'.$tokens[$key][2]; + $catalog->setMetadata($message, $metadata, $domain); + break; + } + } + } + } + + /** + * @param string $file + * + * @return bool + * + * @throws \InvalidArgumentException + */ + protected function canBeExtracted($file) + { + return $this->isFile($file) && 'php' === pathinfo($file, \PATHINFO_EXTENSION); + } + + /** + * {@inheritdoc} + */ + protected function extractFromDirectory($directory) + { + $finder = new Finder(); + + return $finder->files()->name('*.php')->in($directory); + } +} diff --git a/vendor/symfony/translation/Extractor/PhpStringTokenParser.php b/vendor/symfony/translation/Extractor/PhpStringTokenParser.php new file mode 100644 index 00000000..f468fe5e --- /dev/null +++ b/vendor/symfony/translation/Extractor/PhpStringTokenParser.php @@ -0,0 +1,142 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Extractor; + +/* + * The following is derived from code at http://github.com/nikic/PHP-Parser + * + * Copyright (c) 2011 by Nikita Popov + * + * Some rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * * The names of the contributors may not be used to endorse or + * promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +class PhpStringTokenParser +{ + protected static $replacements = [ + '\\' => '\\', + '$' => '$', + 'n' => "\n", + 'r' => "\r", + 't' => "\t", + 'f' => "\f", + 'v' => "\v", + 'e' => "\x1B", + ]; + + /** + * Parses a string token. + * + * @param string $str String token content + * + * @return string The parsed string + */ + public static function parse($str) + { + $bLength = 0; + if ('b' === $str[0]) { + $bLength = 1; + } + + if ('\'' === $str[$bLength]) { + return str_replace( + ['\\\\', '\\\''], + ['\\', '\''], + substr($str, $bLength + 1, -1) + ); + } else { + return self::parseEscapeSequences(substr($str, $bLength + 1, -1), '"'); + } + } + + /** + * Parses escape sequences in strings (all string types apart from single quoted). + * + * @param string $str String without quotes + * @param string|null $quote Quote type + * + * @return string String with escape sequences parsed + */ + public static function parseEscapeSequences($str, $quote) + { + if (null !== $quote) { + $str = str_replace('\\'.$quote, $quote, $str); + } + + return preg_replace_callback( + '~\\\\([\\\\$nrtfve]|[xX][0-9a-fA-F]{1,2}|[0-7]{1,3})~', + [__CLASS__, 'parseCallback'], + $str + ); + } + + private static function parseCallback(array $matches): string + { + $str = $matches[1]; + + if (isset(self::$replacements[$str])) { + return self::$replacements[$str]; + } elseif ('x' === $str[0] || 'X' === $str[0]) { + return \chr(hexdec($str)); + } else { + return \chr(octdec($str)); + } + } + + /** + * Parses a constant doc string. + * + * @param string $startToken Doc string start token content (<< + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Formatter; + +/** + * @author Abdellatif Ait boudad + * + * @deprecated since Symfony 4.2, use MessageFormatterInterface::format() with a %count% parameter instead + */ +interface ChoiceMessageFormatterInterface +{ + /** + * Formats a localized message pattern with given arguments. + * + * @param string $message The message (may also be an object that can be cast to string) + * @param int $number The number to use to find the indice of the message + * @param string $locale The message locale + * @param array $parameters An array of parameters for the message + * + * @return string + */ + public function choiceFormat($message, $number, $locale, array $parameters = []); +} diff --git a/vendor/symfony/translation/Formatter/IntlFormatter.php b/vendor/symfony/translation/Formatter/IntlFormatter.php new file mode 100644 index 00000000..f7f1c36a --- /dev/null +++ b/vendor/symfony/translation/Formatter/IntlFormatter.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Formatter; + +use Symfony\Component\Translation\Exception\InvalidArgumentException; +use Symfony\Component\Translation\Exception\LogicException; + +/** + * @author Guilherme Blanco + * @author Abdellatif Ait boudad + */ +class IntlFormatter implements IntlFormatterInterface +{ + private $hasMessageFormatter; + private $cache = []; + + /** + * {@inheritdoc} + */ + public function formatIntl(string $message, string $locale, array $parameters = []): string + { + // MessageFormatter constructor throws an exception if the message is empty + if ('' === $message) { + return ''; + } + + if (!$formatter = $this->cache[$locale][$message] ?? null) { + if (!($this->hasMessageFormatter ?? $this->hasMessageFormatter = class_exists(\MessageFormatter::class))) { + throw new LogicException('Cannot parse message translation: please install the "intl" PHP extension or the "symfony/polyfill-intl-messageformatter" package.'); + } + try { + $this->cache[$locale][$message] = $formatter = new \MessageFormatter($locale, $message); + } catch (\IntlException $e) { + throw new InvalidArgumentException(sprintf('Invalid message format (error #%d): ', intl_get_error_code()).intl_get_error_message(), 0, $e); + } + } + + foreach ($parameters as $key => $value) { + if (\in_array($key[0] ?? null, ['%', '{'], true)) { + unset($parameters[$key]); + $parameters[trim($key, '%{ }')] = $value; + } + } + + if (false === $message = $formatter->format($parameters)) { + throw new InvalidArgumentException(sprintf('Unable to format message (error #%s): ', $formatter->getErrorCode()).$formatter->getErrorMessage()); + } + + return $message; + } +} diff --git a/vendor/symfony/translation/Formatter/IntlFormatterInterface.php b/vendor/symfony/translation/Formatter/IntlFormatterInterface.php new file mode 100644 index 00000000..02fc6acb --- /dev/null +++ b/vendor/symfony/translation/Formatter/IntlFormatterInterface.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Formatter; + +/** + * Formats ICU message patterns. + * + * @author Nicolas Grekas + */ +interface IntlFormatterInterface +{ + /** + * Formats a localized message using rules defined by ICU MessageFormat. + * + * @see http://icu-project.org/apiref/icu4c/classMessageFormat.html#details + */ + public function formatIntl(string $message, string $locale, array $parameters = []): string; +} diff --git a/vendor/symfony/translation/Formatter/MessageFormatter.php b/vendor/symfony/translation/Formatter/MessageFormatter.php new file mode 100644 index 00000000..785d6f22 --- /dev/null +++ b/vendor/symfony/translation/Formatter/MessageFormatter.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Formatter; + +use Symfony\Component\Translation\IdentityTranslator; +use Symfony\Component\Translation\MessageSelector; +use Symfony\Component\Translation\TranslatorInterface as LegacyTranslatorInterface; +use Symfony\Contracts\Translation\TranslatorInterface; + +// Help opcache.preload discover always-needed symbols +class_exists(IntlFormatter::class); + +/** + * @author Abdellatif Ait boudad + */ +class MessageFormatter implements MessageFormatterInterface, IntlFormatterInterface, ChoiceMessageFormatterInterface +{ + private $translator; + private $intlFormatter; + + /** + * @param TranslatorInterface|null $translator An identity translator to use as selector for pluralization + */ + public function __construct($translator = null, IntlFormatterInterface $intlFormatter = null) + { + if ($translator instanceof MessageSelector) { + $translator = new IdentityTranslator($translator); + } elseif (null !== $translator && !$translator instanceof TranslatorInterface && !$translator instanceof LegacyTranslatorInterface) { + throw new \TypeError(sprintf('Argument 1 passed to "%s()" must be an instance of "%s", "%s" given.', __METHOD__, TranslatorInterface::class, \is_object($translator) ? \get_class($translator) : \gettype($translator))); + } + + $this->translator = $translator ?? new IdentityTranslator(); + $this->intlFormatter = $intlFormatter ?? new IntlFormatter(); + } + + /** + * {@inheritdoc} + */ + public function format($message, $locale, array $parameters = []) + { + if ($this->translator instanceof TranslatorInterface) { + return $this->translator->trans($message, $parameters, null, $locale); + } + + return strtr($message, $parameters); + } + + /** + * {@inheritdoc} + */ + public function formatIntl(string $message, string $locale, array $parameters = []): string + { + return $this->intlFormatter->formatIntl($message, $locale, $parameters); + } + + /** + * {@inheritdoc} + * + * @deprecated since Symfony 4.2, use format() with a %count% parameter instead + */ + public function choiceFormat($message, $number, $locale, array $parameters = []) + { + @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.2, use the format() one instead with a %%count%% parameter.', __METHOD__), \E_USER_DEPRECATED); + + $parameters = ['%count%' => $number] + $parameters; + + if ($this->translator instanceof TranslatorInterface) { + return $this->format($message, $locale, $parameters); + } + + return $this->format($this->translator->transChoice($message, $number, [], null, $locale), $locale, $parameters); + } +} diff --git a/vendor/symfony/translation/Formatter/MessageFormatterInterface.php b/vendor/symfony/translation/Formatter/MessageFormatterInterface.php new file mode 100644 index 00000000..370c0558 --- /dev/null +++ b/vendor/symfony/translation/Formatter/MessageFormatterInterface.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Formatter; + +/** + * @author Guilherme Blanco + * @author Abdellatif Ait boudad + */ +interface MessageFormatterInterface +{ + /** + * Formats a localized message pattern with given arguments. + * + * @param string $message The message (may also be an object that can be cast to string) + * @param string $locale The message locale + * @param array $parameters An array of parameters for the message + * + * @return string + */ + public function format($message, $locale, array $parameters = []); +} diff --git a/vendor/symfony/translation/IdentityTranslator.php b/vendor/symfony/translation/IdentityTranslator.php index 82b24701..86433961 100644 --- a/vendor/symfony/translation/IdentityTranslator.php +++ b/vendor/symfony/translation/IdentityTranslator.php @@ -11,53 +11,67 @@ namespace Symfony\Component\Translation; +use Symfony\Component\Translation\TranslatorInterface as LegacyTranslatorInterface; +use Symfony\Contracts\Translation\TranslatorInterface; +use Symfony\Contracts\Translation\TranslatorTrait; + /** * IdentityTranslator does not translate anything. * * @author Fabien Potencier */ -class IdentityTranslator implements TranslatorInterface +class IdentityTranslator implements LegacyTranslatorInterface, TranslatorInterface { + use TranslatorTrait { + trans as private doTrans; + setLocale as private doSetLocale; + } + private $selector; - private $locale; - /** - * @param MessageSelector|null $selector The message selector for pluralization - */ public function __construct(MessageSelector $selector = null) { - $this->selector = $selector ?: new MessageSelector(); + $this->selector = $selector; + + if (__CLASS__ !== static::class) { + @trigger_error(sprintf('Calling "%s()" is deprecated since Symfony 4.2.', __METHOD__), \E_USER_DEPRECATED); + } } /** * {@inheritdoc} */ - public function setLocale($locale) + public function trans($id, array $parameters = [], $domain = null, $locale = null) { - $this->locale = $locale; + return $this->doTrans($id, $parameters, $domain, $locale); } /** * {@inheritdoc} */ - public function getLocale() + public function setLocale($locale) { - return $this->locale ?: \Locale::getDefault(); + $this->doSetLocale($locale); } /** * {@inheritdoc} + * + * @deprecated since Symfony 4.2, use the trans() method instead with a %count% parameter */ - public function trans($id, array $parameters = array(), $domain = null, $locale = null) + public function transChoice($id, $number, array $parameters = [], $domain = null, $locale = null) { - return strtr((string) $id, $parameters); + @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.2, use the trans() one instead with a "%%count%%" parameter.', __METHOD__), \E_USER_DEPRECATED); + + if ($this->selector) { + return strtr($this->selector->choose((string) $id, $number, $locale ?: $this->getLocale()), $parameters); + } + + return $this->trans($id, ['%count%' => $number] + $parameters, $domain, $locale); } - /** - * {@inheritdoc} - */ - public function transChoice($id, $number, array $parameters = array(), $domain = null, $locale = null) + private function getPluralizationRule(float $number, string $locale): int { - return strtr($this->selector->choose((string) $id, (int) $number, $locale ?: $this->getLocale()), $parameters); + return PluralizationRules::get($number, $locale, false); } } diff --git a/vendor/symfony/translation/Interval.php b/vendor/symfony/translation/Interval.php index 2a51156e..bb6b164e 100644 --- a/vendor/symfony/translation/Interval.php +++ b/vendor/symfony/translation/Interval.php @@ -11,6 +11,10 @@ namespace Symfony\Component\Translation; +@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.2, use IdentityTranslator instead.', Interval::class), \E_USER_DEPRECATED); + +use Symfony\Component\Translation\Exception\InvalidArgumentException; + /** * Tests if a given number belongs to a given math interval. * @@ -30,6 +34,7 @@ * @author Fabien Potencier * * @see http://en.wikipedia.org/wiki/Interval_%28mathematics%29#The_ISO_notation + * @deprecated since Symfony 4.2, use IdentityTranslator instead */ class Interval { @@ -41,14 +46,14 @@ class Interval * * @return bool * - * @throws \InvalidArgumentException + * @throws InvalidArgumentException */ public static function test($number, $interval) { $interval = trim($interval); if (!preg_match('/^'.self::getIntervalRegexp().'$/x', $interval, $matches)) { - throw new \InvalidArgumentException(sprintf('"%s" is not a valid interval.', $interval)); + throw new InvalidArgumentException(sprintf('"%s" is not a valid interval.', $interval)); } if ($matches[1]) { @@ -94,7 +99,7 @@ public static function getIntervalRegexp() EOF; } - private static function convertNumber($number) + private static function convertNumber(string $number): float { if ('-Inf' === $number) { return log(0); diff --git a/vendor/symfony/translation/LICENSE b/vendor/symfony/translation/LICENSE index 21d7fb9e..88bf75bb 100644 --- a/vendor/symfony/translation/LICENSE +++ b/vendor/symfony/translation/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2018 Fabien Potencier +Copyright (c) 2004-2022 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 diff --git a/vendor/symfony/translation/Loader/ArrayLoader.php b/vendor/symfony/translation/Loader/ArrayLoader.php index 7bbfca68..2e9a4285 100644 --- a/vendor/symfony/translation/Loader/ArrayLoader.php +++ b/vendor/symfony/translation/Loader/ArrayLoader.php @@ -25,7 +25,7 @@ class ArrayLoader implements LoaderInterface */ public function load($resource, $locale, $domain = 'messages') { - $this->flatten($resource); + $resource = $this->flatten($resource); $catalogue = new MessageCatalogue($locale); $catalogue->add($resource, $domain); @@ -36,31 +36,23 @@ public function load($resource, $locale, $domain = 'messages') * Flattens an nested array of translations. * * The scheme used is: - * 'key' => array('key2' => array('key3' => 'value')) + * 'key' => ['key2' => ['key3' => 'value']] * Becomes: * 'key.key2.key3' => 'value' - * - * This function takes an array by reference and will modify it - * - * @param array &$messages The array that will be flattened - * @param array $subnode Current subnode being parsed, used internally for recursive calls - * @param string $path Current path being parsed, used internally for recursive calls */ - private function flatten(array &$messages, array $subnode = null, $path = null) + private function flatten(array $messages): array { - if (null === $subnode) { - $subnode = &$messages; - } - foreach ($subnode as $key => $value) { + $result = []; + foreach ($messages as $key => $value) { if (\is_array($value)) { - $nodePath = $path ? $path.'.'.$key : $key; - $this->flatten($messages, $value, $nodePath); - if (null === $path) { - unset($messages[$key]); + foreach ($this->flatten($value) as $k => $v) { + $result[$key.'.'.$k] = $v; } - } elseif (null !== $path) { - $messages[$path.'.'.$key] = $value; + } else { + $result[$key] = $value; } } + + return $result; } } diff --git a/vendor/symfony/translation/Loader/CsvFileLoader.php b/vendor/symfony/translation/Loader/CsvFileLoader.php index 8a763e72..db17bd56 100644 --- a/vendor/symfony/translation/Loader/CsvFileLoader.php +++ b/vendor/symfony/translation/Loader/CsvFileLoader.php @@ -29,7 +29,7 @@ class CsvFileLoader extends FileLoader */ protected function loadResource($resource) { - $messages = array(); + $messages = []; try { $file = new \SplFileObject($resource, 'rb'); @@ -41,6 +41,10 @@ protected function loadResource($resource) $file->setCsvControl($this->delimiter, $this->enclosure, $this->escape); foreach ($file as $data) { + if (false === $data) { + continue; + } + if ('#' !== substr($data[0], 0, 1) && isset($data[1]) && 2 === \count($data)) { $messages[$data[0]] = $data[1]; } diff --git a/vendor/symfony/translation/Loader/FileLoader.php b/vendor/symfony/translation/Loader/FileLoader.php index 9a02f525..42c687d2 100644 --- a/vendor/symfony/translation/Loader/FileLoader.php +++ b/vendor/symfony/translation/Loader/FileLoader.php @@ -37,7 +37,7 @@ public function load($resource, $locale, $domain = 'messages') // empty resource if (null === $messages) { - $messages = array(); + $messages = []; } // not an array @@ -47,7 +47,7 @@ public function load($resource, $locale, $domain = 'messages') $catalogue = parent::load($messages, $locale, $domain); - if (class_exists('Symfony\Component\Config\Resource\FileResource')) { + if (class_exists(FileResource::class)) { $catalogue->addResource(new FileResource($resource)); } diff --git a/vendor/symfony/translation/Loader/IcuDatFileLoader.php b/vendor/symfony/translation/Loader/IcuDatFileLoader.php index 83b5043d..dfdabc9d 100644 --- a/vendor/symfony/translation/Loader/IcuDatFileLoader.php +++ b/vendor/symfony/translation/Loader/IcuDatFileLoader.php @@ -39,12 +39,11 @@ public function load($resource, $locale, $domain = 'messages') try { $rb = new \ResourceBundle($locale, $resource); } catch (\Exception $e) { - // HHVM compatibility: constructor throws on invalid resource $rb = null; } if (!$rb) { - throw new InvalidResourceException(sprintf('Cannot load resource "%s"', $resource)); + throw new InvalidResourceException(sprintf('Cannot load resource "%s".', $resource)); } elseif (intl_is_failure($rb->getErrorCode())) { throw new InvalidResourceException($rb->getErrorMessage(), $rb->getErrorCode()); } @@ -53,7 +52,7 @@ public function load($resource, $locale, $domain = 'messages') $catalogue = new MessageCatalogue($locale); $catalogue->add($messages, $domain); - if (class_exists('Symfony\Component\Config\Resource\FileResource')) { + if (class_exists(FileResource::class)) { $catalogue->addResource(new FileResource($resource.'.dat')); } diff --git a/vendor/symfony/translation/Loader/IcuResFileLoader.php b/vendor/symfony/translation/Loader/IcuResFileLoader.php index fd2d1e4d..126556fe 100644 --- a/vendor/symfony/translation/Loader/IcuResFileLoader.php +++ b/vendor/symfony/translation/Loader/IcuResFileLoader.php @@ -39,12 +39,11 @@ public function load($resource, $locale, $domain = 'messages') try { $rb = new \ResourceBundle($locale, $resource); } catch (\Exception $e) { - // HHVM compatibility: constructor throws on invalid resource $rb = null; } if (!$rb) { - throw new InvalidResourceException(sprintf('Cannot load resource "%s"', $resource)); + throw new InvalidResourceException(sprintf('Cannot load resource "%s".', $resource)); } elseif (intl_is_failure($rb->getErrorCode())) { throw new InvalidResourceException($rb->getErrorMessage(), $rb->getErrorCode()); } @@ -53,7 +52,7 @@ public function load($resource, $locale, $domain = 'messages') $catalogue = new MessageCatalogue($locale); $catalogue->add($messages, $domain); - if (class_exists('Symfony\Component\Config\Resource\DirectoryResource')) { + if (class_exists(DirectoryResource::class)) { $catalogue->addResource(new DirectoryResource($resource)); } @@ -76,7 +75,7 @@ public function load($resource, $locale, $domain = 'messages') * * @return array the flattened ResourceBundle */ - protected function flatten(\ResourceBundle $rb, array &$messages = array(), $path = null) + protected function flatten(\ResourceBundle $rb, array &$messages = [], $path = null) { foreach ($rb as $key => $value) { $nodePath = $path ? $path.'.'.$key : $key; diff --git a/vendor/symfony/translation/Loader/JsonFileLoader.php b/vendor/symfony/translation/Loader/JsonFileLoader.php index ce4e91ff..8a8996b0 100644 --- a/vendor/symfony/translation/Loader/JsonFileLoader.php +++ b/vendor/symfony/translation/Loader/JsonFileLoader.php @@ -25,12 +25,12 @@ class JsonFileLoader extends FileLoader */ protected function loadResource($resource) { - $messages = array(); + $messages = []; if ($data = file_get_contents($resource)) { $messages = json_decode($data, true); if (0 < $errorCode = json_last_error()) { - throw new InvalidResourceException(sprintf('Error parsing JSON - %s', $this->getJSONErrorMessage($errorCode))); + throw new InvalidResourceException('Error parsing JSON: '.$this->getJSONErrorMessage($errorCode)); } } @@ -39,23 +39,19 @@ protected function loadResource($resource) /** * Translates JSON_ERROR_* constant into meaningful message. - * - * @param int $errorCode Error code returned by json_last_error() call - * - * @return string Message string */ - private function getJSONErrorMessage($errorCode) + private function getJSONErrorMessage(int $errorCode): string { switch ($errorCode) { - case JSON_ERROR_DEPTH: + case \JSON_ERROR_DEPTH: return 'Maximum stack depth exceeded'; - case JSON_ERROR_STATE_MISMATCH: + case \JSON_ERROR_STATE_MISMATCH: return 'Underflow or the modes mismatch'; - case JSON_ERROR_CTRL_CHAR: + case \JSON_ERROR_CTRL_CHAR: return 'Unexpected control character found'; - case JSON_ERROR_SYNTAX: + case \JSON_ERROR_SYNTAX: return 'Syntax error, malformed JSON'; - case JSON_ERROR_UTF8: + case \JSON_ERROR_UTF8: return 'Malformed UTF-8 characters, possibly incorrectly encoded'; default: return 'Unknown error'; diff --git a/vendor/symfony/translation/Loader/MoFileLoader.php b/vendor/symfony/translation/Loader/MoFileLoader.php index 93ecffa6..ce8611cd 100644 --- a/vendor/symfony/translation/Loader/MoFileLoader.php +++ b/vendor/symfony/translation/Loader/MoFileLoader.php @@ -19,21 +19,21 @@ class MoFileLoader extends FileLoader { /** - * Magic used for validating the format of a MO file as well as + * Magic used for validating the format of an MO file as well as * detecting if the machine used to create that file was little endian. */ - const MO_LITTLE_ENDIAN_MAGIC = 0x950412de; + public const MO_LITTLE_ENDIAN_MAGIC = 0x950412DE; /** - * Magic used for validating the format of a MO file as well as + * Magic used for validating the format of an MO file as well as * detecting if the machine used to create that file was big endian. */ - const MO_BIG_ENDIAN_MAGIC = 0xde120495; + public const MO_BIG_ENDIAN_MAGIC = 0xDE120495; /** - * The size of the header of a MO file in bytes. + * The size of the header of an MO file in bytes. */ - const MO_HEADER_SIZE = 28; + public const MO_HEADER_SIZE = 28; /** * Parses machine object (MO) format, independent of the machine's endian it @@ -71,7 +71,7 @@ protected function loadResource($resource) // offsetHashes $this->readLong($stream, $isBigEndian); - $messages = array(); + $messages = []; for ($i = 0; $i < $count; ++$i) { $pluralId = null; @@ -89,8 +89,8 @@ protected function loadResource($resource) fseek($stream, $offset); $singularId = fread($stream, $length); - if (false !== strpos($singularId, "\000")) { - list($singularId, $pluralId) = explode("\000", $singularId); + if (str_contains($singularId, "\000")) { + [$singularId, $pluralId] = explode("\000", $singularId); } fseek($stream, $offsetTranslated + $i * 8); @@ -104,24 +104,19 @@ protected function loadResource($resource) fseek($stream, $offset); $translated = fread($stream, $length); - if (false !== strpos($translated, "\000")) { + if (str_contains($translated, "\000")) { $translated = explode("\000", $translated); } - $ids = array('singular' => $singularId, 'plural' => $pluralId); + $ids = ['singular' => $singularId, 'plural' => $pluralId]; $item = compact('ids', 'translated'); - if (\is_array($item['translated'])) { - $messages[$item['ids']['singular']] = stripcslashes($item['translated'][0]); + if (!empty($item['ids']['singular'])) { + $id = $item['ids']['singular']; if (isset($item['ids']['plural'])) { - $plurals = array(); - foreach ($item['translated'] as $plural => $translated) { - $plurals[] = sprintf('{%d} %s', $plural, $translated); - } - $messages[$item['ids']['plural']] = stripcslashes(implode('|', $plurals)); + $id .= '|'.$item['ids']['plural']; } - } elseif (!empty($item['ids']['singular'])) { - $messages[$item['ids']['singular']] = stripcslashes($item['translated']); + $messages[$id] = stripcslashes(implode('|', (array) $item['translated'])); } } @@ -134,11 +129,8 @@ protected function loadResource($resource) * Reads an unsigned long from stream respecting endianness. * * @param resource $stream - * @param bool $isBigEndian - * - * @return int */ - private function readLong($stream, $isBigEndian) + private function readLong($stream, bool $isBigEndian): int { $result = unpack($isBigEndian ? 'N1' : 'V1', fread($stream, 4)); $result = current($result); diff --git a/vendor/symfony/translation/Loader/PhpFileLoader.php b/vendor/symfony/translation/Loader/PhpFileLoader.php index a0050e8d..2310740b 100644 --- a/vendor/symfony/translation/Loader/PhpFileLoader.php +++ b/vendor/symfony/translation/Loader/PhpFileLoader.php @@ -18,11 +18,25 @@ */ class PhpFileLoader extends FileLoader { + private static $cache = []; + /** * {@inheritdoc} */ protected function loadResource($resource) { - return require $resource; + if ([] === self::$cache && \function_exists('opcache_invalidate') && filter_var(\ini_get('opcache.enable'), \FILTER_VALIDATE_BOOLEAN) && (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) || filter_var(\ini_get('opcache.enable_cli'), \FILTER_VALIDATE_BOOLEAN))) { + self::$cache = null; + } + + if (null === self::$cache) { + return require $resource; + } + + if (isset(self::$cache[$resource])) { + return self::$cache[$resource]; + } + + return self::$cache[$resource] = require $resource; } } diff --git a/vendor/symfony/translation/Loader/PoFileLoader.php b/vendor/symfony/translation/Loader/PoFileLoader.php index 066168dc..5e460fbf 100644 --- a/vendor/symfony/translation/Loader/PoFileLoader.php +++ b/vendor/symfony/translation/Loader/PoFileLoader.php @@ -12,7 +12,7 @@ namespace Symfony\Component\Translation\Loader; /** - * @copyright Copyright (c) 2010, Union of RAD http://union-of-rad.org (http://lithify.me/) + * @copyright Copyright (c) 2010, Union of RAD https://github.com/UnionOfRAD/lithium * @copyright Copyright (c) 2012, Clemens Tolboom */ class PoFileLoader extends FileLoader @@ -20,7 +20,7 @@ class PoFileLoader extends FileLoader /** * Parses portable object (PO) format. * - * From http://www.gnu.org/software/gettext/manual/gettext.html#PO-Files + * From https://www.gnu.org/software/gettext/manual/gettext.html#PO-Files * we should be able to parse files having: * * white-space @@ -64,14 +64,14 @@ protected function loadResource($resource) { $stream = fopen($resource, 'r'); - $defaults = array( - 'ids' => array(), + $defaults = [ + 'ids' => [], 'translated' => null, - ); + ]; - $messages = array(); + $messages = []; $item = $defaults; - $flags = array(); + $flags = []; while ($line = fgets($stream)) { $line = trim($line); @@ -82,7 +82,7 @@ protected function loadResource($resource) $this->addMessage($messages, $item); } $item = $defaults; - $flags = array(); + $flags = []; } elseif ('#,' === substr($line, 0, 2)) { $flags = array_map('trim', explode(',', substr($line, 2))); } elseif ('msgid "' === substr($line, 0, 7)) { @@ -126,23 +126,24 @@ protected function loadResource($resource) */ private function addMessage(array &$messages, array $item) { - if (\is_array($item['translated'])) { - $messages[stripcslashes($item['ids']['singular'])] = stripcslashes($item['translated'][0]); + if (!empty($item['ids']['singular'])) { + $id = stripcslashes($item['ids']['singular']); if (isset($item['ids']['plural'])) { - $plurals = $item['translated']; - // PO are by definition indexed so sort by index. - ksort($plurals); - // Make sure every index is filled. - end($plurals); - $count = key($plurals); - // Fill missing spots with '-'. - $empties = array_fill(0, $count + 1, '-'); - $plurals += $empties; - ksort($plurals); - $messages[stripcslashes($item['ids']['plural'])] = stripcslashes(implode('|', $plurals)); + $id .= '|'.stripcslashes($item['ids']['plural']); } - } elseif (!empty($item['ids']['singular'])) { - $messages[stripcslashes($item['ids']['singular'])] = stripcslashes($item['translated']); + + $translated = (array) $item['translated']; + // PO are by definition indexed so sort by index. + ksort($translated); + // Make sure every index is filled. + end($translated); + $count = key($translated); + // Fill missing spots with '-'. + $empties = array_fill(0, $count + 1, '-'); + $translated += $empties; + ksort($translated); + + $messages[$id] = stripcslashes(implode('|', $translated)); } } } diff --git a/vendor/symfony/translation/Loader/QtFileLoader.php b/vendor/symfony/translation/Loader/QtFileLoader.php index 2d4a4c08..29567789 100644 --- a/vendor/symfony/translation/Loader/QtFileLoader.php +++ b/vendor/symfony/translation/Loader/QtFileLoader.php @@ -65,7 +65,7 @@ public function load($resource, $locale, $domain = 'messages') $translation = $translation->nextSibling; } - if (class_exists('Symfony\Component\Config\Resource\FileResource')) { + if (class_exists(FileResource::class)) { $catalogue->addResource(new FileResource($resource)); } } diff --git a/vendor/symfony/translation/Loader/XliffFileLoader.php b/vendor/symfony/translation/Loader/XliffFileLoader.php index 4ad35dec..35e2d9c1 100644 --- a/vendor/symfony/translation/Loader/XliffFileLoader.php +++ b/vendor/symfony/translation/Loader/XliffFileLoader.php @@ -16,6 +16,7 @@ use Symfony\Component\Translation\Exception\InvalidResourceException; use Symfony\Component\Translation\Exception\NotFoundResourceException; use Symfony\Component\Translation\MessageCatalogue; +use Symfony\Component\Translation\Util\XliffUtils; /** * XliffFileLoader loads translations from XLIFF files. @@ -40,23 +41,25 @@ public function load($resource, $locale, $domain = 'messages') $catalogue = new MessageCatalogue($locale); $this->extract($resource, $catalogue, $domain); - if (class_exists('Symfony\Component\Config\Resource\FileResource')) { + if (class_exists(FileResource::class)) { $catalogue->addResource(new FileResource($resource)); } return $catalogue; } - private function extract($resource, MessageCatalogue $catalogue, $domain) + private function extract($resource, MessageCatalogue $catalogue, string $domain) { try { $dom = XmlUtils::loadFile($resource); } catch (\InvalidArgumentException $e) { - throw new InvalidResourceException(sprintf('Unable to load "%s": %s', $resource, $e->getMessage()), $e->getCode(), $e); + throw new InvalidResourceException(sprintf('Unable to load "%s": ', $resource).$e->getMessage(), $e->getCode(), $e); } - $xliffVersion = $this->getVersionNumber($dom); - $this->validateSchema($xliffVersion, $dom, $this->getSchema($xliffVersion)); + $xliffVersion = XliffUtils::getVersionNumber($dom); + if ($errors = XliffUtils::validateSchema($dom)) { + throw new InvalidResourceException(sprintf('Invalid resource provided: "%s"; Errors: ', $resource).XliffUtils::getErrorsAsString($errors)); + } if ('1.2' === $xliffVersion) { $this->extractXliff1($dom, $catalogue, $domain); @@ -69,88 +72,106 @@ private function extract($resource, MessageCatalogue $catalogue, $domain) /** * Extract messages and metadata from DOMDocument into a MessageCatalogue. - * - * @param \DOMDocument $dom Source to extract messages and metadata - * @param MessageCatalogue $catalogue Catalogue where we'll collect messages and metadata - * @param string $domain The domain */ - private function extractXliff1(\DOMDocument $dom, MessageCatalogue $catalogue, $domain) + private function extractXliff1(\DOMDocument $dom, MessageCatalogue $catalogue, string $domain) { $xml = simplexml_import_dom($dom); - $encoding = strtoupper($dom->encoding); + $encoding = $dom->encoding ? strtoupper($dom->encoding) : null; - $xml->registerXPathNamespace('xliff', 'urn:oasis:names:tc:xliff:document:1.2'); - foreach ($xml->xpath('//xliff:trans-unit') as $translation) { - $attributes = $translation->attributes(); + $namespace = 'urn:oasis:names:tc:xliff:document:1.2'; + $xml->registerXPathNamespace('xliff', $namespace); - if (!(isset($attributes['resname']) || isset($translation->source))) { - continue; - } + foreach ($xml->xpath('//xliff:file') as $file) { + $fileAttributes = $file->attributes(); - $source = isset($attributes['resname']) && $attributes['resname'] ? $attributes['resname'] : $translation->source; - // If the xlf file has another encoding specified, try to convert it because - // simple_xml will always return utf-8 encoded values - $target = $this->utf8ToCharset((string) (isset($translation->target) ? $translation->target : $source), $encoding); + $file->registerXPathNamespace('xliff', $namespace); - $catalogue->set((string) $source, $target, $domain); + foreach ($file->xpath('.//xliff:trans-unit') as $translation) { + $attributes = $translation->attributes(); - $metadata = array(); - if ($notes = $this->parseNotesMetadata($translation->note, $encoding)) { - $metadata['notes'] = $notes; - } - if (isset($translation->target) && $translation->target->attributes()) { - $metadata['target-attributes'] = array(); - foreach ($translation->target->attributes() as $key => $value) { - $metadata['target-attributes'][$key] = (string) $value; + if (!(isset($attributes['resname']) || isset($translation->source))) { + continue; + } + + $source = isset($attributes['resname']) && $attributes['resname'] ? $attributes['resname'] : $translation->source; + // If the xlf file has another encoding specified, try to convert it because + // simple_xml will always return utf-8 encoded values + $target = $this->utf8ToCharset((string) ($translation->target ?? $translation->source), $encoding); + + $catalogue->set((string) $source, $target, $domain); + + $metadata = [ + 'source' => (string) $translation->source, + 'file' => [ + 'original' => (string) $fileAttributes['original'], + ], + ]; + if ($notes = $this->parseNotesMetadata($translation->note, $encoding)) { + $metadata['notes'] = $notes; + } + + if (isset($translation->target) && $translation->target->attributes()) { + $metadata['target-attributes'] = []; + foreach ($translation->target->attributes() as $key => $value) { + $metadata['target-attributes'][$key] = (string) $value; + } } - } - $catalogue->setMetadata((string) $source, $metadata, $domain); + if (isset($attributes['id'])) { + $metadata['id'] = (string) $attributes['id']; + } + + $catalogue->setMetadata((string) $source, $metadata, $domain); + } } } - /** - * @param \DOMDocument $dom - * @param MessageCatalogue $catalogue - * @param string $domain - */ - private function extractXliff2(\DOMDocument $dom, MessageCatalogue $catalogue, $domain) + private function extractXliff2(\DOMDocument $dom, MessageCatalogue $catalogue, string $domain) { $xml = simplexml_import_dom($dom); - $encoding = strtoupper($dom->encoding); + $encoding = $dom->encoding ? strtoupper($dom->encoding) : null; $xml->registerXPathNamespace('xliff', 'urn:oasis:names:tc:xliff:document:2.0'); - foreach ($xml->xpath('//xliff:unit/xliff:segment') as $segment) { - $source = $segment->source; + foreach ($xml->xpath('//xliff:unit') as $unit) { + foreach ($unit->segment as $segment) { + $source = $segment->source; - // If the xlf file has another encoding specified, try to convert it because - // simple_xml will always return utf-8 encoded values - $target = $this->utf8ToCharset((string) (isset($segment->target) ? $segment->target : $source), $encoding); + // If the xlf file has another encoding specified, try to convert it because + // simple_xml will always return utf-8 encoded values + $target = $this->utf8ToCharset((string) ($segment->target ?? $source), $encoding); - $catalogue->set((string) $source, $target, $domain); + $catalogue->set((string) $source, $target, $domain); - $metadata = array(); - if (isset($segment->target) && $segment->target->attributes()) { - $metadata['target-attributes'] = array(); - foreach ($segment->target->attributes() as $key => $value) { - $metadata['target-attributes'][$key] = (string) $value; + $metadata = []; + if (isset($segment->target) && $segment->target->attributes()) { + $metadata['target-attributes'] = []; + foreach ($segment->target->attributes() as $key => $value) { + $metadata['target-attributes'][$key] = (string) $value; + } + } + + if (isset($unit->notes)) { + $metadata['notes'] = []; + foreach ($unit->notes->note as $noteNode) { + $note = []; + foreach ($noteNode->attributes() as $key => $value) { + $note[$key] = (string) $value; + } + $note['content'] = (string) $noteNode; + $metadata['notes'][] = $note; + } } - } - $catalogue->setMetadata((string) $source, $metadata, $domain); + $catalogue->setMetadata((string) $source, $metadata, $domain); + } } } /** * Convert a UTF8 string to the specified encoding. - * - * @param string $content String to decode - * @param string $encoding Target encoding - * - * @return string */ - private function utf8ToCharset($content, $encoding = null) + private function utf8ToCharset(string $content, string $encoding = null): string { if ('UTF-8' !== $encoding && !empty($encoding)) { return mb_convert_encoding($content, $encoding, 'UTF-8'); @@ -159,157 +180,18 @@ private function utf8ToCharset($content, $encoding = null) return $content; } - /** - * Validates and parses the given file into a DOMDocument. - * - * @param string $file - * @param \DOMDocument $dom - * @param string $schema source of the schema - * - * @throws \RuntimeException - * @throws InvalidResourceException - */ - private function validateSchema($file, \DOMDocument $dom, $schema) - { - $internalErrors = libxml_use_internal_errors(true); - - $disableEntities = libxml_disable_entity_loader(false); - - if (!@$dom->schemaValidateSource($schema)) { - libxml_disable_entity_loader($disableEntities); - - throw new InvalidResourceException(sprintf('Invalid resource provided: "%s"; Errors: %s', $file, implode("\n", $this->getXmlErrors($internalErrors)))); - } - - libxml_disable_entity_loader($disableEntities); - - $dom->normalizeDocument(); - - libxml_clear_errors(); - libxml_use_internal_errors($internalErrors); - } - - private function getSchema($xliffVersion) - { - if ('1.2' === $xliffVersion) { - $schemaSource = file_get_contents(__DIR__.'/schema/dic/xliff-core/xliff-core-1.2-strict.xsd'); - $xmlUri = 'http://www.w3.org/2001/xml.xsd'; - } elseif ('2.0' === $xliffVersion) { - $schemaSource = file_get_contents(__DIR__.'/schema/dic/xliff-core/xliff-core-2.0.xsd'); - $xmlUri = 'informativeCopiesOf3rdPartySchemas/w3c/xml.xsd'; - } else { - throw new \InvalidArgumentException(sprintf('No support implemented for loading XLIFF version "%s".', $xliffVersion)); - } - - return $this->fixXmlLocation($schemaSource, $xmlUri); - } - - /** - * Internally changes the URI of a dependent xsd to be loaded locally. - * - * @param string $schemaSource Current content of schema file - * @param string $xmlUri External URI of XML to convert to local - * - * @return string - */ - private function fixXmlLocation($schemaSource, $xmlUri) - { - $newPath = str_replace('\\', '/', __DIR__).'/schema/dic/xliff-core/xml.xsd'; - $parts = explode('/', $newPath); - $locationstart = 'file:///'; - if (0 === stripos($newPath, 'phar://')) { - $tmpfile = tempnam(sys_get_temp_dir(), 'sf2'); - if ($tmpfile) { - copy($newPath, $tmpfile); - $parts = explode('/', str_replace('\\', '/', $tmpfile)); - } else { - array_shift($parts); - $locationstart = 'phar:///'; - } - } - $drive = '\\' === \DIRECTORY_SEPARATOR ? array_shift($parts).'/' : ''; - $newPath = $locationstart.$drive.implode('/', array_map('rawurlencode', $parts)); - - return str_replace($xmlUri, $newPath, $schemaSource); - } - - /** - * Returns the XML errors of the internal XML parser. - * - * @param bool $internalErrors - * - * @return array An array of errors - */ - private function getXmlErrors($internalErrors) - { - $errors = array(); - foreach (libxml_get_errors() as $error) { - $errors[] = sprintf('[%s %s] %s (in %s - line %d, column %d)', - LIBXML_ERR_WARNING == $error->level ? 'WARNING' : 'ERROR', - $error->code, - trim($error->message), - $error->file ?: 'n/a', - $error->line, - $error->column - ); - } - - libxml_clear_errors(); - libxml_use_internal_errors($internalErrors); - - return $errors; - } - - /** - * Gets xliff file version based on the root "version" attribute. - * Defaults to 1.2 for backwards compatibility. - * - * @param \DOMDocument $dom - * - * @throws \InvalidArgumentException - * - * @return string - */ - private function getVersionNumber(\DOMDocument $dom) - { - /** @var \DOMNode $xliff */ - foreach ($dom->getElementsByTagName('xliff') as $xliff) { - $version = $xliff->attributes->getNamedItem('version'); - if ($version) { - return $version->nodeValue; - } - - $namespace = $xliff->attributes->getNamedItem('xmlns'); - if ($namespace) { - if (0 !== substr_compare('urn:oasis:names:tc:xliff:document:', $namespace->nodeValue, 0, 34)) { - throw new \InvalidArgumentException(sprintf('Not a valid XLIFF namespace "%s"', $namespace)); - } - - return substr($namespace, 34); - } - } - - // Falls back to v1.2 - return '1.2'; - } - - /* - * @param \SimpleXMLElement|null $noteElement - * @param string|null $encoding - * - * @return array - */ - private function parseNotesMetadata(\SimpleXMLElement $noteElement = null, $encoding = null) + private function parseNotesMetadata(\SimpleXMLElement $noteElement = null, string $encoding = null): array { - $notes = array(); + $notes = []; if (null === $noteElement) { return $notes; } + /** @var \SimpleXMLElement $xmlNote */ foreach ($noteElement as $xmlNote) { $noteAttributes = $xmlNote->attributes(); - $note = array('content' => $this->utf8ToCharset((string) $xmlNote, $encoding)); + $note = ['content' => $this->utf8ToCharset((string) $xmlNote, $encoding)]; if (isset($noteAttributes['priority'])) { $note['priority'] = (int) $noteAttributes['priority']; } diff --git a/vendor/symfony/translation/Loader/YamlFileLoader.php b/vendor/symfony/translation/Loader/YamlFileLoader.php index 6801624a..b03c7b77 100644 --- a/vendor/symfony/translation/Loader/YamlFileLoader.php +++ b/vendor/symfony/translation/Loader/YamlFileLoader.php @@ -12,8 +12,10 @@ namespace Symfony\Component\Translation\Loader; use Symfony\Component\Translation\Exception\InvalidResourceException; +use Symfony\Component\Translation\Exception\LogicException; use Symfony\Component\Yaml\Exception\ParseException; use Symfony\Component\Yaml\Parser as YamlParser; +use Symfony\Component\Yaml\Yaml; /** * YamlFileLoader loads translations from Yaml files. @@ -30,19 +32,23 @@ class YamlFileLoader extends FileLoader protected function loadResource($resource) { if (null === $this->yamlParser) { - if (!class_exists('Symfony\Component\Yaml\Parser')) { - throw new \LogicException('Loading translations from the YAML format requires the Symfony Yaml component.'); + if (!class_exists(\Symfony\Component\Yaml\Parser::class)) { + throw new LogicException('Loading translations from the YAML format requires the Symfony Yaml component.'); } $this->yamlParser = new YamlParser(); } try { - $messages = $this->yamlParser->parse(file_get_contents($resource)); + $messages = $this->yamlParser->parseFile($resource, Yaml::PARSE_CONSTANT); } catch (ParseException $e) { - throw new InvalidResourceException(sprintf('Error parsing YAML, invalid file "%s"', $resource), 0, $e); + throw new InvalidResourceException(sprintf('The file "%s" does not contain valid YAML: ', $resource).$e->getMessage(), 0, $e); } - return $messages; + if (null !== $messages && !\is_array($messages)) { + throw new InvalidResourceException(sprintf('Unable to load file "%s".', $resource)); + } + + return $messages ?: []; } } diff --git a/vendor/symfony/translation/LoggingTranslator.php b/vendor/symfony/translation/LoggingTranslator.php index 1cefcb6b..d7b6fc4b 100644 --- a/vendor/symfony/translation/LoggingTranslator.php +++ b/vendor/symfony/translation/LoggingTranslator.php @@ -12,11 +12,15 @@ namespace Symfony\Component\Translation; use Psr\Log\LoggerInterface; +use Symfony\Component\Translation\Exception\InvalidArgumentException; +use Symfony\Component\Translation\TranslatorInterface as LegacyTranslatorInterface; +use Symfony\Contracts\Translation\LocaleAwareInterface; +use Symfony\Contracts\Translation\TranslatorInterface; /** * @author Abdellatif Ait boudad */ -class LoggingTranslator implements TranslatorInterface, TranslatorBagInterface +class LoggingTranslator implements TranslatorInterface, LegacyTranslatorInterface, TranslatorBagInterface { /** * @var TranslatorInterface|TranslatorBagInterface @@ -27,12 +31,14 @@ class LoggingTranslator implements TranslatorInterface, TranslatorBagInterface /** * @param TranslatorInterface $translator The translator must implement TranslatorBagInterface - * @param LoggerInterface $logger */ - public function __construct(TranslatorInterface $translator, LoggerInterface $logger) + public function __construct($translator, LoggerInterface $logger) { - if (!$translator instanceof TranslatorBagInterface) { - throw new \InvalidArgumentException(sprintf('The Translator "%s" must implement TranslatorInterface and TranslatorBagInterface.', \get_class($translator))); + if (!$translator instanceof LegacyTranslatorInterface && !$translator instanceof TranslatorInterface) { + throw new \TypeError(sprintf('Argument 1 passed to "%s()" must be an instance of "%s", "%s" given.', __METHOD__, TranslatorInterface::class, \is_object($translator) ? \get_class($translator) : \gettype($translator))); + } + if (!$translator instanceof TranslatorBagInterface || !$translator instanceof LocaleAwareInterface) { + throw new InvalidArgumentException(sprintf('The Translator "%s" must implement TranslatorInterface, TranslatorBagInterface and LocaleAwareInterface.', \get_class($translator))); } $this->translator = $translator; @@ -42,7 +48,7 @@ public function __construct(TranslatorInterface $translator, LoggerInterface $lo /** * {@inheritdoc} */ - public function trans($id, array $parameters = array(), $domain = null, $locale = null) + public function trans($id, array $parameters = [], $domain = null, $locale = null) { $trans = $this->translator->trans($id, $parameters, $domain, $locale); $this->log($id, $domain, $locale); @@ -52,10 +58,19 @@ public function trans($id, array $parameters = array(), $domain = null, $locale /** * {@inheritdoc} + * + * @deprecated since Symfony 4.2, use the trans() method instead with a %count% parameter */ - public function transChoice($id, $number, array $parameters = array(), $domain = null, $locale = null) + public function transChoice($id, $number, array $parameters = [], $domain = null, $locale = null) { - $trans = $this->translator->transChoice($id, $number, $parameters, $domain, $locale); + @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.2, use the trans() one instead with a "%%count%%" parameter.', __METHOD__), \E_USER_DEPRECATED); + + if ($this->translator instanceof TranslatorInterface) { + $trans = $this->translator->trans($id, ['%count%' => $number] + $parameters, $domain, $locale); + } else { + $trans = $this->translator->transChoice($id, $number, $parameters, $domain, $locale); + } + $this->log($id, $domain, $locale); return $trans; @@ -66,7 +81,13 @@ public function transChoice($id, $number, array $parameters = array(), $domain = */ public function setLocale($locale) { + $prev = $this->translator->getLocale(); $this->translator->setLocale($locale); + if ($prev === $locale) { + return; + } + + $this->logger->debug(sprintf('The locale of the translator has changed from "%s" to "%s".', $prev, $locale)); } /** @@ -88,7 +109,7 @@ public function getCatalogue($locale = null) /** * Gets the fallback locales. * - * @return array $locales The fallback locales + * @return array The fallback locales */ public function getFallbackLocales() { @@ -96,7 +117,7 @@ public function getFallbackLocales() return $this->translator->getFallbackLocales(); } - return array(); + return []; } /** @@ -104,17 +125,13 @@ public function getFallbackLocales() */ public function __call($method, $args) { - return \call_user_func_array(array($this->translator, $method), $args); + return $this->translator->{$method}(...$args); } /** * Logs for missing translations. - * - * @param string $id - * @param string|null $domain - * @param string|null $locale */ - private function log($id, $domain, $locale) + private function log(?string $id, ?string $domain, ?string $locale) { if (null === $domain) { $domain = 'messages'; @@ -127,9 +144,9 @@ private function log($id, $domain, $locale) } if ($catalogue->has($id, $domain)) { - $this->logger->debug('Translation use fallback catalogue.', array('id' => $id, 'domain' => $domain, 'locale' => $catalogue->getLocale())); + $this->logger->debug('Translation use fallback catalogue.', ['id' => $id, 'domain' => $domain, 'locale' => $catalogue->getLocale()]); } else { - $this->logger->warning('Translation not found.', array('id' => $id, 'domain' => $domain, 'locale' => $catalogue->getLocale())); + $this->logger->warning('Translation not found.', ['id' => $id, 'domain' => $domain, 'locale' => $catalogue->getLocale()]); } } } diff --git a/vendor/symfony/translation/MessageCatalogue.php b/vendor/symfony/translation/MessageCatalogue.php index 920bc84c..b43b22d6 100644 --- a/vendor/symfony/translation/MessageCatalogue.php +++ b/vendor/symfony/translation/MessageCatalogue.php @@ -12,25 +12,29 @@ namespace Symfony\Component\Translation; use Symfony\Component\Config\Resource\ResourceInterface; +use Symfony\Component\Translation\Exception\LogicException; /** * @author Fabien Potencier */ class MessageCatalogue implements MessageCatalogueInterface, MetadataAwareInterface { - private $messages = array(); - private $metadata = array(); - private $resources = array(); + private $messages = []; + private $metadata = []; + private $resources = []; private $locale; private $fallbackCatalogue; private $parent; /** - * @param string $locale The locale - * @param array $messages An array of messages classified by domain + * @param array $messages An array of messages classified by domain */ - public function __construct($locale, array $messages = array()) + public function __construct(?string $locale, array $messages = []) { + if (null === $locale) { + @trigger_error(sprintf('Passing "null" to the first argument of the "%s" method has been deprecated since Symfony 4.4 and will throw an error in 5.0.', __METHOD__), \E_USER_DEPRECATED); + } + $this->locale = $locale; $this->messages = $messages; } @@ -48,7 +52,16 @@ public function getLocale() */ public function getDomains() { - return array_keys($this->messages); + $domains = []; + + foreach ($this->messages as $domain => $messages) { + if (str_ends_with($domain, self::INTL_DOMAIN_SUFFIX)) { + $domain = substr($domain, 0, -\strlen(self::INTL_DOMAIN_SUFFIX)); + } + $domains[$domain] = $domain; + } + + return array_values($domains); } /** @@ -56,11 +69,27 @@ public function getDomains() */ public function all($domain = null) { - if (null === $domain) { - return $this->messages; + if (null !== $domain) { + // skip messages merge if intl-icu requested explicitly + if (str_ends_with($domain, self::INTL_DOMAIN_SUFFIX)) { + return $this->messages[$domain] ?? []; + } + + return ($this->messages[$domain.self::INTL_DOMAIN_SUFFIX] ?? []) + ($this->messages[$domain] ?? []); + } + + $allMessages = []; + + foreach ($this->messages as $domain => $messages) { + if (str_ends_with($domain, self::INTL_DOMAIN_SUFFIX)) { + $domain = substr($domain, 0, -\strlen(self::INTL_DOMAIN_SUFFIX)); + $allMessages[$domain] = $messages + ($allMessages[$domain] ?? []); + } else { + $allMessages[$domain] = ($allMessages[$domain] ?? []) + $messages; + } } - return isset($this->messages[$domain]) ? $this->messages[$domain] : array(); + return $allMessages; } /** @@ -68,7 +97,7 @@ public function all($domain = null) */ public function set($id, $translation, $domain = 'messages') { - $this->add(array($id => $translation), $domain); + $this->add([$id => $translation], $domain); } /** @@ -76,7 +105,7 @@ public function set($id, $translation, $domain = 'messages') */ public function has($id, $domain = 'messages') { - if (isset($this->messages[$domain][$id])) { + if (isset($this->messages[$domain][$id]) || isset($this->messages[$domain.self::INTL_DOMAIN_SUFFIX][$id])) { return true; } @@ -92,7 +121,7 @@ public function has($id, $domain = 'messages') */ public function defines($id, $domain = 'messages') { - return isset($this->messages[$domain][$id]); + return isset($this->messages[$domain][$id]) || isset($this->messages[$domain.self::INTL_DOMAIN_SUFFIX][$id]); } /** @@ -100,6 +129,10 @@ public function defines($id, $domain = 'messages') */ public function get($id, $domain = 'messages') { + if (isset($this->messages[$domain.self::INTL_DOMAIN_SUFFIX][$id])) { + return $this->messages[$domain.self::INTL_DOMAIN_SUFFIX][$id]; + } + if (isset($this->messages[$domain][$id])) { return $this->messages[$domain][$id]; } @@ -116,7 +149,7 @@ public function get($id, $domain = 'messages') */ public function replace($messages, $domain = 'messages') { - $this->messages[$domain] = array(); + unset($this->messages[$domain], $this->messages[$domain.self::INTL_DOMAIN_SUFFIX]); $this->add($messages, $domain); } @@ -126,10 +159,14 @@ public function replace($messages, $domain = 'messages') */ public function add($messages, $domain = 'messages') { - if (!isset($this->messages[$domain])) { - $this->messages[$domain] = $messages; - } else { - $this->messages[$domain] = array_replace($this->messages[$domain], $messages); + $altDomain = str_ends_with($domain, self::INTL_DOMAIN_SUFFIX) ? substr($domain, 0, -\strlen(self::INTL_DOMAIN_SUFFIX)) : $domain.self::INTL_DOMAIN_SUFFIX; + foreach ($messages as $id => $message) { + unset($this->messages[$altDomain][$id]); + $this->messages[$domain][$id] = $message; + } + + if ([] === ($this->messages[$altDomain] ?? null)) { + unset($this->messages[$altDomain]); } } @@ -139,10 +176,14 @@ public function add($messages, $domain = 'messages') public function addCatalogue(MessageCatalogueInterface $catalogue) { if ($catalogue->getLocale() !== $this->locale) { - throw new \LogicException(sprintf('Cannot add a catalogue for locale "%s" as the current locale for this catalogue is "%s"', $catalogue->getLocale(), $this->locale)); + throw new LogicException(sprintf('Cannot add a catalogue for locale "%s" as the current locale for this catalogue is "%s".', $catalogue->getLocale(), $this->locale)); } foreach ($catalogue->all() as $domain => $messages) { + if ($intlMessages = $catalogue->all($domain.self::INTL_DOMAIN_SUFFIX)) { + $this->add($intlMessages, $domain.self::INTL_DOMAIN_SUFFIX); + $messages = array_diff_key($messages, $intlMessages); + } $this->add($messages, $domain); } @@ -165,14 +206,14 @@ public function addFallbackCatalogue(MessageCatalogueInterface $catalogue) $c = $catalogue; while ($c = $c->getFallbackCatalogue()) { if ($c->getLocale() === $this->getLocale()) { - throw new \LogicException(sprintf('Circular reference detected when adding a fallback catalogue for locale "%s".', $catalogue->getLocale())); + throw new LogicException(sprintf('Circular reference detected when adding a fallback catalogue for locale "%s".', $catalogue->getLocale())); } } $c = $this; do { if ($c->getLocale() === $catalogue->getLocale()) { - throw new \LogicException(sprintf('Circular reference detected when adding a fallback catalogue for locale "%s".', $catalogue->getLocale())); + throw new LogicException(sprintf('Circular reference detected when adding a fallback catalogue for locale "%s".', $catalogue->getLocale())); } foreach ($catalogue->getResources() as $resource) { @@ -230,6 +271,8 @@ public function getMetadata($key = '', $domain = 'messages') return $this->metadata[$domain][$key]; } } + + return null; } /** @@ -246,7 +289,7 @@ public function setMetadata($key, $value, $domain = 'messages') public function deleteMetadata($key = '', $domain = 'messages') { if ('' == $domain) { - $this->metadata = array(); + $this->metadata = []; } elseif ('' == $key) { unset($this->metadata[$domain]); } else { diff --git a/vendor/symfony/translation/MessageCatalogueInterface.php b/vendor/symfony/translation/MessageCatalogueInterface.php index 4dad27fb..853ce703 100644 --- a/vendor/symfony/translation/MessageCatalogueInterface.php +++ b/vendor/symfony/translation/MessageCatalogueInterface.php @@ -20,6 +20,8 @@ */ interface MessageCatalogueInterface { + public const INTL_DOMAIN_SUFFIX = '+intl-icu'; + /** * Gets the catalogue locale. * @@ -105,7 +107,7 @@ public function add($messages, $domain = 'messages'); * * The two catalogues must have the same locale. */ - public function addCatalogue(MessageCatalogueInterface $catalogue); + public function addCatalogue(self $catalogue); /** * Merges translations from the given Catalogue into the current one @@ -113,7 +115,7 @@ public function addCatalogue(MessageCatalogueInterface $catalogue); * * This is used to provide default translations when they do not exist for the current locale. */ - public function addFallbackCatalogue(MessageCatalogueInterface $catalogue); + public function addFallbackCatalogue(self $catalogue); /** * Gets the fallback catalogue. diff --git a/vendor/symfony/translation/MessageSelector.php b/vendor/symfony/translation/MessageSelector.php index 4a610d53..38de357a 100644 --- a/vendor/symfony/translation/MessageSelector.php +++ b/vendor/symfony/translation/MessageSelector.php @@ -11,11 +11,17 @@ namespace Symfony\Component\Translation; +@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.2, use IdentityTranslator instead.', MessageSelector::class), \E_USER_DEPRECATED); + +use Symfony\Component\Translation\Exception\InvalidArgumentException; + /** * MessageSelector. * * @author Fabien Potencier * @author Bernhard Schussek + * + * @deprecated since Symfony 4.2, use IdentityTranslator instead. */ class MessageSelector { @@ -37,21 +43,27 @@ class MessageSelector * The two methods can also be mixed: * {0} There are no apples|one: There is one apple|more: There are %count% apples * - * @param string $message The message being translated - * @param int $number The number of items represented for the message - * @param string $locale The locale to use for choosing + * @param string $message The message being translated + * @param int|float $number The number of items represented for the message + * @param string $locale The locale to use for choosing * * @return string * - * @throws \InvalidArgumentException + * @throws InvalidArgumentException */ public function choose($message, $number, $locale) { - $parts = explode('|', $message); - $explicitRules = array(); - $standardRules = array(); + $parts = []; + if (preg_match('/^\|++$/', $message)) { + $parts = explode('|', $message); + } elseif (preg_match_all('/(?:\|\||[^\|])++/', $message, $matches)) { + $parts = $matches[0]; + } + + $explicitRules = []; + $standardRules = []; foreach ($parts as $part) { - $part = trim($part); + $part = trim(str_replace('||', '|', $part)); if (preg_match('/^(?P'.Interval::getIntervalRegexp().')\s*(?P.*?)$/xs', $part, $matches)) { $explicitRules[$matches['interval']] = $matches['message']; @@ -78,7 +90,7 @@ public function choose($message, $number, $locale) return $standardRules[0]; } - throw new \InvalidArgumentException(sprintf('Unable to choose a translation for "%s" with locale "%s" for value "%d". Double check that this translation has the correct plural options (e.g. "There is one apple|There are %%count%% apples").', $message, $locale, $number)); + throw new InvalidArgumentException(sprintf('Unable to choose a translation for "%s" with locale "%s" for value "%d". Double check that this translation has the correct plural options (e.g. "There is one apple|There are %%count%% apples").', $message, $locale, $number)); } return $standardRules[$position]; diff --git a/vendor/symfony/translation/PluralizationRules.php b/vendor/symfony/translation/PluralizationRules.php index 15241148..84513a24 100644 --- a/vendor/symfony/translation/PluralizationRules.php +++ b/vendor/symfony/translation/PluralizationRules.php @@ -15,32 +15,40 @@ * Returns the plural rules for a given locale. * * @author Fabien Potencier + * + * @deprecated since Symfony 4.2, use IdentityTranslator instead */ class PluralizationRules { - private static $rules = array(); + private static $rules = []; /** * Returns the plural position to use for the given locale and number. * - * @param int $number The number + * @param float $number The number * @param string $locale The locale * * @return int The plural position */ - public static function get($number, $locale) + public static function get($number, $locale/* , bool $triggerDeprecation = true */) { + $number = abs($number); + + if (3 > \func_num_args() || func_get_arg(2)) { + @trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.2.', __CLASS__), \E_USER_DEPRECATED); + } + if ('pt_BR' === $locale) { // temporary set a locale for brazilian $locale = 'xbr'; } - if (\strlen($locale) > 3) { + if ('en_US_POSIX' !== $locale && \strlen($locale) > 3) { $locale = substr($locale, 0, -\strlen(strrchr($locale, '_'))); } if (isset(self::$rules[$locale])) { - $return = \call_user_func(self::$rules[$locale], $number); + $return = self::$rules[$locale]($number); if (!\is_int($return) || $return < 0) { return 0; @@ -80,6 +88,7 @@ public static function get($number, $locale) case 'de': case 'el': case 'en': + case 'en_US_POSIX': case 'eo': case 'es': case 'et': @@ -138,7 +147,7 @@ public static function get($number, $locale) case 'xbr': case 'ti': case 'wa': - return ((0 == $number) || (1 == $number)) ? 0 : 1; + return ($number < 2) ? 0 : 1; case 'be': case 'bs': @@ -193,11 +202,11 @@ public static function get($number, $locale) * * @param callable $rule A PHP callable * @param string $locale The locale - * - * @throws \LogicException */ - public static function set($rule, $locale) + public static function set(callable $rule, $locale) { + @trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.2.', __CLASS__), \E_USER_DEPRECATED); + if ('pt_BR' === $locale) { // temporary set a locale for brazilian $locale = 'xbr'; @@ -207,10 +216,6 @@ public static function set($rule, $locale) $locale = substr($locale, 0, -\strlen(strrchr($locale, '_'))); } - if (!\is_callable($rule)) { - throw new \LogicException('The given rule can not be called'); - } - self::$rules[$locale] = $rule; } } diff --git a/vendor/symfony/translation/Reader/TranslationReader.php b/vendor/symfony/translation/Reader/TranslationReader.php new file mode 100644 index 00000000..2b983452 --- /dev/null +++ b/vendor/symfony/translation/Reader/TranslationReader.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Reader; + +use Symfony\Component\Finder\Finder; +use Symfony\Component\Translation\Loader\LoaderInterface; +use Symfony\Component\Translation\MessageCatalogue; + +/** + * TranslationReader reads translation messages from translation files. + * + * @author Michel Salib + */ +class TranslationReader implements TranslationReaderInterface +{ + /** + * Loaders used for import. + * + * @var array + */ + private $loaders = []; + + /** + * Adds a loader to the translation extractor. + * + * @param string $format The format of the loader + */ + public function addLoader($format, LoaderInterface $loader) + { + $this->loaders[$format] = $loader; + } + + /** + * {@inheritdoc} + */ + public function read($directory, MessageCatalogue $catalogue) + { + if (!is_dir($directory)) { + return; + } + + foreach ($this->loaders as $format => $loader) { + // load any existing translation files + $finder = new Finder(); + $extension = $catalogue->getLocale().'.'.$format; + $files = $finder->files()->name('*.'.$extension)->in($directory); + foreach ($files as $file) { + $domain = substr($file->getFilename(), 0, -1 * \strlen($extension) - 1); + $catalogue->addCatalogue($loader->load($file->getPathname(), $catalogue->getLocale(), $domain)); + } + } + } +} diff --git a/vendor/symfony/translation/Reader/TranslationReaderInterface.php b/vendor/symfony/translation/Reader/TranslationReaderInterface.php new file mode 100644 index 00000000..0b2ad332 --- /dev/null +++ b/vendor/symfony/translation/Reader/TranslationReaderInterface.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Reader; + +use Symfony\Component\Translation\MessageCatalogue; + +/** + * TranslationReader reads translation messages from translation files. + * + * @author Tobias Nyholm + */ +interface TranslationReaderInterface +{ + /** + * Reads translation messages from a directory to the catalogue. + * + * @param string $directory + */ + public function read($directory, MessageCatalogue $catalogue); +} diff --git a/vendor/symfony/translation/Resources/data/parents.json b/vendor/symfony/translation/Resources/data/parents.json new file mode 100644 index 00000000..32a33cda --- /dev/null +++ b/vendor/symfony/translation/Resources/data/parents.json @@ -0,0 +1,141 @@ +{ + "az_Cyrl": "root", + "bs_Cyrl": "root", + "en_150": "en_001", + "en_AG": "en_001", + "en_AI": "en_001", + "en_AT": "en_150", + "en_AU": "en_001", + "en_BB": "en_001", + "en_BE": "en_150", + "en_BM": "en_001", + "en_BS": "en_001", + "en_BW": "en_001", + "en_BZ": "en_001", + "en_CC": "en_001", + "en_CH": "en_150", + "en_CK": "en_001", + "en_CM": "en_001", + "en_CX": "en_001", + "en_CY": "en_001", + "en_DE": "en_150", + "en_DG": "en_001", + "en_DK": "en_150", + "en_DM": "en_001", + "en_ER": "en_001", + "en_FI": "en_150", + "en_FJ": "en_001", + "en_FK": "en_001", + "en_FM": "en_001", + "en_GB": "en_001", + "en_GD": "en_001", + "en_GG": "en_001", + "en_GH": "en_001", + "en_GI": "en_001", + "en_GM": "en_001", + "en_GY": "en_001", + "en_HK": "en_001", + "en_IE": "en_001", + "en_IL": "en_001", + "en_IM": "en_001", + "en_IN": "en_001", + "en_IO": "en_001", + "en_JE": "en_001", + "en_JM": "en_001", + "en_KE": "en_001", + "en_KI": "en_001", + "en_KN": "en_001", + "en_KY": "en_001", + "en_LC": "en_001", + "en_LR": "en_001", + "en_LS": "en_001", + "en_MG": "en_001", + "en_MO": "en_001", + "en_MS": "en_001", + "en_MT": "en_001", + "en_MU": "en_001", + "en_MV": "en_001", + "en_MW": "en_001", + "en_MY": "en_001", + "en_NA": "en_001", + "en_NF": "en_001", + "en_NG": "en_001", + "en_NL": "en_150", + "en_NR": "en_001", + "en_NU": "en_001", + "en_NZ": "en_001", + "en_PG": "en_001", + "en_PK": "en_001", + "en_PN": "en_001", + "en_PW": "en_001", + "en_RW": "en_001", + "en_SB": "en_001", + "en_SC": "en_001", + "en_SD": "en_001", + "en_SE": "en_150", + "en_SG": "en_001", + "en_SH": "en_001", + "en_SI": "en_150", + "en_SL": "en_001", + "en_SS": "en_001", + "en_SX": "en_001", + "en_SZ": "en_001", + "en_TC": "en_001", + "en_TK": "en_001", + "en_TO": "en_001", + "en_TT": "en_001", + "en_TV": "en_001", + "en_TZ": "en_001", + "en_UG": "en_001", + "en_VC": "en_001", + "en_VG": "en_001", + "en_VU": "en_001", + "en_WS": "en_001", + "en_ZA": "en_001", + "en_ZM": "en_001", + "en_ZW": "en_001", + "es_AR": "es_419", + "es_BO": "es_419", + "es_BR": "es_419", + "es_BZ": "es_419", + "es_CL": "es_419", + "es_CO": "es_419", + "es_CR": "es_419", + "es_CU": "es_419", + "es_DO": "es_419", + "es_EC": "es_419", + "es_GT": "es_419", + "es_HN": "es_419", + "es_MX": "es_419", + "es_NI": "es_419", + "es_PA": "es_419", + "es_PE": "es_419", + "es_PR": "es_419", + "es_PY": "es_419", + "es_SV": "es_419", + "es_US": "es_419", + "es_UY": "es_419", + "es_VE": "es_419", + "ff_Adlm": "root", + "hi_Latn": "en_IN", + "ks_Deva": "root", + "nb": "no", + "nn": "no", + "pa_Arab": "root", + "pt_AO": "pt_PT", + "pt_CH": "pt_PT", + "pt_CV": "pt_PT", + "pt_GQ": "pt_PT", + "pt_GW": "pt_PT", + "pt_LU": "pt_PT", + "pt_MO": "pt_PT", + "pt_MZ": "pt_PT", + "pt_ST": "pt_PT", + "pt_TL": "pt_PT", + "sd_Deva": "root", + "sr_Latn": "root", + "uz_Arab": "root", + "uz_Cyrl": "root", + "zh_Hant": "root", + "zh_Hant_MO": "zh_Hant_HK" +} diff --git a/vendor/symfony/translation/Loader/schema/dic/xliff-core/xliff-core-1.2-strict.xsd b/vendor/symfony/translation/Resources/schemas/xliff-core-1.2-strict.xsd similarity index 99% rename from vendor/symfony/translation/Loader/schema/dic/xliff-core/xliff-core-1.2-strict.xsd rename to vendor/symfony/translation/Resources/schemas/xliff-core-1.2-strict.xsd index 3ce2a8e8..dface628 100644 --- a/vendor/symfony/translation/Loader/schema/dic/xliff-core/xliff-core-1.2-strict.xsd +++ b/vendor/symfony/translation/Resources/schemas/xliff-core-1.2-strict.xsd @@ -3,16 +3,16 @@