Plugin Directory

Changeset 3369336


Ignore:
Timestamp:
09/28/2025 09:29:12 PM (6 months ago)
Author:
mihdan
Message:

Update to version 5.1.6 from GitHub

Location:
mihdan-no-external-links
Files:
2 added
52 edited
1 copied

Legend:

Unmodified
Added
Removed
  • mihdan-no-external-links/tags/5.1.6/admin/Admin.php

    r3096422 r3369336  
    155155     *
    156156     * @since    4.0.0
    157      */
    158     public function enqueue_scripts( $hook ): void {
     157     *
     158     * @param string $hook The current admin page.
     159     */
     160    public function enqueue_scripts( string $hook ): void {
    159161        wp_enqueue_script(
    160162            $this->plugin_name,
     
    436438        register_setting(
    437439            $this->plugin_name . '-settings',
    438             $this->options_prefix . 'masking_type'
     440            $this->options_prefix . 'masking_type',
     441            [
     442                'sanitize_callback' => 'sanitize_text_field',
     443            ]
    439444        );
    440445
    441446        register_setting(
    442447            $this->plugin_name . '-settings',
    443             $this->options_prefix . 'redirect_time'
     448            $this->options_prefix . 'redirect_time',
     449            [
     450                'sanitize_callback' => 'sanitize_text_field',
     451            ]
    444452        );
    445453
     
    455463        register_setting(
    456464            $this->plugin_name . '-settings',
    457             $this->options_prefix . 'mask_links'
     465            $this->options_prefix . 'mask_links',
     466            [
     467                'sanitize_callback' => 'sanitize_text_field',
     468            ]
    458469        );
    459470
    460471        register_setting(
    461472            $this->plugin_name . '-settings',
    462             $this->options_prefix . 'mask_posts_pages'
     473            $this->options_prefix . 'mask_posts_pages',
     474            [
     475                'sanitize_callback' => 'intval',
     476            ]
    463477        );
    464478
    465479        register_setting(
    466480            $this->plugin_name . '-settings',
    467             $this->options_prefix . 'mask_comments'
     481            $this->options_prefix . 'mask_comments',
     482            [
     483                'sanitize_callback' => 'intval',
     484            ]
    468485        );
    469486
    470487        register_setting(
    471488            $this->plugin_name . '-settings',
    472             $this->options_prefix . 'mask_comment_author'
     489            $this->options_prefix . 'mask_comment_author',
     490            [
     491                'sanitize_callback' => 'intval',
     492            ]
    473493        );
    474494
    475495        register_setting(
    476496            $this->plugin_name . '-settings',
    477             $this->options_prefix . 'mask_rss'
     497            $this->options_prefix . 'mask_rss',
     498            [
     499                'sanitize_callback' => 'intval',
     500            ]
    478501        );
    479502
    480503        register_setting(
    481504            $this->plugin_name . '-settings',
    482             $this->options_prefix . 'mask_rss_comments'
     505            $this->options_prefix . 'mask_rss_comments',
     506            [
     507                'sanitize_callback' => 'intval',
     508            ]
    483509        );
    484510
     
    494520        register_setting(
    495521            $this->plugin_name . '-settings',
    496             $this->options_prefix . 'nofollow'
     522            $this->options_prefix . 'nofollow',
     523            [
     524                'sanitize_callback' => 'intval',
     525            ]
    497526        );
    498527
    499528        register_setting(
    500529            $this->plugin_name . '-settings',
    501             $this->options_prefix . 'target_blank'
     530            $this->options_prefix . 'target_blank',
     531            [
     532                'sanitize_callback' => 'intval',
     533            ]
    502534        );
    503535
    504536        register_setting(
    505537            $this->plugin_name . '-settings',
    506             $this->options_prefix . 'noindex_tag'
     538            $this->options_prefix . 'noindex_tag',
     539            [
     540                'sanitize_callback' => 'intval',
     541            ]
    507542        );
    508543
    509544        register_setting(
    510545            $this->plugin_name . '-settings',
    511             $this->options_prefix . 'noindex_comment'
     546            $this->options_prefix . 'noindex_comment',
     547            [
     548                'sanitize_callback' => 'intval',
     549            ]
    512550        );
    513551
     
    528566        register_setting(
    529567            $this->plugin_name . '-settings-seo-hide',
    530             $this->options_prefix . 'seo_hide'
     568            $this->options_prefix . 'seo_hide',
     569            [
     570                'sanitize_callback' => 'intval',
     571            ]
    531572        );
    532573
     
    565606        register_setting(
    566607            $this->plugin_name . '-settings-seo-hide',
    567             $this->options_prefix . 'seo_hide_include_list'
     608            $this->options_prefix . 'seo_hide_include_list',
     609            [
     610                'sanitize_callback' => 'wp_kses_post',
     611            ]
    568612        );
    569613
     
    585629        register_setting(
    586630            $this->plugin_name . '-settings-seo-hide',
    587             $this->options_prefix . 'seo_hide_mode'
     631            $this->options_prefix . 'seo_hide_mode',
     632            [
     633                'sanitize_callback' => 'sanitize_text_field',
     634            ]
    588635        );
    589636
     
    607654        register_setting(
    608655            $this->plugin_name . '-settings-seo-hide',
    609             $this->options_prefix . 'seo_hide_exclude_list'
     656            $this->options_prefix . 'seo_hide_exclude_list',
     657            [
     658                'sanitize_callback' => [ $this, 'wp_kses_post' ],
     659            ]
    610660        );
    611661
     
    621671        register_setting(
    622672            $this->plugin_name . '-settings-advanced',
    623             $this->options_prefix . 'logging'
     673            $this->options_prefix . 'logging',
     674            [
     675                'sanitize_callback' => 'intval',
     676            ]
    624677        );
    625678
    626679        register_setting(
    627680            $this->plugin_name . '-settings-advanced',
    628             $this->options_prefix . 'log_duration'
     681            $this->options_prefix . 'log_duration',
     682            [
     683                'sanitize_callback' => 'intval',
     684            ]
    629685        );
    630686
     
    639695        register_setting(
    640696            $this->plugin_name . '-settings-advanced',
    641             $this->options_prefix . 'anonymize_links'
     697            $this->options_prefix . 'anonymize_links',
     698            [
     699                'sanitize_callback' => 'intval',
     700            ]
    642701        );
    643702
    644703        register_setting(
    645704            $this->plugin_name . '-settings-advanced',
    646             $this->options_prefix . 'anonymous_link_provider'
     705            $this->options_prefix . 'anonymous_link_provider',
     706            [
     707                'sanitize_callback' => 'sanitize_text_field',
     708            ]
    647709        );
    648710
     
    657719        register_setting(
    658720            $this->plugin_name . '-settings-advanced',
    659             $this->options_prefix . 'bot_targeting'
     721            $this->options_prefix . 'bot_targeting',
     722            [
     723                'sanitize_callback' => 'sanitize_text_field',
     724            ]
    660725        );
    661726
    662727        register_setting(
    663728            $this->plugin_name . '-settings-advanced',
    664             $this->options_prefix . 'bots_selector'
     729            $this->options_prefix . 'bots_selector',
     730            [
     731                'sanitize_callback' => [ $this, 'sanitize_text_array_deep' ],
     732            ]
    665733        );
    666734
     
    676744        register_setting(
    677745            $this->plugin_name . '-settings-advanced',
    678             $this->options_prefix . 'check_referrer'
     746            $this->options_prefix . 'check_referrer',
     747            [
     748                'sanitize_callback' => 'intval',
     749            ]
    679750        );
    680751
    681752        register_setting(
    682753            $this->plugin_name . '-settings-advanced',
    683             $this->options_prefix . 'remove_all_links'
     754            $this->options_prefix . 'remove_all_links',
     755            [
     756                'sanitize_callback' => 'intval',
     757            ]
    684758        );
    685759
    686760        register_setting(
    687761            $this->plugin_name . '-settings-advanced',
    688             $this->options_prefix . 'links_to_text'
     762            $this->options_prefix . 'links_to_text',
     763            [
     764                'sanitize_callback' => 'intval',
     765            ]
    689766        );
    690767
     
    700777        register_setting(
    701778            $this->plugin_name . '-settings-advanced',
    702             $this->options_prefix . 'debug_mode'
     779            $this->options_prefix . 'debug_mode',
     780            [
     781                'sanitize_callback' => 'intval',
     782            ]
    703783        );
    704784
     
    731811        register_setting(
    732812            $this->plugin_name . '-settings-links',
    733             $this->options_prefix . 'link_structure'
     813            $this->options_prefix . 'link_structure',
     814            [
     815                'sanitize_callback' => 'sanitize_text_field',
     816            ]
    734817        );
    735818
     
    801884        register_setting(
    802885            $this->plugin_name . '-settings-links',
    803             $this->options_prefix . 'link_encoding'
     886            $this->options_prefix . 'link_encoding',
     887            [
     888                'sanitize_callback' => 'sanitize_text_field',
     889            ]
    804890        );
    805891
     
    876962        register_setting(
    877963            $this->plugin_name . '-settings-links',
    878             $this->options_prefix . 'link_shortening'
     964            $this->options_prefix . 'link_shortening',
     965            [
     966                'sanitize_callback' => 'sanitize_text_field',
     967            ]
    879968        );
    880969
     
    9621051        register_setting(
    9631052            $this->plugin_name . '-settings-advanced',
    964             $this->options_prefix . 'redirect_message'
     1053            $this->options_prefix . 'redirect_message',
     1054            [
     1055                'sanitize_callback' => 'wp_kses_post',
     1056            ]
    9651057        );
    9661058
     
    9761068        register_setting(
    9771069            $this->plugin_name . '-settings-advanced',
    978             $this->options_prefix . 'redirect_page'
     1070            $this->options_prefix . 'redirect_page',
     1071            [
     1072                'sanitize_callback' => 'intval',
     1073            ]
    9791074        );
    9801075
     
    9951090        register_setting(
    9961091            $this->plugin_name . '-settings-include-exclude',
    997             $this->options_prefix . 'inclusion_list'
     1092            $this->options_prefix . 'inclusion_list',
     1093            [
     1094                'sanitize_callback' => 'wp_kses_post',
     1095            ]
    9981096        );
    9991097
     
    10141112        register_setting(
    10151113            $this->plugin_name . '-settings-include-exclude',
    1016             $this->options_prefix . 'exclusion_list'
     1114            $this->options_prefix . 'exclusion_list',
     1115            [
     1116                'sanitize_callback' => 'wp_kses_post',
     1117            ]
    10171118        );
    10181119
     
    10331134        register_setting(
    10341135            $this->plugin_name . '-settings-include-exclude',
    1035             $this->options_prefix . 'skip_auth'
     1136            $this->options_prefix . 'skip_auth',
     1137            [
     1138                'sanitize_callback' => 'intval',
     1139            ]
    10361140        );
    10371141
     
    10521156        register_setting(
    10531157            $this->plugin_name . '-settings-include-exclude',
    1054             $this->options_prefix . 'skip_follow'
     1158            $this->options_prefix . 'skip_follow',
     1159            [
     1160                'sanitize_callback' => 'intval',
     1161            ]
    10551162        );
    10561163
     
    22502357        return $actions;
    22512358    }
     2359
     2360    /**
     2361     * Sanitizes an array of strings from user input or from the database.
     2362     *
     2363     * @param array $array Array to sanitize.
     2364     *
     2365     * @return array
     2366     */
     2367    public function sanitize_text_array_deep( array $array ): array {
     2368        return array_map( 'sanitize_text_field', $array );
     2369    }
    22522370}
  • mihdan-no-external-links/tags/5.1.6/admin/LogTable.php

    r2783436 r3369336  
    9999                    'delete' => sprintf(
    100100                        '<a href="?page=%s&action=%s&log=%s&_wpnonce=%s">Delete</a>',
    101                         isset( $_REQUEST['page'] ) ? absint( $_REQUEST['page'] ) : 1,
     101                        isset( $_REQUEST['page'] ) ? sanitize_text_field( $_REQUEST['page'] ) : 1,
    102102                        'delete',
    103103                        absint( $item['id'] ),
     
    294294            : '';
    295295
    296         if ( ! empty( $nonce ) && ! wp_verify_nonce( $nonce, $this->options_prefix . 'delete_log' ) ) {
    297             wp_die( esc_html__( 'Are you sure you want to do this?' ) );
    298         }
    299 
    300         if ( 'delete' === $this->current_action() ) {
     296        if ( 'delete' === $this->current_action() && wp_verify_nonce( $nonce, $this->options_prefix . 'delete_log' ) ) {
    301297
    302298            $delete_count = 0;
     
    312308        }
    313309
    314         if (
    315             ( isset( $_POST['action'] ) && 'bulk-delete' === $_POST['action'] ) ||
    316             ( isset( $_POST['action2'] ) && 'bulk-delete' === $_POST['action2'] )
    317         ) {
     310        if ( 'bulk-delete' === $this->current_action() && wp_verify_nonce( $nonce, 'bulk-' . $this->_args['plural'] ) ) {
    318311
    319312            $delete_ids = ! empty( $_POST['bulk-delete'] )
    320                 ? array_map( 'sanitize_text_field', wp_unslash( $_POST['bulk-delete'] ) )
     313                ? array_map( 'intval', wp_unslash( $_POST['bulk-delete'] ) )
    321314                : [];
    322315
     
    335328            exit;
    336329        }
    337 
    338330    }
    339331
  • mihdan-no-external-links/tags/5.1.6/admin/MaskTable.php

    r2783436 r3369336  
    241241            'cb'      => '<input type="checkbox" />',
    242242            'title'   => __( 'URL', $this->plugin_name ),
    243             'mask'    => __( 'Mask' ),
    244             'numeric' => __( 'Numeric' ),
     243            'mask'    => __( 'Mask', $this->plugin_name ),
     244            'numeric' => __( 'Numeric', $this->plugin_name ),
    245245        ];
    246246
     
    317317
    318318        if ( ! empty( $nonce ) && ! wp_verify_nonce( $nonce, $this->options_prefix . 'delete_mask' ) ) {
    319             wp_die( esc_html__( 'Are you sure you want to do this?' ) );
     319            wp_die( esc_html__( 'Are you sure you want to do this?', $this->plugin_name ) );
    320320        }
    321321
  • mihdan-no-external-links/tags/5.1.6/admin/SiteHealth.php

    r2782193 r3369336  
    7575            'status'      => 'good',
    7676            'badge'       => [
    77                 'label' => __( 'Performance' ),
     77                'label' => __( 'Performance', $this->plugin_name ),
    7878                'color' => 'blue',
    7979            ],
     
    8686                'status'      => 'critical',
    8787                'badge'       => [
    88                     'label' => __( 'Performance' ),
     88                    'label' => __( 'Performance', $this->plugin_name ),
    8989                    'color' => 'red',
    9090                ],
  • mihdan-no-external-links/tags/5.1.6/includes/Main.php

    r3007441 r3369336  
    116116        $this->define_admin_hooks();
    117117        $this->define_public_hooks();
    118 
    119118    }
    120119
     
    264263        $plugin_i18n = new I18n( $this->get_plugin_name() );
    265264
    266         $this->loader->add_action( 'plugins_loaded', $plugin_i18n, 'load_plugin_textdomain' );
     265        $this->loader->add_action( 'init', $plugin_i18n, 'load_plugin_textdomain' );
    267266
    268267    }
     
    342341            'skip_follow'             => false,
    343342            'redirect_page'           => 0,
    344             'redirect_message'        => __(
    345                 'You will be redirected in 3 seconds. If your browser does not automatically redirect you, please <a href="%linkurl%">click here</a>.',
    346                 $this->plugin_name
    347             ),
     343            'redirect_message'        => 'You will be redirected in 3 seconds. If your browser does not automatically redirect you, please <a href="%linkurl%">click here</a>.',
    348344            'output_buffer'           => $output_buffer,
    349345        );
    350346
    351347        $this->options = $this->validate_options( $options );
    352 
    353348    }
    354349
     
    547542
    548543        $this->loader->add_filter( 'set-screen-option', $this->admin, 'mask_page_set_screen_options', null, 3 );
    549         $hook_name = vsprintf(
    550             'load-%s_page_%s-masks',
    551             [ strtolower( sanitize_file_name( __( 'No External Links', $this->plugin_name ) ) ), $this->get_plugin_name() ]
    552         );
     544
     545        $hook_name = sprintf( 'load-no-external-links_page_%s-masks', $this->get_plugin_name() );
    553546
    554547        $this->loader->add_action( $hook_name, $this->admin, 'mask_page_screen_options' );
     
    556549        $this->loader->add_filter( 'set-screen-option', $this->admin, 'log_page_set_screen_options', null, 3 );
    557550
    558         $hook_name = vsprintf(
    559             'load-%s_page_%s-logs',
    560             [ strtolower( sanitize_file_name( __( 'No External Links', $this->plugin_name ) ) ), $this->get_plugin_name() ]
    561         );
     551        $hook_name = sprintf( 'load-no-external-links_page_%s-logs', $this->get_plugin_name() );
    562552
    563553        $this->loader->add_action( $hook_name, $this->admin, 'log_page_screen_options' );
  • mihdan-no-external-links/tags/5.1.6/mihdan-no-external-links.php

    r3369209 r3369336  
    1111 * Plugin URI:        https://www.kobzarev.com/projects/no-external-links/
    1212 * Description:       Convert external links into internal links, site wide or post/page specific. Add NoFollow, Click logging, and more...
    13  * Version:           5.1.5
     13 * Version:           5.1.6
    1414 * Author:            Mikhail Kobzarev
    1515 * Author URI:        https://www.kobzarev.com/
     
    2929
    3030const MIHDAN_NO_EXTERNAL_LINKS_DIR     = __DIR__;
    31 const MIHDAN_NO_EXTERNAL_LINKS_VERSION = '5.1.5';
     31const MIHDAN_NO_EXTERNAL_LINKS_VERSION = '5.1.6';
    3232const MIHDAN_NO_EXTERNAL_LINKS_SLUG    = 'mihdan-no-external-links';
    3333
  • mihdan-no-external-links/tags/5.1.6/readme.txt

    r3369209 r3369336  
    55Requires at least: 5.7.4
    66Tested up to: 6.8
    7 Stable tag: 5.1.5
     7Stable tag: 5.1.6
    88Requires PHP: 7.4
    99License: GPLv2 or later
     
    6363
    6464== Changelog ==
     65
     66= 5.1.6 (28.09.2025) =
     67* Resolved #[39](https://github.com/mihdan/mihdan-no-external-links/issues/39)
     68* Resolved #[40](https://github.com/mihdan/mihdan-no-external-links/issues/40)
     69* The error "Function _load_textdomain_just_in_time was called incorrectly" has been fixed
    6570
    6671= 5.1.5 (28.09.2025) =
  • mihdan-no-external-links/tags/5.1.6/vendor/composer/installed.json

    r2900866 r3369336  
    197197        {
    198198            "name": "phpseclib/phpseclib",
    199             "version": "3.0.19",
    200             "version_normalized": "3.0.19.0",
     199            "version": "3.0.34",
     200            "version_normalized": "3.0.34.0",
    201201            "source": {
    202202                "type": "git",
    203203                "url": "https://github.com/phpseclib/phpseclib.git",
    204                 "reference": "cc181005cf548bfd8a4896383bb825d859259f95"
     204                "reference": "56c79f16a6ae17e42089c06a2144467acc35348a"
    205205            },
    206206            "dist": {
    207207                "type": "zip",
    208                 "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/cc181005cf548bfd8a4896383bb825d859259f95",
    209                 "reference": "cc181005cf548bfd8a4896383bb825d859259f95",
     208                "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/56c79f16a6ae17e42089c06a2144467acc35348a",
     209                "reference": "56c79f16a6ae17e42089c06a2144467acc35348a",
    210210                "shasum": ""
    211211            },
     
    225225                "ext-openssl": "Install the OpenSSL extension in order to speed up a wide variety of cryptographic operations."
    226226            },
    227             "time": "2023-03-05T17:13:09+00:00",
     227            "time": "2023-11-27T11:13:31+00:00",
    228228            "type": "library",
    229229            "installation-source": "dist",
     
    290290            "support": {
    291291                "issues": "https://github.com/phpseclib/phpseclib/issues",
    292                 "source": "https://github.com/phpseclib/phpseclib/tree/3.0.19"
     292                "source": "https://github.com/phpseclib/phpseclib/tree/3.0.34"
    293293            },
    294294            "funding": [
  • mihdan-no-external-links/tags/5.1.6/vendor/composer/installed.php

    r3369209 r3369336  
    22    'root' => array(
    33        'name' => 'mihdan/mihdan-no-external-links',
    4         'pretty_version' => '5.1.5',
    5         'version' => '5.1.5.0',
    6         'reference' => 'a6d16d367a6ceb1bbe468219125173021c653f99',
     4        'pretty_version' => '5.1.6',
     5        'version' => '5.1.6.0',
     6        'reference' => 'eda3a90f6434e53d5238700433c3b2aa66195b62',
    77        'type' => 'wordpress-plugin',
    88        'install_path' => __DIR__ . '/../../',
     
    1212    'versions' => array(
    1313        'mihdan/mihdan-no-external-links' => array(
    14             'pretty_version' => '5.1.5',
    15             'version' => '5.1.5.0',
    16             'reference' => 'a6d16d367a6ceb1bbe468219125173021c653f99',
     14            'pretty_version' => '5.1.6',
     15            'version' => '5.1.6.0',
     16            'reference' => 'eda3a90f6434e53d5238700433c3b2aa66195b62',
    1717            'type' => 'wordpress-plugin',
    1818            'install_path' => __DIR__ . '/../../',
     
    4848        ),
    4949        'phpseclib/phpseclib' => array(
    50             'pretty_version' => '3.0.19',
    51             'version' => '3.0.19.0',
    52             'reference' => 'cc181005cf548bfd8a4896383bb825d859259f95',
     50            'pretty_version' => '3.0.34',
     51            'version' => '3.0.34.0',
     52            'reference' => '56c79f16a6ae17e42089c06a2144467acc35348a',
    5353            'type' => 'library',
    5454            'install_path' => __DIR__ . '/../phpseclib/phpseclib',
  • mihdan-no-external-links/tags/5.1.6/vendor/phpseclib/phpseclib/BACKERS.md

    r2900866 r3369336  
    1414- Tharyrok
    1515- [cjhaas](https://github.com/cjhaas)
     16- [istiak-tridip](https://github.com/istiak-tridip)
  • mihdan-no-external-links/tags/5.1.6/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/AsymmetricKey.php

    r2900866 r3369336  
    131131     * @param string $key
    132132     * @param string $password optional
    133      * @return AsymmetricKey
     133     * @return \phpseclib3\Crypt\Common\PublicKey|\phpseclib3\Crypt\Common\PrivateKey
    134134     */
    135135    public static function load($key, $password = false)
  • mihdan-no-external-links/tags/5.1.6/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/Formats/Keys/PKCS8.php

    r2900866 r3369336  
    142142            case 'RC2':
    143143                $cipher = new RC2('cbc');
     144                $cipher->setKeyLength(64);
    144145                break;
    145146            case '3-KeyTripleDES':
     
    219220        switch ($algo) {
    220221            case 'desCBC':
    221                 $cipher = new TripleDES('cbc');
     222                $cipher = new DES('cbc');
    222223                break;
    223224            case 'des-EDE3-CBC':
  • mihdan-no-external-links/tags/5.1.6/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/SymmetricKey.php

    r2900866 r3369336  
    669669                // PHP_OS & "\xDF\xDF\xDF" == strtoupper(substr(PHP_OS, 0, 3)), but a lot faster
    670670                case (PHP_OS & "\xDF\xDF\xDF") === 'WIN':
    671                 case (php_uname('m') & "\xDF\xDF\xDF") != 'ARM':
     671                case !(is_string(php_uname('m')) && (php_uname('m') & "\xDF\xDF\xDF") == 'ARM'):
    672672                case defined('PHP_INT_SIZE') && PHP_INT_SIZE == 8:
    673673                    self::$use_reg_intval = true;
    674674                    break;
    675                 case (php_uname('m') & "\xDF\xDF\xDF") == 'ARM':
     675                case is_string(php_uname('m')) && (php_uname('m') & "\xDF\xDF\xDF") == 'ARM':
    676676                    switch (true) {
    677677                        /* PHP 7.0.0 introduced a bug that affected 32-bit ARM processors:
     
    918918     * @param string $password
    919919     * @param string $method
    920      * @param string[] ...$func_args
     920     * @param int|string ...$func_args
    921921     * @throws \LengthException if pbkdf1 is being used and the derived key length exceeds the hash length
    922922     * @throws \RuntimeException if bcrypt is being used and a salt isn't provided
  • mihdan-no-external-links/tags/5.1.6/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php

    r2900866 r3369336  
    842842            self::ENCRYPTION_NONE
    843843        ];
    844         $numSelected = 0;
     844        $encryptedCount = 0;
    845845        $selected = 0;
    846846        foreach ($masks as $mask) {
    847847            if ($padding & $mask) {
    848848                $selected = $mask;
    849                 $numSelected++;
     849                $encryptedCount++;
    850850            }
    851851        }
    852         if ($numSelected > 1) {
     852        if ($encryptedCount > 1) {
    853853            throw new InconsistentSetupException('Multiple encryption padding modes have been selected; at most only one should be selected');
    854854        }
     
    860860            self::SIGNATURE_PKCS1
    861861        ];
    862         $numSelected = 0;
     862        $signatureCount = 0;
    863863        $selected = 0;
    864864        foreach ($masks as $mask) {
    865865            if ($padding & $mask) {
    866866                $selected = $mask;
    867                 $numSelected++;
     867                $signatureCount++;
    868868            }
    869869        }
    870         if ($numSelected > 1) {
     870        if ($signatureCount > 1) {
    871871            throw new InconsistentSetupException('Multiple signature padding modes have been selected; at most only one should be selected');
    872872        }
     
    874874
    875875        $new = clone $this;
    876         $new->encryptionPadding = $encryptionPadding;
    877         $new->signaturePadding = $signaturePadding;
     876        if ($encryptedCount) {
     877            $new->encryptionPadding = $encryptionPadding;
     878        }
     879        if ($signatureCount) {
     880            $new->signaturePadding = $signaturePadding;
     881        }
    878882        return $new;
    879883    }
  • mihdan-no-external-links/tags/5.1.6/vendor/phpseclib/phpseclib/phpseclib/Crypt/Rijndael.php

    r2782199 r3369336  
    834834        // Generating encrypt code:
    835835        $init_encrypt .= '
    836             static $tables;
    837836            if (empty($tables)) {
    838837                $tables = &$this->getTables();
     
    891890        // Generating decrypt code:
    892891        $init_decrypt .= '
    893             static $invtables;
    894892            if (empty($invtables)) {
    895893                $invtables = &$this->getInvTables();
     
    948946        $this->inline_crypt = $this->createInlineCryptFunction(
    949947            [
    950                'init_crypt'    => '',
     948               'init_crypt'    => 'static $tables; static $invtables;',
    951949               'init_encrypt'  => $init_encrypt,
    952950               'init_decrypt'  => $init_decrypt,
  • mihdan-no-external-links/tags/5.1.6/vendor/phpseclib/phpseclib/phpseclib/File/ASN1.php

    r2782199 r3369336  
    2222namespace phpseclib3\File;
    2323
    24 use DateTime;
    2524use phpseclib3\Common\Functions\Strings;
    2625use phpseclib3\File\ASN1\Element;
     
    206205        }
    207206
    208         return [self::decode_ber($encoded)];
     207        return [$decoded];
    209208    }
    210209
     
    14041403                    }
    14051404                    break;
    1406                 case ($c & 0x80000000) != 0:
     1405                case ($c & (PHP_INT_SIZE == 8 ? 0x80000000 : (1 << 31))) != 0:
    14071406                    return false;
    14081407                case $c >= 0x04000000:
  • mihdan-no-external-links/tags/5.1.6/vendor/phpseclib/phpseclib/phpseclib/File/X509.php

    r2900866 r3369336  
    165165     * @var array
    166166     */
    167     private $CAs;
     167    private $CAs = [];
    168168
    169169    /**
     
    316316                'id-at-role' => '2.5.4.72',
    317317                'id-at-postalAddress' => '2.5.4.16',
     318                'jurisdictionOfIncorporationCountryName' => '1.3.6.1.4.1.311.60.2.1.3',
     319                'jurisdictionOfIncorporationStateOrProvinceName' => '1.3.6.1.4.1.311.60.2.1.2',
     320                'jurisdictionLocalityName' => '1.3.6.1.4.1.311.60.2.1.1',
     321                'id-at-businessCategory' => '2.5.4.15',
    318322
    319323                //'id-domainComponent' => '0.9.2342.19200300.100.1.25',
     
    10391043            foreach ($names as $name) {
    10401044                foreach ($name as $key => $value) {
    1041                     $value = str_replace(['.', '*'], ['\.', '[^.]*'], $value);
     1045                    $value = preg_quote($value);
     1046                    $value = str_replace('\*', '[^.]*', $value);
    10421047                    switch ($key) {
    10431048                        case 'dNSName':
     
    15391544    {
    15401545        switch (strtolower($propName)) {
     1546            case 'jurisdictionofincorporationcountryname':
     1547            case 'jurisdictioncountryname':
     1548            case 'jurisdictionc':
     1549                return 'jurisdictionOfIncorporationCountryName';
     1550            case 'jurisdictionofincorporationstateorprovincename':
     1551            case 'jurisdictionstateorprovincename':
     1552            case 'jurisdictionst':
     1553                return 'jurisdictionOfIncorporationStateOrProvinceName';
     1554            case 'jurisdictionlocalityname':
     1555            case 'jurisdictionl':
     1556                return 'jurisdictionLocalityName';
     1557            case 'id-at-businesscategory':
     1558            case 'businesscategory':
     1559                return 'id-at-businessCategory';
    15411560            case 'id-at-countryname':
    15421561            case 'countryname':
     
    20312050            return false;
    20322051        }
    2033         if (empty($this->CAs)) {
    2034             return $chain;
    2035         }
    20362052        while (true) {
    20372053            $currentCert = $chain[count($chain) - 1];
  • mihdan-no-external-links/tags/5.1.6/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger.php

    r2900866 r3369336  
    101101        self::$mainEngine = $fqmain;
    102102
    103         if (!in_array('Default', $modexps)) {
    104             $modexps[] = 'DefaultEngine';
    105         }
    106 
    107103        $found = false;
    108104        foreach ($modexps as $modexp) {
     
    141137        if (!isset(self::$mainEngine)) {
    142138            $engines = [
    143                 ['GMP'],
     139                ['GMP', ['DefaultEngine']],
    144140                ['PHP64', ['OpenSSL']],
    145141                ['BCMath', ['OpenSSL']],
    146                 ['PHP32', ['OpenSSL']]
     142                ['PHP32', ['OpenSSL']],
     143                ['PHP64', ['DefaultEngine']],
     144                ['PHP32', ['DefaultEngine']]
    147145            ];
     146
    148147            foreach ($engines as $engine) {
    149148                try {
    150                     self::setEngine($engine[0], isset($engine[1]) ? $engine[1] : []);
    151                     break;
     149                    self::setEngine($engine[0], $engine[1]);
     150                    return;
    152151                } catch (\Exception $e) {
    153152                }
    154153            }
     154
     155            throw new \UnexpectedValueException('No valid BigInteger found. This is only possible when JIT is enabled on Windows and neither the GMP or BCMath extensions are available so either disable JIT or install GMP / BCMath');
    155156        }
    156157    }
  • mihdan-no-external-links/tags/5.1.6/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/Engine.php

    r2900866 r3369336  
    645645        }
    646646
     647        if ($this->compare($n) > 0) {
     648            list(, $temp) = $this->divide($n);
     649            return $temp->powModInner($e, $n);
     650        }
     651
    647652        return $this->powModInner($e, $n);
    648653    }
  • mihdan-no-external-links/tags/5.1.6/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP.php

    r2782199 r3369336  
    13271327        return array_reverse($vals);
    13281328    }
     1329
     1330    /**
     1331     * @return bool
     1332     */
     1333    protected static function testJITOnWindows()
     1334    {
     1335        // see https://github.com/php/php-src/issues/11917
     1336        if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN' && function_exists('opcache_get_status') && PHP_VERSION_ID < 80213 && !defined('PHPSECLIB_ALLOW_JIT')) {
     1337            $status = opcache_get_status();
     1338            if ($status && isset($status['jit']) && $status['jit']['enabled'] && $status['jit']['on']) {
     1339                return true;
     1340            }
     1341        }
     1342        return false;
     1343    }
    13291344}
  • mihdan-no-external-links/tags/5.1.6/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP32.php

    r2782199 r3369336  
    8181            $step = count($vals) & 3;
    8282            if ($step) {
    83                 $digit = floor($digit / pow(2, 2 * $step));
     83                $digit = (int) floor($digit / pow(2, 2 * $step));
    8484            }
    8585            if ($step != 3) {
    86                 $digit &= static::MAX_DIGIT;
     86                $digit = (int) fmod($digit, static::BASE_FULL);
    8787                $i++;
    8888            }
     
    103103    public static function isValidEngine()
    104104    {
    105         return PHP_INT_SIZE >= 4;
     105        return PHP_INT_SIZE >= 4 && !self::testJITOnWindows();
    106106    }
    107107
  • mihdan-no-external-links/tags/5.1.6/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP64.php

    r2782199 r3369336  
    104104    public static function isValidEngine()
    105105    {
    106         return PHP_INT_SIZE >= 8;
     106        return PHP_INT_SIZE >= 8 && !self::testJITOnWindows();
    107107    }
    108108
  • mihdan-no-external-links/tags/5.1.6/vendor/phpseclib/phpseclib/phpseclib/Math/BinaryField.php

    r2782199 r3369336  
    4949    {
    5050        $m = array_shift($indices);
     51        if ($m > 571) {
     52            /* sect571r1 and sect571k1 are the largest binary curves that https://www.secg.org/sec2-v2.pdf defines
     53               altho theoretically there may be legit reasons to use binary finite fields with larger degrees
     54               imposing a limit on the maximum size is both reasonable and precedented. in particular,
     55               http://tools.ietf.org/html/rfc4253#section-6.1 (The Secure Shell (SSH) Transport Layer Protocol) says
     56               "implementations SHOULD check that the packet length is reasonable in order for the implementation to
     57                avoid denial of service and/or buffer overflow attacks" */
     58            throw new \OutOfBoundsException('Degrees larger than 571 are not supported');
     59        }
    5160        $val = str_repeat('0', $m) . '1';
    5261        foreach ($indices as $index) {
  • mihdan-no-external-links/tags/5.1.6/vendor/phpseclib/phpseclib/phpseclib/Math/PrimeField/Integer.php

    r2900866 r3369336  
    264264
    265265        while (!$t->equals($one)) {
    266             for ($i == clone $one; $i->compare($m) < 0; $i = $i->add($one)) {
     266            for ($i = clone $one; $i->compare($m) < 0; $i = $i->add($one)) {
    267267                if ($t->powMod($two->pow($i), static::$modulo[$this->instanceID])->equals($one)) {
    268268                    break;
     
    313313    public function toBytes()
    314314    {
    315         $length = static::$modulo[$this->instanceID]->getLengthInBytes();
    316         return str_pad($this->value->toBytes(), $length, "\0", STR_PAD_LEFT);
     315        if (isset(static::$modulo[$this->instanceID])) {
     316            $length = static::$modulo[$this->instanceID]->getLengthInBytes();
     317            return str_pad($this->value->toBytes(), $length, "\0", STR_PAD_LEFT);
     318        }
     319        return $this->value->toBytes();
    317320    }
    318321
  • mihdan-no-external-links/tags/5.1.6/vendor/phpseclib/phpseclib/phpseclib/Net/SFTP.php

    r2900866 r3369336  
    9494     * @access private
    9595     */
    96     private $packet_types = [];
     96    private static $packet_types = [];
    9797
    9898    /**
     
    103103     * @access private
    104104     */
    105     private $status_codes = [];
     105    private static $status_codes = [];
    106106
    107107    /** @var array<int, string> */
    108     private $attributes;
     108    private static $attributes;
    109109
    110110    /** @var array<int, string> */
    111     private $open_flags;
     111    private static $open_flags;
    112112
    113113    /** @var array<int, string> */
    114     private $open_flags5;
     114    private static $open_flags5;
    115115
    116116    /** @var array<int, string> */
    117     private $file_types;
     117    private static $file_types;
    118118
    119119    /**
     
    351351     * Connects to an SFTP server
    352352     *
    353      * @param string $host
     353     * $host can either be a string, representing the host, or a stream resource.
     354     *
     355     * @param mixed $host
    354356     * @param int $port
    355357     * @param int $timeout
     
    361363        $this->max_sftp_packet = 1 << 15;
    362364
    363         $this->packet_types = [
    364             1  => 'NET_SFTP_INIT',
    365             2  => 'NET_SFTP_VERSION',
    366             3  => 'NET_SFTP_OPEN',
    367             4  => 'NET_SFTP_CLOSE',
    368             5  => 'NET_SFTP_READ',
    369             6  => 'NET_SFTP_WRITE',
    370             7  => 'NET_SFTP_LSTAT',
    371             9  => 'NET_SFTP_SETSTAT',
    372             10 => 'NET_SFTP_FSETSTAT',
    373             11 => 'NET_SFTP_OPENDIR',
    374             12 => 'NET_SFTP_READDIR',
    375             13 => 'NET_SFTP_REMOVE',
    376             14 => 'NET_SFTP_MKDIR',
    377             15 => 'NET_SFTP_RMDIR',
    378             16 => 'NET_SFTP_REALPATH',
    379             17 => 'NET_SFTP_STAT',
    380             18 => 'NET_SFTP_RENAME',
    381             19 => 'NET_SFTP_READLINK',
    382             20 => 'NET_SFTP_SYMLINK',
    383             21 => 'NET_SFTP_LINK',
    384 
    385             101 => 'NET_SFTP_STATUS',
    386             102 => 'NET_SFTP_HANDLE',
    387             103 => 'NET_SFTP_DATA',
    388             104 => 'NET_SFTP_NAME',
    389             105 => 'NET_SFTP_ATTRS',
    390 
    391             200 => 'NET_SFTP_EXTENDED'
    392         ];
    393         $this->status_codes = [
    394             0 => 'NET_SFTP_STATUS_OK',
    395             1 => 'NET_SFTP_STATUS_EOF',
    396             2 => 'NET_SFTP_STATUS_NO_SUCH_FILE',
    397             3 => 'NET_SFTP_STATUS_PERMISSION_DENIED',
    398             4 => 'NET_SFTP_STATUS_FAILURE',
    399             5 => 'NET_SFTP_STATUS_BAD_MESSAGE',
    400             6 => 'NET_SFTP_STATUS_NO_CONNECTION',
    401             7 => 'NET_SFTP_STATUS_CONNECTION_LOST',
    402             8 => 'NET_SFTP_STATUS_OP_UNSUPPORTED',
    403             9 => 'NET_SFTP_STATUS_INVALID_HANDLE',
    404             10 => 'NET_SFTP_STATUS_NO_SUCH_PATH',
    405             11 => 'NET_SFTP_STATUS_FILE_ALREADY_EXISTS',
    406             12 => 'NET_SFTP_STATUS_WRITE_PROTECT',
    407             13 => 'NET_SFTP_STATUS_NO_MEDIA',
    408             14 => 'NET_SFTP_STATUS_NO_SPACE_ON_FILESYSTEM',
    409             15 => 'NET_SFTP_STATUS_QUOTA_EXCEEDED',
    410             16 => 'NET_SFTP_STATUS_UNKNOWN_PRINCIPAL',
    411             17 => 'NET_SFTP_STATUS_LOCK_CONFLICT',
    412             18 => 'NET_SFTP_STATUS_DIR_NOT_EMPTY',
    413             19 => 'NET_SFTP_STATUS_NOT_A_DIRECTORY',
    414             20 => 'NET_SFTP_STATUS_INVALID_FILENAME',
    415             21 => 'NET_SFTP_STATUS_LINK_LOOP',
    416             22 => 'NET_SFTP_STATUS_CANNOT_DELETE',
    417             23 => 'NET_SFTP_STATUS_INVALID_PARAMETER',
    418             24 => 'NET_SFTP_STATUS_FILE_IS_A_DIRECTORY',
    419             25 => 'NET_SFTP_STATUS_BYTE_RANGE_LOCK_CONFLICT',
    420             26 => 'NET_SFTP_STATUS_BYTE_RANGE_LOCK_REFUSED',
    421             27 => 'NET_SFTP_STATUS_DELETE_PENDING',
    422             28 => 'NET_SFTP_STATUS_FILE_CORRUPT',
    423             29 => 'NET_SFTP_STATUS_OWNER_INVALID',
    424             30 => 'NET_SFTP_STATUS_GROUP_INVALID',
    425             31 => 'NET_SFTP_STATUS_NO_MATCHING_BYTE_RANGE_LOCK'
    426         ];
    427         // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-7.1
    428         // the order, in this case, matters quite a lot - see \phpseclib3\Net\SFTP::_parseAttributes() to understand why
    429         $this->attributes = [
    430             0x00000001 => 'NET_SFTP_ATTR_SIZE',
    431             0x00000002 => 'NET_SFTP_ATTR_UIDGID',          // defined in SFTPv3, removed in SFTPv4+
    432             0x00000080 => 'NET_SFTP_ATTR_OWNERGROUP',      // defined in SFTPv4+
    433             0x00000004 => 'NET_SFTP_ATTR_PERMISSIONS',
    434             0x00000008 => 'NET_SFTP_ATTR_ACCESSTIME',
    435             0x00000010 => 'NET_SFTP_ATTR_CREATETIME',      // SFTPv4+
    436             0x00000020 => 'NET_SFTP_ATTR_MODIFYTIME',
    437             0x00000040 => 'NET_SFTP_ATTR_ACL',
    438             0x00000100 => 'NET_SFTP_ATTR_SUBSECOND_TIMES',
    439             0x00000200 => 'NET_SFTP_ATTR_BITS',            // SFTPv5+
    440             0x00000400 => 'NET_SFTP_ATTR_ALLOCATION_SIZE', // SFTPv6+
    441             0x00000800 => 'NET_SFTP_ATTR_TEXT_HINT',
    442             0x00001000 => 'NET_SFTP_ATTR_MIME_TYPE',
    443             0x00002000 => 'NET_SFTP_ATTR_LINK_COUNT',
    444             0x00004000 => 'NET_SFTP_ATTR_UNTRANSLATED_NAME',
    445             0x00008000 => 'NET_SFTP_ATTR_CTIME',
    446             // 0x80000000 will yield a floating point on 32-bit systems and converting floating points to integers
    447             // yields inconsistent behavior depending on how php is compiled.  so we left shift -1 (which, in
    448             // two's compliment, consists of all 1 bits) by 31.  on 64-bit systems this'll yield 0xFFFFFFFF80000000.
    449             // that's not a problem, however, and 'anded' and a 32-bit number, as all the leading 1 bits are ignored.
    450             (PHP_INT_SIZE == 4 ? -1 : 0xFFFFFFFF) => 'NET_SFTP_ATTR_EXTENDED'
    451         ];
    452         // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-6.3
    453         // the flag definitions change somewhat in SFTPv5+.  if SFTPv5+ support is added to this library, maybe name
    454         // the array for that $this->open5_flags and similarly alter the constant names.
    455         $this->open_flags = [
    456             0x00000001 => 'NET_SFTP_OPEN_READ',
    457             0x00000002 => 'NET_SFTP_OPEN_WRITE',
    458             0x00000004 => 'NET_SFTP_OPEN_APPEND',
    459             0x00000008 => 'NET_SFTP_OPEN_CREATE',
    460             0x00000010 => 'NET_SFTP_OPEN_TRUNCATE',
    461             0x00000020 => 'NET_SFTP_OPEN_EXCL',
    462             0x00000040 => 'NET_SFTP_OPEN_TEXT' // defined in SFTPv4
    463         ];
    464         // SFTPv5+ changed the flags up:
    465         // https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-13#section-8.1.1.3
    466         $this->open_flags5 = [
    467             // when SSH_FXF_ACCESS_DISPOSITION is a 3 bit field that controls how the file is opened
    468             0x00000000 => 'NET_SFTP_OPEN_CREATE_NEW',
    469             0x00000001 => 'NET_SFTP_OPEN_CREATE_TRUNCATE',
    470             0x00000002 => 'NET_SFTP_OPEN_OPEN_EXISTING',
    471             0x00000003 => 'NET_SFTP_OPEN_OPEN_OR_CREATE',
    472             0x00000004 => 'NET_SFTP_OPEN_TRUNCATE_EXISTING',
    473             // the rest of the flags are not supported
    474             0x00000008 => 'NET_SFTP_OPEN_APPEND_DATA', // "the offset field of SS_FXP_WRITE requests is ignored"
    475             0x00000010 => 'NET_SFTP_OPEN_APPEND_DATA_ATOMIC',
    476             0x00000020 => 'NET_SFTP_OPEN_TEXT_MODE',
    477             0x00000040 => 'NET_SFTP_OPEN_BLOCK_READ',
    478             0x00000080 => 'NET_SFTP_OPEN_BLOCK_WRITE',
    479             0x00000100 => 'NET_SFTP_OPEN_BLOCK_DELETE',
    480             0x00000200 => 'NET_SFTP_OPEN_BLOCK_ADVISORY',
    481             0x00000400 => 'NET_SFTP_OPEN_NOFOLLOW',
    482             0x00000800 => 'NET_SFTP_OPEN_DELETE_ON_CLOSE',
    483             0x00001000 => 'NET_SFTP_OPEN_ACCESS_AUDIT_ALARM_INFO',
    484             0x00002000 => 'NET_SFTP_OPEN_ACCESS_BACKUP',
    485             0x00004000 => 'NET_SFTP_OPEN_BACKUP_STREAM',
    486             0x00008000 => 'NET_SFTP_OPEN_OVERRIDE_OWNER',
    487         ];
    488         // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-5.2
    489         // see \phpseclib3\Net\SFTP::_parseLongname() for an explanation
    490         $this->file_types = [
    491             1 => 'NET_SFTP_TYPE_REGULAR',
    492             2 => 'NET_SFTP_TYPE_DIRECTORY',
    493             3 => 'NET_SFTP_TYPE_SYMLINK',
    494             4 => 'NET_SFTP_TYPE_SPECIAL',
    495             5 => 'NET_SFTP_TYPE_UNKNOWN',
    496             // the following types were first defined for use in SFTPv5+
    497             // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-05#section-5.2
    498             6 => 'NET_SFTP_TYPE_SOCKET',
    499             7 => 'NET_SFTP_TYPE_CHAR_DEVICE',
    500             8 => 'NET_SFTP_TYPE_BLOCK_DEVICE',
    501             9 => 'NET_SFTP_TYPE_FIFO'
    502         ];
    503         $this->define_array(
    504             $this->packet_types,
    505             $this->status_codes,
    506             $this->attributes,
    507             $this->open_flags,
    508             $this->open_flags5,
    509             $this->file_types
    510         );
     365        if (empty(self::$packet_types)) {
     366            self::$packet_types = [
     367                1  => 'NET_SFTP_INIT',
     368                2  => 'NET_SFTP_VERSION',
     369                3  => 'NET_SFTP_OPEN',
     370                4  => 'NET_SFTP_CLOSE',
     371                5  => 'NET_SFTP_READ',
     372                6  => 'NET_SFTP_WRITE',
     373                7  => 'NET_SFTP_LSTAT',
     374                9  => 'NET_SFTP_SETSTAT',
     375                10 => 'NET_SFTP_FSETSTAT',
     376                11 => 'NET_SFTP_OPENDIR',
     377                12 => 'NET_SFTP_READDIR',
     378                13 => 'NET_SFTP_REMOVE',
     379                14 => 'NET_SFTP_MKDIR',
     380                15 => 'NET_SFTP_RMDIR',
     381                16 => 'NET_SFTP_REALPATH',
     382                17 => 'NET_SFTP_STAT',
     383                18 => 'NET_SFTP_RENAME',
     384                19 => 'NET_SFTP_READLINK',
     385                20 => 'NET_SFTP_SYMLINK',
     386                21 => 'NET_SFTP_LINK',
     387
     388                101 => 'NET_SFTP_STATUS',
     389                102 => 'NET_SFTP_HANDLE',
     390                103 => 'NET_SFTP_DATA',
     391                104 => 'NET_SFTP_NAME',
     392                105 => 'NET_SFTP_ATTRS',
     393
     394                200 => 'NET_SFTP_EXTENDED'
     395            ];
     396            self::$status_codes = [
     397                0 => 'NET_SFTP_STATUS_OK',
     398                1 => 'NET_SFTP_STATUS_EOF',
     399                2 => 'NET_SFTP_STATUS_NO_SUCH_FILE',
     400                3 => 'NET_SFTP_STATUS_PERMISSION_DENIED',
     401                4 => 'NET_SFTP_STATUS_FAILURE',
     402                5 => 'NET_SFTP_STATUS_BAD_MESSAGE',
     403                6 => 'NET_SFTP_STATUS_NO_CONNECTION',
     404                7 => 'NET_SFTP_STATUS_CONNECTION_LOST',
     405                8 => 'NET_SFTP_STATUS_OP_UNSUPPORTED',
     406                9 => 'NET_SFTP_STATUS_INVALID_HANDLE',
     407                10 => 'NET_SFTP_STATUS_NO_SUCH_PATH',
     408                11 => 'NET_SFTP_STATUS_FILE_ALREADY_EXISTS',
     409                12 => 'NET_SFTP_STATUS_WRITE_PROTECT',
     410                13 => 'NET_SFTP_STATUS_NO_MEDIA',
     411                14 => 'NET_SFTP_STATUS_NO_SPACE_ON_FILESYSTEM',
     412                15 => 'NET_SFTP_STATUS_QUOTA_EXCEEDED',
     413                16 => 'NET_SFTP_STATUS_UNKNOWN_PRINCIPAL',
     414                17 => 'NET_SFTP_STATUS_LOCK_CONFLICT',
     415                18 => 'NET_SFTP_STATUS_DIR_NOT_EMPTY',
     416                19 => 'NET_SFTP_STATUS_NOT_A_DIRECTORY',
     417                20 => 'NET_SFTP_STATUS_INVALID_FILENAME',
     418                21 => 'NET_SFTP_STATUS_LINK_LOOP',
     419                22 => 'NET_SFTP_STATUS_CANNOT_DELETE',
     420                23 => 'NET_SFTP_STATUS_INVALID_PARAMETER',
     421                24 => 'NET_SFTP_STATUS_FILE_IS_A_DIRECTORY',
     422                25 => 'NET_SFTP_STATUS_BYTE_RANGE_LOCK_CONFLICT',
     423                26 => 'NET_SFTP_STATUS_BYTE_RANGE_LOCK_REFUSED',
     424                27 => 'NET_SFTP_STATUS_DELETE_PENDING',
     425                28 => 'NET_SFTP_STATUS_FILE_CORRUPT',
     426                29 => 'NET_SFTP_STATUS_OWNER_INVALID',
     427                30 => 'NET_SFTP_STATUS_GROUP_INVALID',
     428                31 => 'NET_SFTP_STATUS_NO_MATCHING_BYTE_RANGE_LOCK'
     429            ];
     430            // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-7.1
     431            // the order, in this case, matters quite a lot - see \phpseclib3\Net\SFTP::_parseAttributes() to understand why
     432            self::$attributes = [
     433                0x00000001 => 'NET_SFTP_ATTR_SIZE',
     434                0x00000002 => 'NET_SFTP_ATTR_UIDGID',          // defined in SFTPv3, removed in SFTPv4+
     435                0x00000080 => 'NET_SFTP_ATTR_OWNERGROUP',      // defined in SFTPv4+
     436                0x00000004 => 'NET_SFTP_ATTR_PERMISSIONS',
     437                0x00000008 => 'NET_SFTP_ATTR_ACCESSTIME',
     438                0x00000010 => 'NET_SFTP_ATTR_CREATETIME',      // SFTPv4+
     439                0x00000020 => 'NET_SFTP_ATTR_MODIFYTIME',
     440                0x00000040 => 'NET_SFTP_ATTR_ACL',
     441                0x00000100 => 'NET_SFTP_ATTR_SUBSECOND_TIMES',
     442                0x00000200 => 'NET_SFTP_ATTR_BITS',            // SFTPv5+
     443                0x00000400 => 'NET_SFTP_ATTR_ALLOCATION_SIZE', // SFTPv6+
     444                0x00000800 => 'NET_SFTP_ATTR_TEXT_HINT',
     445                0x00001000 => 'NET_SFTP_ATTR_MIME_TYPE',
     446                0x00002000 => 'NET_SFTP_ATTR_LINK_COUNT',
     447                0x00004000 => 'NET_SFTP_ATTR_UNTRANSLATED_NAME',
     448                0x00008000 => 'NET_SFTP_ATTR_CTIME',
     449                // 0x80000000 will yield a floating point on 32-bit systems and converting floating points to integers
     450                // yields inconsistent behavior depending on how php is compiled.  so we left shift -1 (which, in
     451                // two's compliment, consists of all 1 bits) by 31.  on 64-bit systems this'll yield 0xFFFFFFFF80000000.
     452                // that's not a problem, however, and 'anded' and a 32-bit number, as all the leading 1 bits are ignored.
     453                (PHP_INT_SIZE == 4 ? (-1 << 31) : 0x80000000) => 'NET_SFTP_ATTR_EXTENDED'
     454            ];
     455            // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-6.3
     456            // the flag definitions change somewhat in SFTPv5+.  if SFTPv5+ support is added to this library, maybe name
     457            // the array for that $this->open5_flags and similarly alter the constant names.
     458            self::$open_flags = [
     459                0x00000001 => 'NET_SFTP_OPEN_READ',
     460                0x00000002 => 'NET_SFTP_OPEN_WRITE',
     461                0x00000004 => 'NET_SFTP_OPEN_APPEND',
     462                0x00000008 => 'NET_SFTP_OPEN_CREATE',
     463                0x00000010 => 'NET_SFTP_OPEN_TRUNCATE',
     464                0x00000020 => 'NET_SFTP_OPEN_EXCL',
     465                0x00000040 => 'NET_SFTP_OPEN_TEXT' // defined in SFTPv4
     466            ];
     467            // SFTPv5+ changed the flags up:
     468            // https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-13#section-8.1.1.3
     469            self::$open_flags5 = [
     470                // when SSH_FXF_ACCESS_DISPOSITION is a 3 bit field that controls how the file is opened
     471                0x00000000 => 'NET_SFTP_OPEN_CREATE_NEW',
     472                0x00000001 => 'NET_SFTP_OPEN_CREATE_TRUNCATE',
     473                0x00000002 => 'NET_SFTP_OPEN_OPEN_EXISTING',
     474                0x00000003 => 'NET_SFTP_OPEN_OPEN_OR_CREATE',
     475                0x00000004 => 'NET_SFTP_OPEN_TRUNCATE_EXISTING',
     476                // the rest of the flags are not supported
     477                0x00000008 => 'NET_SFTP_OPEN_APPEND_DATA', // "the offset field of SS_FXP_WRITE requests is ignored"
     478                0x00000010 => 'NET_SFTP_OPEN_APPEND_DATA_ATOMIC',
     479                0x00000020 => 'NET_SFTP_OPEN_TEXT_MODE',
     480                0x00000040 => 'NET_SFTP_OPEN_BLOCK_READ',
     481                0x00000080 => 'NET_SFTP_OPEN_BLOCK_WRITE',
     482                0x00000100 => 'NET_SFTP_OPEN_BLOCK_DELETE',
     483                0x00000200 => 'NET_SFTP_OPEN_BLOCK_ADVISORY',
     484                0x00000400 => 'NET_SFTP_OPEN_NOFOLLOW',
     485                0x00000800 => 'NET_SFTP_OPEN_DELETE_ON_CLOSE',
     486                0x00001000 => 'NET_SFTP_OPEN_ACCESS_AUDIT_ALARM_INFO',
     487                0x00002000 => 'NET_SFTP_OPEN_ACCESS_BACKUP',
     488                0x00004000 => 'NET_SFTP_OPEN_BACKUP_STREAM',
     489                0x00008000 => 'NET_SFTP_OPEN_OVERRIDE_OWNER',
     490            ];
     491            // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-5.2
     492            // see \phpseclib3\Net\SFTP::_parseLongname() for an explanation
     493            self::$file_types = [
     494                1 => 'NET_SFTP_TYPE_REGULAR',
     495                2 => 'NET_SFTP_TYPE_DIRECTORY',
     496                3 => 'NET_SFTP_TYPE_SYMLINK',
     497                4 => 'NET_SFTP_TYPE_SPECIAL',
     498                5 => 'NET_SFTP_TYPE_UNKNOWN',
     499                // the following types were first defined for use in SFTPv5+
     500                // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-05#section-5.2
     501                6 => 'NET_SFTP_TYPE_SOCKET',
     502                7 => 'NET_SFTP_TYPE_CHAR_DEVICE',
     503                8 => 'NET_SFTP_TYPE_BLOCK_DEVICE',
     504                9 => 'NET_SFTP_TYPE_FIFO'
     505            ];
     506            self::define_array(
     507                self::$packet_types,
     508                self::$status_codes,
     509                self::$attributes,
     510                self::$open_flags,
     511                self::$open_flags5,
     512                self::$file_types
     513            );
     514        }
    511515
    512516        if (!defined('NET_SFTP_QUEUE_SIZE')) {
     
    544548    private function partial_init_sftp_connection()
    545549    {
    546         $this->window_size_server_to_client[self::CHANNEL] = $this->window_size;
    547 
    548         $packet = Strings::packSSH2(
    549             'CsN3',
    550             NET_SSH2_MSG_CHANNEL_OPEN,
    551             'session',
    552             self::CHANNEL,
    553             $this->window_size,
    554             0x4000
    555         );
    556 
    557         $this->send_binary_packet($packet);
    558 
    559         $this->channel_status[self::CHANNEL] = NET_SSH2_MSG_CHANNEL_OPEN;
    560 
    561         $response = $this->get_channel_packet(self::CHANNEL, true);
     550        $response = $this->openChannel(self::CHANNEL, true);
    562551        if ($response === true && $this->isTimeout()) {
    563552            return false;
     
    816805        }
    817806
    818         $error = $this->status_codes[$status];
     807        $error = self::$status_codes[$status];
    819808
    820809        if ($this->version > 2) {
     
    21392128        if ($start >= 0) {
    21402129            $offset = $start;
    2141         } elseif ($mode & self::RESUME) {
     2130        } elseif ($mode & (self::RESUME | self::RESUME_START)) {
    21422131            // if NET_SFTP_OPEN_APPEND worked as it should _size() wouldn't need to be called
    21432132            $size = $this->stat($remote_file)['size'];
     
    22112200                fseek($fp, $local_start);
    22122201                $size -= $local_start;
     2202            } elseif ($mode & self::RESUME) {
     2203                fseek($fp, $offset);
     2204                $size -= $offset;
    22132205            }
    22142206        } elseif ($dataCallback) {
     
    24982490        }
    24992491
    2500         if ($length > 0 && $length <= $offset - $start) {
    2501             if ($local_file === false) {
    2502                 $content = substr($content, 0, $length);
    2503             } else {
    2504                 ftruncate($fp, $length + $res_offset);
    2505             }
    2506         }
    2507 
    25082492        if ($fclose_check) {
    25092493            fclose($fp);
     
    28422826
    28432827    /**
     2828     * Recursively go through rawlist() output to get the total filesize
     2829     *
     2830     * @return int
     2831     */
     2832    private static function recursiveFilesize(array $files)
     2833    {
     2834        $size = 0;
     2835        foreach ($files as $name => $file) {
     2836            if ($name == '.' || $name == '..') {
     2837                continue;
     2838            }
     2839            $size += is_array($file) ?
     2840                self::recursiveFilesize($file) :
     2841                $file->size;
     2842        }
     2843        return $size;
     2844    }
     2845
     2846    /**
    28442847     * Gets file size
    28452848     *
    28462849     * @param string $path
     2850     * @param bool $recursive
    28472851     * @return mixed
    28482852     */
    2849     public function filesize($path)
    2850     {
    2851         return $this->get_stat_cache_prop($path, 'size');
     2853    public function filesize($path, $recursive = false)
     2854    {
     2855        return !$recursive || $this->filetype($path) != 'dir' ?
     2856            $this->get_stat_cache_prop($path, 'size') :
     2857            self::recursiveFilesize($this->rawlist($path, true));
    28522858    }
    28532859
     
    30423048        }
    30433049
    3044         foreach ($this->attributes as $key => $value) {
     3050        foreach (self::$attributes as $key => $value) {
    30453051            switch ($flags & $key) {
    30463052                case NET_SFTP_ATTR_UIDGID:
     
    32733279
    32743280        if (defined('NET_SFTP_LOGGING')) {
    3275             $packet_type = '-> ' . $this->packet_types[$type] .
     3281            $packet_type = '-> ' . self::$packet_types[$type] .
    32763282                           ' (' . round($stop - $start, 4) . 's)';
    32773283            $this->append_log($packet_type, $data);
     
    33773383
    33783384        if (defined('NET_SFTP_LOGGING')) {
    3379             $packet_type = '<- ' . $this->packet_types[$this->packet_type] .
     3385            $packet_type = '<- ' . self::$packet_types[$this->packet_type] .
    33803386                           ' (' . round($stop - $start, 4) . 's)';
    33813387            $this->append_log($packet_type, $packet);
     
    34213427     * 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')
    34223428     *
    3423      * @return array|string
     3429     * @return array|string|false
    34243430     */
    34253431    public function getSFTPLog()
  • mihdan-no-external-links/tags/5.1.6/vendor/phpseclib/phpseclib/phpseclib/Net/SSH2.php

    r2900866 r3369336  
    554554     * @access private
    555555     */
    556     private $message_numbers = [];
     556    private static $message_numbers = [];
    557557
    558558    /**
     
    563563     * @access private
    564564     */
    565     private $disconnect_reasons = [];
     565    private static $disconnect_reasons = [];
    566566
    567567    /**
     
    572572     * @access private
    573573     */
    574     private $channel_open_failure_reasons = [];
     574    private static $channel_open_failure_reasons = [];
    575575
    576576    /**
     
    582582     * @access private
    583583     */
    584     private $terminal_modes = [];
     584    private static $terminal_modes = [];
    585585
    586586    /**
     
    592592     * @access private
    593593     */
    594     private $channel_extended_data_type_codes = [];
     594    private static $channel_extended_data_type_codes = [];
    595595
    596596    /**
     
    648648
    649649    /**
     650     * The identifier of the interactive channel which was opened most recently
     651     *
     652     * @see self::getInteractiveChannelId()
     653     * @var int
     654     */
     655    private $channel_id_last_interactive = 0;
     656
     657    /**
    650658     * Packet Size
    651659     *
     
    837845     */
    838846    private $request_pty = false;
    839 
    840     /**
    841      * Flag set while exec() is running when using enablePTY()
    842      *
    843      * @var bool
    844      */
    845     private $in_request_pty_exec = false;
    846 
    847     /**
    848      * Flag set after startSubsystem() is called
    849      *
    850      * @var bool
    851      */
    852     private $in_subsystem;
    853847
    854848    /**
     
    10951089
    10961090    /**
     1091     * How many channels are currently opened
     1092     *
     1093     * @var int
     1094     */
     1095    private $channelCount = 0;
     1096
     1097    /**
     1098     * Does the server support multiple channels? If not then error out
     1099     * when multiple channels are attempted to be opened
     1100     *
     1101     * @var bool
     1102     */
     1103    private $errorOnMultipleChannels;
     1104
     1105    /**
    10971106     * Default Constructor.
    10981107     *
     
    11061115    public function __construct($host, $port = 22, $timeout = 10)
    11071116    {
    1108         $this->message_numbers = [
    1109             1 => 'NET_SSH2_MSG_DISCONNECT',
    1110             2 => 'NET_SSH2_MSG_IGNORE',
    1111             3 => 'NET_SSH2_MSG_UNIMPLEMENTED',
    1112             4 => 'NET_SSH2_MSG_DEBUG',
    1113             5 => 'NET_SSH2_MSG_SERVICE_REQUEST',
    1114             6 => 'NET_SSH2_MSG_SERVICE_ACCEPT',
    1115             20 => 'NET_SSH2_MSG_KEXINIT',
    1116             21 => 'NET_SSH2_MSG_NEWKEYS',
    1117             30 => 'NET_SSH2_MSG_KEXDH_INIT',
    1118             31 => 'NET_SSH2_MSG_KEXDH_REPLY',
    1119             50 => 'NET_SSH2_MSG_USERAUTH_REQUEST',
    1120             51 => 'NET_SSH2_MSG_USERAUTH_FAILURE',
    1121             52 => 'NET_SSH2_MSG_USERAUTH_SUCCESS',
    1122             53 => 'NET_SSH2_MSG_USERAUTH_BANNER',
    1123 
    1124             80 => 'NET_SSH2_MSG_GLOBAL_REQUEST',
    1125             81 => 'NET_SSH2_MSG_REQUEST_SUCCESS',
    1126             82 => 'NET_SSH2_MSG_REQUEST_FAILURE',
    1127             90 => 'NET_SSH2_MSG_CHANNEL_OPEN',
    1128             91 => 'NET_SSH2_MSG_CHANNEL_OPEN_CONFIRMATION',
    1129             92 => 'NET_SSH2_MSG_CHANNEL_OPEN_FAILURE',
    1130             93 => 'NET_SSH2_MSG_CHANNEL_WINDOW_ADJUST',
    1131             94 => 'NET_SSH2_MSG_CHANNEL_DATA',
    1132             95 => 'NET_SSH2_MSG_CHANNEL_EXTENDED_DATA',
    1133             96 => 'NET_SSH2_MSG_CHANNEL_EOF',
    1134             97 => 'NET_SSH2_MSG_CHANNEL_CLOSE',
    1135             98 => 'NET_SSH2_MSG_CHANNEL_REQUEST',
    1136             99 => 'NET_SSH2_MSG_CHANNEL_SUCCESS',
    1137             100 => 'NET_SSH2_MSG_CHANNEL_FAILURE'
    1138         ];
    1139         $this->disconnect_reasons = [
    1140             1 => 'NET_SSH2_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT',
    1141             2 => 'NET_SSH2_DISCONNECT_PROTOCOL_ERROR',
    1142             3 => 'NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED',
    1143             4 => 'NET_SSH2_DISCONNECT_RESERVED',
    1144             5 => 'NET_SSH2_DISCONNECT_MAC_ERROR',
    1145             6 => 'NET_SSH2_DISCONNECT_COMPRESSION_ERROR',
    1146             7 => 'NET_SSH2_DISCONNECT_SERVICE_NOT_AVAILABLE',
    1147             8 => 'NET_SSH2_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED',
    1148             9 => 'NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE',
    1149             10 => 'NET_SSH2_DISCONNECT_CONNECTION_LOST',
    1150             11 => 'NET_SSH2_DISCONNECT_BY_APPLICATION',
    1151             12 => 'NET_SSH2_DISCONNECT_TOO_MANY_CONNECTIONS',
    1152             13 => 'NET_SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER',
    1153             14 => 'NET_SSH2_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE',
    1154             15 => 'NET_SSH2_DISCONNECT_ILLEGAL_USER_NAME'
    1155         ];
    1156         $this->channel_open_failure_reasons = [
    1157             1 => 'NET_SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED'
    1158         ];
    1159         $this->terminal_modes = [
    1160             0 => 'NET_SSH2_TTY_OP_END'
    1161         ];
    1162         $this->channel_extended_data_type_codes = [
    1163             1 => 'NET_SSH2_EXTENDED_DATA_STDERR'
    1164         ];
    1165 
    1166         $this->define_array(
    1167             $this->message_numbers,
    1168             $this->disconnect_reasons,
    1169             $this->channel_open_failure_reasons,
    1170             $this->terminal_modes,
    1171             $this->channel_extended_data_type_codes,
    1172             [60 => 'NET_SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ'],
    1173             [60 => 'NET_SSH2_MSG_USERAUTH_PK_OK'],
    1174             [60 => 'NET_SSH2_MSG_USERAUTH_INFO_REQUEST',
    1175                   61 => 'NET_SSH2_MSG_USERAUTH_INFO_RESPONSE'],
    1176             // RFC 4419 - diffie-hellman-group-exchange-sha{1,256}
    1177             [30 => 'NET_SSH2_MSG_KEXDH_GEX_REQUEST_OLD',
    1178                   31 => 'NET_SSH2_MSG_KEXDH_GEX_GROUP',
    1179                   32 => 'NET_SSH2_MSG_KEXDH_GEX_INIT',
    1180                   33 => 'NET_SSH2_MSG_KEXDH_GEX_REPLY',
    1181                   34 => 'NET_SSH2_MSG_KEXDH_GEX_REQUEST'],
    1182             // RFC 5656 - Elliptic Curves (for curve25519-sha256@libssh.org)
    1183             [30 => 'NET_SSH2_MSG_KEX_ECDH_INIT',
    1184                   31 => 'NET_SSH2_MSG_KEX_ECDH_REPLY']
    1185         );
     1117        if (empty(self::$message_numbers)) {
     1118            self::$message_numbers = [
     1119                1 => 'NET_SSH2_MSG_DISCONNECT',
     1120                2 => 'NET_SSH2_MSG_IGNORE',
     1121                3 => 'NET_SSH2_MSG_UNIMPLEMENTED',
     1122                4 => 'NET_SSH2_MSG_DEBUG',
     1123                5 => 'NET_SSH2_MSG_SERVICE_REQUEST',
     1124                6 => 'NET_SSH2_MSG_SERVICE_ACCEPT',
     1125                7 => 'NET_SSH2_MSG_EXT_INFO', // RFC 8308
     1126                20 => 'NET_SSH2_MSG_KEXINIT',
     1127                21 => 'NET_SSH2_MSG_NEWKEYS',
     1128                30 => 'NET_SSH2_MSG_KEXDH_INIT',
     1129                31 => 'NET_SSH2_MSG_KEXDH_REPLY',
     1130                50 => 'NET_SSH2_MSG_USERAUTH_REQUEST',
     1131                51 => 'NET_SSH2_MSG_USERAUTH_FAILURE',
     1132                52 => 'NET_SSH2_MSG_USERAUTH_SUCCESS',
     1133                53 => 'NET_SSH2_MSG_USERAUTH_BANNER',
     1134
     1135                80 => 'NET_SSH2_MSG_GLOBAL_REQUEST',
     1136                81 => 'NET_SSH2_MSG_REQUEST_SUCCESS',
     1137                82 => 'NET_SSH2_MSG_REQUEST_FAILURE',
     1138                90 => 'NET_SSH2_MSG_CHANNEL_OPEN',
     1139                91 => 'NET_SSH2_MSG_CHANNEL_OPEN_CONFIRMATION',
     1140                92 => 'NET_SSH2_MSG_CHANNEL_OPEN_FAILURE',
     1141                93 => 'NET_SSH2_MSG_CHANNEL_WINDOW_ADJUST',
     1142                94 => 'NET_SSH2_MSG_CHANNEL_DATA',
     1143                95 => 'NET_SSH2_MSG_CHANNEL_EXTENDED_DATA',
     1144                96 => 'NET_SSH2_MSG_CHANNEL_EOF',
     1145                97 => 'NET_SSH2_MSG_CHANNEL_CLOSE',
     1146                98 => 'NET_SSH2_MSG_CHANNEL_REQUEST',
     1147                99 => 'NET_SSH2_MSG_CHANNEL_SUCCESS',
     1148                100 => 'NET_SSH2_MSG_CHANNEL_FAILURE'
     1149            ];
     1150            self::$disconnect_reasons = [
     1151                1 => 'NET_SSH2_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT',
     1152                2 => 'NET_SSH2_DISCONNECT_PROTOCOL_ERROR',
     1153                3 => 'NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED',
     1154                4 => 'NET_SSH2_DISCONNECT_RESERVED',
     1155                5 => 'NET_SSH2_DISCONNECT_MAC_ERROR',
     1156                6 => 'NET_SSH2_DISCONNECT_COMPRESSION_ERROR',
     1157                7 => 'NET_SSH2_DISCONNECT_SERVICE_NOT_AVAILABLE',
     1158                8 => 'NET_SSH2_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED',
     1159                9 => 'NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE',
     1160                10 => 'NET_SSH2_DISCONNECT_CONNECTION_LOST',
     1161                11 => 'NET_SSH2_DISCONNECT_BY_APPLICATION',
     1162                12 => 'NET_SSH2_DISCONNECT_TOO_MANY_CONNECTIONS',
     1163                13 => 'NET_SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER',
     1164                14 => 'NET_SSH2_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE',
     1165                15 => 'NET_SSH2_DISCONNECT_ILLEGAL_USER_NAME'
     1166            ];
     1167            self::$channel_open_failure_reasons = [
     1168                1 => 'NET_SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED'
     1169            ];
     1170            self::$terminal_modes = [
     1171                0 => 'NET_SSH2_TTY_OP_END'
     1172            ];
     1173            self::$channel_extended_data_type_codes = [
     1174                1 => 'NET_SSH2_EXTENDED_DATA_STDERR'
     1175            ];
     1176
     1177            self::define_array(
     1178                self::$message_numbers,
     1179                self::$disconnect_reasons,
     1180                self::$channel_open_failure_reasons,
     1181                self::$terminal_modes,
     1182                self::$channel_extended_data_type_codes,
     1183                [60 => 'NET_SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ'],
     1184                [60 => 'NET_SSH2_MSG_USERAUTH_PK_OK'],
     1185                [60 => 'NET_SSH2_MSG_USERAUTH_INFO_REQUEST',
     1186                      61 => 'NET_SSH2_MSG_USERAUTH_INFO_RESPONSE'],
     1187                // RFC 4419 - diffie-hellman-group-exchange-sha{1,256}
     1188                [30 => 'NET_SSH2_MSG_KEXDH_GEX_REQUEST_OLD',
     1189                      31 => 'NET_SSH2_MSG_KEXDH_GEX_GROUP',
     1190                      32 => 'NET_SSH2_MSG_KEXDH_GEX_INIT',
     1191                      33 => 'NET_SSH2_MSG_KEXDH_GEX_REPLY',
     1192                      34 => 'NET_SSH2_MSG_KEXDH_GEX_REQUEST'],
     1193                // RFC 5656 - Elliptic Curves (for curve25519-sha256@libssh.org)
     1194                [30 => 'NET_SSH2_MSG_KEX_ECDH_INIT',
     1195                      31 => 'NET_SSH2_MSG_KEX_ECDH_REPLY']
     1196            );
     1197        }
    11861198
    11871199        /**
     
    12681280    {
    12691281        $this->send_kex_first = false;
     1282    }
     1283
     1284    /**
     1285     * stream_select wrapper
     1286     *
     1287     * Quoting https://stackoverflow.com/a/14262151/569976,
     1288     * "The general approach to `EINTR` is to simply handle the error and retry the operation again"
     1289     *
     1290     * This wrapper does that loop
     1291     */
     1292    private static function stream_select(&$read, &$write, &$except, $seconds, $microseconds = null)
     1293    {
     1294        $remaining = $seconds + $microseconds / 1000000;
     1295        $start = microtime(true);
     1296        while (true) {
     1297            $result = @stream_select($read, $write, $except, $seconds, $microseconds);
     1298            if ($result !== false) {
     1299                return $result;
     1300            }
     1301            $elapsed = microtime(true) - $start;
     1302            $seconds = (int) ($remaining - floor($elapsed));
     1303            $microseconds = (int) (1000000 * ($remaining - $seconds));
     1304            if ($elapsed >= $remaining) {
     1305                return false;
     1306            }
     1307        }
    12701308    }
    12711309
     
    13341372                    $sec = (int) floor($this->curTimeout);
    13351373                    $usec = (int) (1000000 * ($this->curTimeout - $sec));
    1336                     if (@stream_select($read, $write, $except, $sec, $usec) === false) {
     1374                    if (static::stream_select($read, $write, $except, $sec, $usec) === false) {
    13371375                        throw new \RuntimeException('Connection timed out whilst receiving server identification string');
    13381376                    }
     
    13881426            throw new UnableToConnectException("Cannot connect to SSH $matches[3] servers");
    13891427        }
     1428
     1429        // Ubuntu's OpenSSH from 5.8 to 6.9 didn't work with multiple channels. see
     1430        // https://bugs.launchpad.net/ubuntu/+source/openssh/+bug/1334916 for more info.
     1431        // https://lists.ubuntu.com/archives/oneiric-changes/2011-July/005772.html discusses
     1432        // when consolekit was incorporated.
     1433        // https://marc.info/?l=openssh-unix-dev&m=163409903417589&w=2 discusses some of the
     1434        // issues with how Ubuntu incorporated consolekit
     1435        $pattern = '#^SSH-2\.0-OpenSSH_([\d.]+)[^ ]* Ubuntu-.*$#';
     1436        $match = preg_match($pattern, $this->server_identifier, $matches);
     1437        $match = $match && version_compare('5.8', $matches[1], '<=');
     1438        $match = $match && version_compare('6.9', $matches[1], '>=');
     1439        $this->errorOnMultipleChannels = $match;
    13901440
    13911441        if (!$this->send_id_string_first) {
     
    14871537            SSH2::getSupportedCompressionAlgorithms();
    14881538
     1539        $kex_algorithms = array_merge($kex_algorithms, array('ext-info-c'));
     1540
    14891541        // some SSH servers have buggy implementations of some of the above algorithms
    14901542        switch (true) {
     
    15011553                        $c2s_mac_algorithms,
    15021554                        ['hmac-sha1-96', 'hmac-md5-96']
     1555                    ));
     1556                }
     1557                break;
     1558            case substr($this->server_identifier, 0, 24) == 'SSH-2.0-TurboFTP_SERVER_':
     1559                if (!isset($preferred['server_to_client']['crypt'])) {
     1560                    $s2c_encryption_algorithms = array_values(array_diff(
     1561                        $s2c_encryption_algorithms,
     1562                        ['aes128-gcm@openssh.com', 'aes256-gcm@openssh.com']
     1563                    ));
     1564                }
     1565                if (!isset($preferred['client_to_server']['crypt'])) {
     1566                    $c2s_encryption_algorithms = array_values(array_diff(
     1567                        $c2s_encryption_algorithms,
     1568                        ['aes128-gcm@openssh.com', 'aes256-gcm@openssh.com']
    15031569                    ));
    15041570                }
     
    21222188     *
    21232189     * @param string $username
    2124      * @param string|AsymmetricKey|array[]|Agent|null ...$args
     2190     * @param string|PrivateKey|array[]|Agent|null ...$args
    21252191     * @return bool
    21262192     * @see self::_login()
     
    21472213     *
    21482214     * @param string $username
    2149      * @param string ...$args
     2215     * @param string|PrivateKey|array[]|Agent|null ...$args
    21502216     * @return bool
    21512217     * @see self::_login_helper()
     
    22672333                }
    22682334                $this->disconnect_helper(NET_SSH2_DISCONNECT_CONNECTION_LOST);
    2269                 throw new ConnectionClosedException('Connection closed by server');
    2270             }
    2271 
    2272             list($type, $service) = Strings::unpackSSH2('Cs', $response);
     2335                throw $e;
     2336            }
     2337
     2338            list($type) = Strings::unpackSSH2('C', $response);
     2339
     2340            if ($type == NET_SSH2_MSG_EXT_INFO) {
     2341                list($nr_extensions) = Strings::unpackSSH2('N', $response);
     2342                for ($i = 0; $i < $nr_extensions; $i++) {
     2343                    list($extension_name, $extension_value) = Strings::unpackSSH2('ss', $response);
     2344                    if ($extension_name == 'server-sig-algs') {
     2345                        $this->supported_private_key_algorithms = explode(',', $extension_value);
     2346                    }
     2347                }
     2348
     2349                $response = $this->get_binary_packet();
     2350                list($type) = Strings::unpackSSH2('C', $response);
     2351            }
     2352
     2353            list($service) = Strings::unpackSSH2('s', $response);
     2354
    22732355            if ($type != NET_SSH2_MSG_SERVICE_ACCEPT || $service != 'ssh-userauth') {
    22742356                $this->disconnect_helper(NET_SSH2_DISCONNECT_PROTOCOL_ERROR);
     
    25462628            $algos = ['rsa-sha2-256', 'rsa-sha2-512', 'ssh-rsa'];
    25472629            if (isset($this->preferred['hostkey'])) {
    2548                 $algos = array_intersect($this->preferred['hostkey'], $algos);
     2630                $algos = array_intersect($algos, $this->preferred['hostkey']);
    25492631            }
    25502632            $algo = self::array_intersect_first($algos, $this->supported_private_key_algorithms);
     
    27302812        }
    27312813
    2732         if ($this->in_request_pty_exec) {
    2733             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.');
    2734         }
    2735 
    2736         // RFC4254 defines the (client) window size as "bytes the other party can send before it must wait for the window to
    2737         // be adjusted".  0x7FFFFFFF is, at 2GB, the max size.  technically, it should probably be decremented, but,
    2738         // honestly, if you're transferring more than 2GB, you probably shouldn't be using phpseclib, anyway.
    2739         // see http://tools.ietf.org/html/rfc4254#section-5.2 for more info
    2740         $this->window_size_server_to_client[self::CHANNEL_EXEC] = $this->window_size;
    2741         // 0x8000 is the maximum max packet size, per http://tools.ietf.org/html/rfc4253#section-6.1, although since PuTTy
    2742         // uses 0x4000, that's what will be used here, as well.
    2743         $packet_size = 0x4000;
    2744 
    2745         $packet = Strings::packSSH2(
    2746             'CsN3',
    2747             NET_SSH2_MSG_CHANNEL_OPEN,
    2748             'session',
    2749             self::CHANNEL_EXEC,
    2750             $this->window_size_server_to_client[self::CHANNEL_EXEC],
    2751             $packet_size
    2752         );
    2753         $this->send_binary_packet($packet);
    2754 
    2755         $this->channel_status[self::CHANNEL_EXEC] = NET_SSH2_MSG_CHANNEL_OPEN;
    2756 
    2757         $this->get_channel_packet(self::CHANNEL_EXEC);
     2814        //if ($this->isPTYOpen()) {
     2815        //    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.');
     2816        //}
     2817
     2818        $this->openChannel(self::CHANNEL_EXEC);
    27582819
    27592820        if ($this->request_pty === true) {
     
    27802841                throw new \RuntimeException('Unable to request pseudo-terminal');
    27812842            }
    2782 
    2783             $this->in_request_pty_exec = true;
    27842843        }
    27852844
     
    28112870        $this->channel_status[self::CHANNEL_EXEC] = NET_SSH2_MSG_CHANNEL_DATA;
    28122871
    2813         if ($this->in_request_pty_exec) {
     2872        if ($this->request_pty === true) {
     2873            $this->channel_id_last_interactive = self::CHANNEL_EXEC;
    28142874            return true;
    28152875        }
     
    28372897
    28382898    /**
    2839      * Creates an interactive shell
    2840      *
    2841      * @see self::read()
    2842      * @see self::write()
     2899     * How many channels are currently open?
     2900     *
     2901     * @return int
     2902     */
     2903    public function getOpenChannelCount()
     2904    {
     2905        return $this->channelCount;
     2906    }
     2907
     2908    /**
     2909     * Opens a channel
     2910     *
     2911     * @param string $channel
     2912     * @param bool $skip_extended
    28432913     * @return bool
    2844      * @throws \UnexpectedValueException on receipt of unexpected packets
    2845      * @throws \RuntimeException on other errors
    2846      */
    2847     private function initShell()
    2848     {
    2849         if ($this->in_request_pty_exec === true) {
    2850             return true;
    2851         }
    2852 
    2853         $this->window_size_server_to_client[self::CHANNEL_SHELL] = $this->window_size;
     2914     */
     2915    protected function openChannel($channel, $skip_extended = false)
     2916    {
     2917        if (isset($this->channel_status[$channel]) && $this->channel_status[$channel] != NET_SSH2_MSG_CHANNEL_CLOSE) {
     2918            throw new \RuntimeException('Please close the channel (' . $channel . ') before trying to open it again');
     2919        }
     2920
     2921        $this->channelCount++;
     2922
     2923        if ($this->channelCount > 1 && $this->errorOnMultipleChannels) {
     2924            throw new \RuntimeException("Ubuntu's OpenSSH from 5.8 to 6.9 doesn't work with multiple channels");
     2925        }
     2926
     2927        // RFC4254 defines the (client) window size as "bytes the other party can send before it must wait for the window to
     2928        // be adjusted".  0x7FFFFFFF is, at 2GB, the max size.  technically, it should probably be decremented, but,
     2929        // honestly, if you're transferring more than 2GB, you probably shouldn't be using phpseclib, anyway.
     2930        // see http://tools.ietf.org/html/rfc4254#section-5.2 for more info
     2931        $this->window_size_server_to_client[$channel] = $this->window_size;
     2932        // 0x8000 is the maximum max packet size, per http://tools.ietf.org/html/rfc4253#section-6.1, although since PuTTy
     2933        // uses 0x4000, that's what will be used here, as well.
    28542934        $packet_size = 0x4000;
    28552935
     
    28582938            NET_SSH2_MSG_CHANNEL_OPEN,
    28592939            'session',
    2860             self::CHANNEL_SHELL,
    2861             $this->window_size_server_to_client[self::CHANNEL_SHELL],
     2940            $channel,
     2941            $this->window_size_server_to_client[$channel],
    28622942            $packet_size
    28632943        );
     
    28652945        $this->send_binary_packet($packet);
    28662946
    2867         $this->channel_status[self::CHANNEL_SHELL] = NET_SSH2_MSG_CHANNEL_OPEN;
    2868 
    2869         $this->get_channel_packet(self::CHANNEL_SHELL);
     2947        $this->channel_status[$channel] = NET_SSH2_MSG_CHANNEL_OPEN;
     2948
     2949        return $this->get_channel_packet($channel, $skip_extended);
     2950    }
     2951
     2952    /**
     2953     * Creates an interactive shell
     2954     *
     2955     * Returns bool(true) if the shell was opened.
     2956     * Returns bool(false) if the shell was already open.
     2957     *
     2958     * @see self::isShellOpen()
     2959     * @see self::read()
     2960     * @see self::write()
     2961     * @return bool
     2962     * @throws InsufficientSetupException if not authenticated
     2963     * @throws \UnexpectedValueException on receipt of unexpected packets
     2964     * @throws \RuntimeException on other errors
     2965     */
     2966    public function openShell()
     2967    {
     2968        if (!$this->isAuthenticated()) {
     2969            throw new InsufficientSetupException('Operation disallowed prior to login()');
     2970        }
     2971
     2972        $this->openChannel(self::CHANNEL_SHELL);
    28702973
    28712974        $terminal_modes = pack('C', NET_SSH2_TTY_OP_END);
     
    29083011        $this->channel_status[self::CHANNEL_SHELL] = NET_SSH2_MSG_CHANNEL_DATA;
    29093012
     3013        $this->channel_id_last_interactive = self::CHANNEL_SHELL;
     3014
    29103015        $this->bitmap |= self::MASK_SHELL;
    29113016
     
    29143019
    29153020    /**
    2916      * Return the channel to be used with read() / write()
    2917      *
     3021     * Return the channel to be used with read(), write(), and reset(), if none were specified
     3022     * @deprecated for lack of transparency in intended channel target, to be potentially replaced
     3023     *             with method which guarantees open-ness of all yielded channels and throws
     3024     *             error for multiple open channels
    29183025     * @see self::read()
    29193026     * @see self::write()
     
    29233030    {
    29243031        switch (true) {
    2925             case $this->in_subsystem:
     3032            case $this->is_channel_status_data(self::CHANNEL_SUBSYSTEM):
    29263033                return self::CHANNEL_SUBSYSTEM;
    2927             case $this->in_request_pty_exec:
     3034            case $this->is_channel_status_data(self::CHANNEL_EXEC):
    29283035                return self::CHANNEL_EXEC;
    29293036            default:
    29303037                return self::CHANNEL_SHELL;
    29313038        }
     3039    }
     3040
     3041    /**
     3042     * Indicates the DATA status on the given channel
     3043     *
     3044     * @param int $channel The channel number to evaluate
     3045     * @return bool
     3046     */
     3047    private function is_channel_status_data($channel)
     3048    {
     3049        return isset($this->channel_status[$channel]) && $this->channel_status[$channel] == NET_SSH2_MSG_CHANNEL_DATA;
    29323050    }
    29333051
     
    29883106     * if $mode == self::READ_REGEX, a regular expression.
    29893107     *
     3108     * If not specifying a channel, an open interactive channel will be selected, or, if there are
     3109     * no open channels, an interactive shell will be created. If there are multiple open
     3110     * interactive channels, a legacy behavior will apply in which channel selection prioritizes
     3111     * an active subsystem, the exec pty, and, lastly, the shell. If using multiple interactive
     3112     * channels, callers are discouraged from relying on this legacy behavior and should specify
     3113     * the intended channel.
     3114     *
    29903115     * @see self::write()
    29913116     * @param string $expect
    2992      * @param int $mode
     3117     * @param int $mode One of the self::READ_* constants
     3118     * @param int|null $channel Channel id returned by self::getInteractiveChannelId()
    29933119     * @return string|bool|null
    29943120     * @throws \RuntimeException on connection error
    2995      */
    2996     public function read($expect = '', $mode = self::READ_SIMPLE)
    2997     {
     3121     * @throws InsufficientSetupException on unexpected channel status, possibly due to closure
     3122     */
     3123    public function read($expect = '', $mode = self::READ_SIMPLE, $channel = null)
     3124    {
     3125        if (!$this->isAuthenticated()) {
     3126            throw new InsufficientSetupException('Operation disallowed prior to login()');
     3127        }
     3128
    29983129        $this->curTimeout = $this->timeout;
    29993130        $this->is_timeout = false;
    30003131
    3001         if (!$this->isAuthenticated()) {
    3002             throw new InsufficientSetupException('Operation disallowed prior to login()');
    3003         }
    3004 
    3005         if (!($this->bitmap & self::MASK_SHELL) && !$this->initShell()) {
    3006             throw new \RuntimeException('Unable to initiate an interactive shell session');
    3007         }
    3008 
    3009         $channel = $this->get_interactive_channel();
     3132        if ($channel === null) {
     3133            $channel = $this->get_interactive_channel();
     3134        }
     3135
     3136        if (!$this->is_channel_status_data($channel) && empty($this->channel_buffers[$channel])) {
     3137            if ($channel != self::CHANNEL_SHELL) {
     3138                throw new InsufficientSetupException('Data is not available on channel');
     3139            } elseif (!$this->openShell()) {
     3140                throw new \RuntimeException('Unable to initiate an interactive shell session');
     3141            }
     3142        }
    30103143
    30113144        if ($mode == self::READ_NEXT) {
     
    30253158            $response = $this->get_channel_packet($channel);
    30263159            if ($response === true) {
    3027                 $this->in_request_pty_exec = false;
    30283160                return Strings::shift($this->interactiveBuffer, strlen($this->interactiveBuffer));
    30293161            }
     
    30353167    /**
    30363168     * Inputs a command into an interactive shell.
     3169     *
     3170     * If not specifying a channel, an open interactive channel will be selected, or, if there are
     3171     * no open channels, an interactive shell will be created. If there are multiple open
     3172     * interactive channels, a legacy behavior will apply in which channel selection prioritizes
     3173     * an active subsystem, the exec pty, and, lastly, the shell. If using multiple interactive
     3174     * channels, callers are discouraged from relying on this legacy behavior and should specify
     3175     * the intended channel.
    30373176     *
    30383177     * @see SSH2::read()
    30393178     * @param string $cmd
     3179     * @param int|null $channel Channel id returned by self::getInteractiveChannelId()
    30403180     * @return void
    30413181     * @throws \RuntimeException on connection error
    3042      */
    3043     public function write($cmd)
     3182     * @throws InsufficientSetupException on unexpected channel status, possibly due to closure
     3183     */
     3184    public function write($cmd, $channel = null)
    30443185    {
    30453186        if (!$this->isAuthenticated()) {
     
    30473188        }
    30483189
    3049         if (!($this->bitmap & self::MASK_SHELL) && !$this->initShell()) {
    3050             throw new \RuntimeException('Unable to initiate an interactive shell session');
    3051         }
    3052 
    3053         $this->send_channel_packet($this->get_interactive_channel(), $cmd);
     3190        if ($channel === null) {
     3191            $channel = $this->get_interactive_channel();
     3192        }
     3193
     3194        if (!$this->is_channel_status_data($channel)) {
     3195            if ($channel != self::CHANNEL_SHELL) {
     3196                throw new InsufficientSetupException('Data is not available on channel');
     3197            } elseif (!$this->openShell()) {
     3198                throw new \RuntimeException('Unable to initiate an interactive shell session');
     3199            }
     3200        }
     3201
     3202        $this->send_channel_packet($channel, $cmd);
    30543203    }
    30553204
     
    30693218    public function startSubsystem($subsystem)
    30703219    {
    3071         $this->window_size_server_to_client[self::CHANNEL_SUBSYSTEM] = $this->window_size;
    3072 
    3073         $packet = Strings::packSSH2(
    3074             'CsN3',
    3075             NET_SSH2_MSG_CHANNEL_OPEN,
    3076             'session',
    3077             self::CHANNEL_SUBSYSTEM,
    3078             $this->window_size,
    3079             0x4000
    3080         );
    3081 
    3082         $this->send_binary_packet($packet);
    3083 
    3084         $this->channel_status[self::CHANNEL_SUBSYSTEM] = NET_SSH2_MSG_CHANNEL_OPEN;
    3085 
    3086         $this->get_channel_packet(self::CHANNEL_SUBSYSTEM);
     3220        $this->openChannel(self::CHANNEL_SUBSYSTEM);
    30873221
    30883222        $packet = Strings::packSSH2(
     
    31043238        $this->channel_status[self::CHANNEL_SUBSYSTEM] = NET_SSH2_MSG_CHANNEL_DATA;
    31053239
    3106         $this->bitmap |= self::MASK_SHELL;
    3107         $this->in_subsystem = true;
     3240        $this->channel_id_last_interactive = self::CHANNEL_SUBSYSTEM;
    31083241
    31093242        return true;
     
    31183251    public function stopSubsystem()
    31193252    {
    3120         $this->in_subsystem = false;
    3121         $this->close_channel(self::CHANNEL_SUBSYSTEM);
     3253        if ($this->isInteractiveChannelOpen(self::CHANNEL_SUBSYSTEM)) {
     3254            $this->close_channel(self::CHANNEL_SUBSYSTEM);
     3255        }
    31223256        return true;
    31233257    }
     
    31283262     * If read() timed out you might want to just close the channel and have it auto-restart on the next read() call
    31293263     *
    3130      */
    3131     public function reset()
    3132     {
    3133         $this->close_channel($this->get_interactive_channel());
     3264     * If not specifying a channel, an open interactive channel will be selected. If there are
     3265     * multiple open interactive channels, a legacy behavior will apply in which channel selection
     3266     * prioritizes an active subsystem, the exec pty, and, lastly, the shell. If using multiple
     3267     * interactive channels, callers are discouraged from relying on this legacy behavior and
     3268     * should specify the intended channel.
     3269     *
     3270     * @param int|null $channel Channel id returned by self::getInteractiveChannelId()
     3271     * @return void
     3272     */
     3273    public function reset($channel = null)
     3274    {
     3275        if ($channel === null) {
     3276            $channel = $this->get_interactive_channel();
     3277        }
     3278        if ($this->isInteractiveChannelOpen($channel)) {
     3279            $this->close_channel($channel);
     3280        }
    31343281    }
    31353282
     
    31773324    public function isConnected()
    31783325    {
    3179         return (bool) ($this->bitmap & self::MASK_CONNECTED);
     3326        return ($this->bitmap & self::MASK_CONNECTED) && is_resource($this->fsock) && !feof($this->fsock);
    31803327    }
    31813328
     
    31883335    {
    31893336        return (bool) ($this->bitmap & self::MASK_LOGIN);
     3337    }
     3338
     3339    /**
     3340     * Is the interactive shell active?
     3341     *
     3342     * @return bool
     3343     */
     3344    public function isShellOpen()
     3345    {
     3346        return $this->isInteractiveChannelOpen(self::CHANNEL_SHELL);
     3347    }
     3348
     3349    /**
     3350     * Is the exec pty active?
     3351     *
     3352     * @return bool
     3353     */
     3354    public function isPTYOpen()
     3355    {
     3356        return $this->isInteractiveChannelOpen(self::CHANNEL_EXEC);
     3357    }
     3358
     3359    /**
     3360     * Is the given interactive channel active?
     3361     *
     3362     * @param int $channel Channel id returned by self::getInteractiveChannelId()
     3363     * @return bool
     3364     */
     3365    public function isInteractiveChannelOpen($channel)
     3366    {
     3367        return $this->isAuthenticated() && $this->is_channel_status_data($channel);
     3368    }
     3369
     3370    /**
     3371     * Returns a channel identifier, presently of the last interactive channel opened, regardless of current status.
     3372     * Returns 0 if no interactive channel has been opened.
     3373     *
     3374     * @see self::isInteractiveChannelOpen()
     3375     * @return int
     3376     */
     3377    public function getInteractiveChannelId()
     3378    {
     3379        return $this->channel_id_last_interactive;
    31903380    }
    31913381
     
    32063396        }
    32073397
    3208         $this->window_size_server_to_client[self::CHANNEL_KEEP_ALIVE] = $this->window_size;
    3209         $packet_size = 0x4000;
    3210         $packet = Strings::packSSH2(
    3211             'CsN3',
    3212             NET_SSH2_MSG_CHANNEL_OPEN,
    3213             'session',
    3214             self::CHANNEL_KEEP_ALIVE,
    3215             $this->window_size_server_to_client[self::CHANNEL_KEEP_ALIVE],
    3216             $packet_size
    3217         );
    3218 
    32193398        try {
    3220             $this->send_binary_packet($packet);
    3221 
    3222             $this->channel_status[self::CHANNEL_KEEP_ALIVE] = NET_SSH2_MSG_CHANNEL_OPEN;
    3223 
    3224             $response = $this->get_channel_packet(self::CHANNEL_KEEP_ALIVE);
     3399            $this->openChannel(self::CHANNEL_KEEP_ALIVE);
    32253400        } catch (\RuntimeException $e) {
    32263401            return $this->reconnect();
     
    32623437        $this->retry_connect = true;
    32633438        $this->get_seq_no = $this->send_seq_no = 0;
     3439        $this->channel_status = [];
     3440        $this->channel_id_last_interactive = 0;
    32643441    }
    32653442
     
    32843461            if (!$this->curTimeout) {
    32853462                if ($this->keepAlive <= 0) {
    3286                     @stream_select($read, $write, $except, null);
     3463                    static::stream_select($read, $write, $except, null);
    32873464                } else {
    3288                     if (!@stream_select($read, $write, $except, $this->keepAlive)) {
     3465                    if (!static::stream_select($read, $write, $except, $this->keepAlive)) {
    32893466                        $this->send_binary_packet(pack('CN', NET_SSH2_MSG_IGNORE, 0));
    32903467                        return $this->get_binary_packet(true);
     
    33003477
    33013478                if ($this->keepAlive > 0 && $this->keepAlive < $this->curTimeout) {
    3302                     if (!@stream_select($read, $write, $except, $this->keepAlive)) {
     3479                    if (!static::stream_select($read, $write, $except, $this->keepAlive)) {
    33033480                        $this->send_binary_packet(pack('CN', NET_SSH2_MSG_IGNORE, 0));
    33043481                        $elapsed = microtime(true) - $start;
     
    33143491
    33153492                // this can return a "stream_select(): unable to select [4]: Interrupted system call" error
    3316                 if (!@stream_select($read, $write, $except, $sec, $usec)) {
     3493                if (!static::stream_select($read, $write, $except, $sec, $usec)) {
    33173494                    $this->is_timeout = true;
    33183495                    return true;
     
    35053682        if (defined('NET_SSH2_LOGGING')) {
    35063683            $current = microtime(true);
    3507             $message_number = isset($this->message_numbers[ord($payload[0])]) ? $this->message_numbers[ord($payload[0])] : 'UNKNOWN (' . ord($payload[0]) . ')';
     3684            $message_number = isset(self::$message_numbers[ord($payload[0])]) ? self::$message_numbers[ord($payload[0])] : 'UNKNOWN (' . ord($payload[0]) . ')';
    35083685            $message_number = '<- ' . $message_number .
    35093686                              ' (since last: ' . round($current - $this->last_packet, 4) . ', network: ' . round($stop - $start, 4) . 's)';
     
    35873764                Strings::shift($payload, 1);
    35883765                list($reason_code, $message) = Strings::unpackSSH2('Ns', $payload);
    3589                 $this->errors[] = 'SSH_MSG_DISCONNECT: ' . $this->disconnect_reasons[$reason_code] . "\r\n$message";
     3766                $this->errors[] = 'SSH_MSG_DISCONNECT: ' . self::$disconnect_reasons[$reason_code] . "\r\n$message";
    35903767                $this->bitmap = 0;
    35913768                return false;
     
    37743951    public function disablePTY()
    37753952    {
    3776         if ($this->in_request_pty_exec) {
     3953        if ($this->isPTYOpen()) {
    37773954            $this->close_channel(self::CHANNEL_EXEC);
    3778             $this->in_request_pty_exec = false;
    37793955        }
    37803956        $this->request_pty = false;
     
    38023978     * - if the channel status is CHANNEL_OPEN and the response was CHANNEL_OPEN_CONFIRMATION
    38033979     * - if the channel status is CHANNEL_REQUEST and the response was CHANNEL_SUCCESS
     3980     * - if the channel status is CHANNEL_CLOSE and the response was CHANNEL_CLOSE
    38043981     *
    38053982     * bool(false) is returned if:
     
    39694146                        }
    39704147                    case NET_SSH2_MSG_CHANNEL_CLOSE:
    3971                         return $type == NET_SSH2_MSG_CHANNEL_CLOSE ? true : $this->get_channel_packet($client_channel, $skip_extended);
     4148                        if ($client_channel == $channel && $type == NET_SSH2_MSG_CHANNEL_CLOSE) {
     4149                            return true;
     4150                        }
     4151                        return $this->get_channel_packet($client_channel, $skip_extended);
    39724152                }
    39734153            }
     
    40044184                    $this->curTimeout = 5;
    40054185
    4006                     if ($this->bitmap & self::MASK_SHELL) {
    4007                         $this->bitmap &= ~self::MASK_SHELL;
    4008                     }
     4186                    $this->close_channel_bitmap($channel);
     4187
    40094188                    if ($this->channel_status[$channel] != NET_SSH2_MSG_CHANNEL_EOF) {
    40104189                        $this->send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_CLOSE, $this->server_channels[$channel]));
     
    40124191
    40134192                    $this->channel_status[$channel] = NET_SSH2_MSG_CHANNEL_CLOSE;
     4193                    $this->channelCount--;
     4194
    40144195                    if ($client_channel == $channel) {
    40154196                        return true;
     
    41584339        if (defined('NET_SSH2_LOGGING')) {
    41594340            $current = microtime(true);
    4160             $message_number = isset($this->message_numbers[ord($logged[0])]) ? $this->message_numbers[ord($logged[0])] : 'UNKNOWN (' . ord($logged[0]) . ')';
     4341            $message_number = isset(self::$message_numbers[ord($logged[0])]) ? self::$message_numbers[ord($logged[0])] : 'UNKNOWN (' . ord($logged[0]) . ')';
    41614342            $message_number = '-> ' . $message_number .
    41624343                              ' (since last: ' . round($current - $this->last_packet, 4) . ', network: ' . round($stop - $start, 4) . 's)';
     
    41674348        if (strlen($packet) != $sent) {
    41684349            $this->bitmap = 0;
    4169             throw new \RuntimeException("Only $sent of " . strlen($packet) . " bytes were sent");
     4350            $message = $sent === false ?
     4351                'Unable to write ' . strlen($packet) . ' bytes' :
     4352                "Only $sent of " . strlen($packet) . " bytes were sent";
     4353            throw new \RuntimeException($message);
    41704354        }
    41714355    }
     
    43434527
    43444528        $this->channel_status[$client_channel] = NET_SSH2_MSG_CHANNEL_CLOSE;
     4529        $this->channelCount--;
    43454530
    43464531        $this->curTimeout = 5;
    43474532
    43484533        while (!is_bool($this->get_channel_packet($client_channel))) {
    4349         }
    4350 
    4351         if ($this->is_timeout) {
    4352             $this->disconnect();
    43534534        }
    43544535
     
    43574538        }
    43584539
    4359         if ($this->bitmap & self::MASK_SHELL) {
    4360             $this->bitmap &= ~self::MASK_SHELL;
     4540        $this->close_channel_bitmap($client_channel);
     4541    }
     4542
     4543    /**
     4544     * Maintains execution state bitmap in response to channel closure
     4545     *
     4546     * @param int $client_channel The channel number to maintain closure status of
     4547     * @return void
     4548     */
     4549    private function close_channel_bitmap($client_channel)
     4550    {
     4551        switch ($client_channel) {
     4552            case self::CHANNEL_SHELL:
     4553                // Shell status has been maintained in the bitmap for backwards
     4554                //  compatibility sake, but can be removed going forward
     4555                if ($this->bitmap & self::MASK_SHELL) {
     4556                    $this->bitmap &= ~self::MASK_SHELL;
     4557                }
     4558                break;
    43614559        }
    43624560    }
     
    43964594     * @access protected
    43974595     */
    4398     protected function define_array(...$args)
     4596    protected static function define_array(...$args)
    43994597    {
    44004598        foreach ($args as $arg) {
     
    47994997            ]
    48004998        ];
     4999    }
     5000
     5001    /**
     5002     * Force multiple channels (even if phpseclib has decided to disable them)
     5003     */
     5004    public function forceMultipleChannels()
     5005    {
     5006        $this->errorOnMultipleChannels = false;
    48015007    }
    48025008
  • mihdan-no-external-links/trunk/admin/Admin.php

    r3096422 r3369336  
    155155     *
    156156     * @since    4.0.0
    157      */
    158     public function enqueue_scripts( $hook ): void {
     157     *
     158     * @param string $hook The current admin page.
     159     */
     160    public function enqueue_scripts( string $hook ): void {
    159161        wp_enqueue_script(
    160162            $this->plugin_name,
     
    436438        register_setting(
    437439            $this->plugin_name . '-settings',
    438             $this->options_prefix . 'masking_type'
     440            $this->options_prefix . 'masking_type',
     441            [
     442                'sanitize_callback' => 'sanitize_text_field',
     443            ]
    439444        );
    440445
    441446        register_setting(
    442447            $this->plugin_name . '-settings',
    443             $this->options_prefix . 'redirect_time'
     448            $this->options_prefix . 'redirect_time',
     449            [
     450                'sanitize_callback' => 'sanitize_text_field',
     451            ]
    444452        );
    445453
     
    455463        register_setting(
    456464            $this->plugin_name . '-settings',
    457             $this->options_prefix . 'mask_links'
     465            $this->options_prefix . 'mask_links',
     466            [
     467                'sanitize_callback' => 'sanitize_text_field',
     468            ]
    458469        );
    459470
    460471        register_setting(
    461472            $this->plugin_name . '-settings',
    462             $this->options_prefix . 'mask_posts_pages'
     473            $this->options_prefix . 'mask_posts_pages',
     474            [
     475                'sanitize_callback' => 'intval',
     476            ]
    463477        );
    464478
    465479        register_setting(
    466480            $this->plugin_name . '-settings',
    467             $this->options_prefix . 'mask_comments'
     481            $this->options_prefix . 'mask_comments',
     482            [
     483                'sanitize_callback' => 'intval',
     484            ]
    468485        );
    469486
    470487        register_setting(
    471488            $this->plugin_name . '-settings',
    472             $this->options_prefix . 'mask_comment_author'
     489            $this->options_prefix . 'mask_comment_author',
     490            [
     491                'sanitize_callback' => 'intval',
     492            ]
    473493        );
    474494
    475495        register_setting(
    476496            $this->plugin_name . '-settings',
    477             $this->options_prefix . 'mask_rss'
     497            $this->options_prefix . 'mask_rss',
     498            [
     499                'sanitize_callback' => 'intval',
     500            ]
    478501        );
    479502
    480503        register_setting(
    481504            $this->plugin_name . '-settings',
    482             $this->options_prefix . 'mask_rss_comments'
     505            $this->options_prefix . 'mask_rss_comments',
     506            [
     507                'sanitize_callback' => 'intval',
     508            ]
    483509        );
    484510
     
    494520        register_setting(
    495521            $this->plugin_name . '-settings',
    496             $this->options_prefix . 'nofollow'
     522            $this->options_prefix . 'nofollow',
     523            [
     524                'sanitize_callback' => 'intval',
     525            ]
    497526        );
    498527
    499528        register_setting(
    500529            $this->plugin_name . '-settings',
    501             $this->options_prefix . 'target_blank'
     530            $this->options_prefix . 'target_blank',
     531            [
     532                'sanitize_callback' => 'intval',
     533            ]
    502534        );
    503535
    504536        register_setting(
    505537            $this->plugin_name . '-settings',
    506             $this->options_prefix . 'noindex_tag'
     538            $this->options_prefix . 'noindex_tag',
     539            [
     540                'sanitize_callback' => 'intval',
     541            ]
    507542        );
    508543
    509544        register_setting(
    510545            $this->plugin_name . '-settings',
    511             $this->options_prefix . 'noindex_comment'
     546            $this->options_prefix . 'noindex_comment',
     547            [
     548                'sanitize_callback' => 'intval',
     549            ]
    512550        );
    513551
     
    528566        register_setting(
    529567            $this->plugin_name . '-settings-seo-hide',
    530             $this->options_prefix . 'seo_hide'
     568            $this->options_prefix . 'seo_hide',
     569            [
     570                'sanitize_callback' => 'intval',
     571            ]
    531572        );
    532573
     
    565606        register_setting(
    566607            $this->plugin_name . '-settings-seo-hide',
    567             $this->options_prefix . 'seo_hide_include_list'
     608            $this->options_prefix . 'seo_hide_include_list',
     609            [
     610                'sanitize_callback' => 'wp_kses_post',
     611            ]
    568612        );
    569613
     
    585629        register_setting(
    586630            $this->plugin_name . '-settings-seo-hide',
    587             $this->options_prefix . 'seo_hide_mode'
     631            $this->options_prefix . 'seo_hide_mode',
     632            [
     633                'sanitize_callback' => 'sanitize_text_field',
     634            ]
    588635        );
    589636
     
    607654        register_setting(
    608655            $this->plugin_name . '-settings-seo-hide',
    609             $this->options_prefix . 'seo_hide_exclude_list'
     656            $this->options_prefix . 'seo_hide_exclude_list',
     657            [
     658                'sanitize_callback' => [ $this, 'wp_kses_post' ],
     659            ]
    610660        );
    611661
     
    621671        register_setting(
    622672            $this->plugin_name . '-settings-advanced',
    623             $this->options_prefix . 'logging'
     673            $this->options_prefix . 'logging',
     674            [
     675                'sanitize_callback' => 'intval',
     676            ]
    624677        );
    625678
    626679        register_setting(
    627680            $this->plugin_name . '-settings-advanced',
    628             $this->options_prefix . 'log_duration'
     681            $this->options_prefix . 'log_duration',
     682            [
     683                'sanitize_callback' => 'intval',
     684            ]
    629685        );
    630686
     
    639695        register_setting(
    640696            $this->plugin_name . '-settings-advanced',
    641             $this->options_prefix . 'anonymize_links'
     697            $this->options_prefix . 'anonymize_links',
     698            [
     699                'sanitize_callback' => 'intval',
     700            ]
    642701        );
    643702
    644703        register_setting(
    645704            $this->plugin_name . '-settings-advanced',
    646             $this->options_prefix . 'anonymous_link_provider'
     705            $this->options_prefix . 'anonymous_link_provider',
     706            [
     707                'sanitize_callback' => 'sanitize_text_field',
     708            ]
    647709        );
    648710
     
    657719        register_setting(
    658720            $this->plugin_name . '-settings-advanced',
    659             $this->options_prefix . 'bot_targeting'
     721            $this->options_prefix . 'bot_targeting',
     722            [
     723                'sanitize_callback' => 'sanitize_text_field',
     724            ]
    660725        );
    661726
    662727        register_setting(
    663728            $this->plugin_name . '-settings-advanced',
    664             $this->options_prefix . 'bots_selector'
     729            $this->options_prefix . 'bots_selector',
     730            [
     731                'sanitize_callback' => [ $this, 'sanitize_text_array_deep' ],
     732            ]
    665733        );
    666734
     
    676744        register_setting(
    677745            $this->plugin_name . '-settings-advanced',
    678             $this->options_prefix . 'check_referrer'
     746            $this->options_prefix . 'check_referrer',
     747            [
     748                'sanitize_callback' => 'intval',
     749            ]
    679750        );
    680751
    681752        register_setting(
    682753            $this->plugin_name . '-settings-advanced',
    683             $this->options_prefix . 'remove_all_links'
     754            $this->options_prefix . 'remove_all_links',
     755            [
     756                'sanitize_callback' => 'intval',
     757            ]
    684758        );
    685759
    686760        register_setting(
    687761            $this->plugin_name . '-settings-advanced',
    688             $this->options_prefix . 'links_to_text'
     762            $this->options_prefix . 'links_to_text',
     763            [
     764                'sanitize_callback' => 'intval',
     765            ]
    689766        );
    690767
     
    700777        register_setting(
    701778            $this->plugin_name . '-settings-advanced',
    702             $this->options_prefix . 'debug_mode'
     779            $this->options_prefix . 'debug_mode',
     780            [
     781                'sanitize_callback' => 'intval',
     782            ]
    703783        );
    704784
     
    731811        register_setting(
    732812            $this->plugin_name . '-settings-links',
    733             $this->options_prefix . 'link_structure'
     813            $this->options_prefix . 'link_structure',
     814            [
     815                'sanitize_callback' => 'sanitize_text_field',
     816            ]
    734817        );
    735818
     
    801884        register_setting(
    802885            $this->plugin_name . '-settings-links',
    803             $this->options_prefix . 'link_encoding'
     886            $this->options_prefix . 'link_encoding',
     887            [
     888                'sanitize_callback' => 'sanitize_text_field',
     889            ]
    804890        );
    805891
     
    876962        register_setting(
    877963            $this->plugin_name . '-settings-links',
    878             $this->options_prefix . 'link_shortening'
     964            $this->options_prefix . 'link_shortening',
     965            [
     966                'sanitize_callback' => 'sanitize_text_field',
     967            ]
    879968        );
    880969
     
    9621051        register_setting(
    9631052            $this->plugin_name . '-settings-advanced',
    964             $this->options_prefix . 'redirect_message'
     1053            $this->options_prefix . 'redirect_message',
     1054            [
     1055                'sanitize_callback' => 'wp_kses_post',
     1056            ]
    9651057        );
    9661058
     
    9761068        register_setting(
    9771069            $this->plugin_name . '-settings-advanced',
    978             $this->options_prefix . 'redirect_page'
     1070            $this->options_prefix . 'redirect_page',
     1071            [
     1072                'sanitize_callback' => 'intval',
     1073            ]
    9791074        );
    9801075
     
    9951090        register_setting(
    9961091            $this->plugin_name . '-settings-include-exclude',
    997             $this->options_prefix . 'inclusion_list'
     1092            $this->options_prefix . 'inclusion_list',
     1093            [
     1094                'sanitize_callback' => 'wp_kses_post',
     1095            ]
    9981096        );
    9991097
     
    10141112        register_setting(
    10151113            $this->plugin_name . '-settings-include-exclude',
    1016             $this->options_prefix . 'exclusion_list'
     1114            $this->options_prefix . 'exclusion_list',
     1115            [
     1116                'sanitize_callback' => 'wp_kses_post',
     1117            ]
    10171118        );
    10181119
     
    10331134        register_setting(
    10341135            $this->plugin_name . '-settings-include-exclude',
    1035             $this->options_prefix . 'skip_auth'
     1136            $this->options_prefix . 'skip_auth',
     1137            [
     1138                'sanitize_callback' => 'intval',
     1139            ]
    10361140        );
    10371141
     
    10521156        register_setting(
    10531157            $this->plugin_name . '-settings-include-exclude',
    1054             $this->options_prefix . 'skip_follow'
     1158            $this->options_prefix . 'skip_follow',
     1159            [
     1160                'sanitize_callback' => 'intval',
     1161            ]
    10551162        );
    10561163
     
    22502357        return $actions;
    22512358    }
     2359
     2360    /**
     2361     * Sanitizes an array of strings from user input or from the database.
     2362     *
     2363     * @param array $array Array to sanitize.
     2364     *
     2365     * @return array
     2366     */
     2367    public function sanitize_text_array_deep( array $array ): array {
     2368        return array_map( 'sanitize_text_field', $array );
     2369    }
    22522370}
  • mihdan-no-external-links/trunk/admin/LogTable.php

    r2783436 r3369336  
    9999                    'delete' => sprintf(
    100100                        '<a href="?page=%s&action=%s&log=%s&_wpnonce=%s">Delete</a>',
    101                         isset( $_REQUEST['page'] ) ? absint( $_REQUEST['page'] ) : 1,
     101                        isset( $_REQUEST['page'] ) ? sanitize_text_field( $_REQUEST['page'] ) : 1,
    102102                        'delete',
    103103                        absint( $item['id'] ),
     
    294294            : '';
    295295
    296         if ( ! empty( $nonce ) && ! wp_verify_nonce( $nonce, $this->options_prefix . 'delete_log' ) ) {
    297             wp_die( esc_html__( 'Are you sure you want to do this?' ) );
    298         }
    299 
    300         if ( 'delete' === $this->current_action() ) {
     296        if ( 'delete' === $this->current_action() && wp_verify_nonce( $nonce, $this->options_prefix . 'delete_log' ) ) {
    301297
    302298            $delete_count = 0;
     
    312308        }
    313309
    314         if (
    315             ( isset( $_POST['action'] ) && 'bulk-delete' === $_POST['action'] ) ||
    316             ( isset( $_POST['action2'] ) && 'bulk-delete' === $_POST['action2'] )
    317         ) {
     310        if ( 'bulk-delete' === $this->current_action() && wp_verify_nonce( $nonce, 'bulk-' . $this->_args['plural'] ) ) {
    318311
    319312            $delete_ids = ! empty( $_POST['bulk-delete'] )
    320                 ? array_map( 'sanitize_text_field', wp_unslash( $_POST['bulk-delete'] ) )
     313                ? array_map( 'intval', wp_unslash( $_POST['bulk-delete'] ) )
    321314                : [];
    322315
     
    335328            exit;
    336329        }
    337 
    338330    }
    339331
  • mihdan-no-external-links/trunk/admin/MaskTable.php

    r2783436 r3369336  
    241241            'cb'      => '<input type="checkbox" />',
    242242            'title'   => __( 'URL', $this->plugin_name ),
    243             'mask'    => __( 'Mask' ),
    244             'numeric' => __( 'Numeric' ),
     243            'mask'    => __( 'Mask', $this->plugin_name ),
     244            'numeric' => __( 'Numeric', $this->plugin_name ),
    245245        ];
    246246
     
    317317
    318318        if ( ! empty( $nonce ) && ! wp_verify_nonce( $nonce, $this->options_prefix . 'delete_mask' ) ) {
    319             wp_die( esc_html__( 'Are you sure you want to do this?' ) );
     319            wp_die( esc_html__( 'Are you sure you want to do this?', $this->plugin_name ) );
    320320        }
    321321
  • mihdan-no-external-links/trunk/admin/SiteHealth.php

    r2782193 r3369336  
    7575            'status'      => 'good',
    7676            'badge'       => [
    77                 'label' => __( 'Performance' ),
     77                'label' => __( 'Performance', $this->plugin_name ),
    7878                'color' => 'blue',
    7979            ],
     
    8686                'status'      => 'critical',
    8787                'badge'       => [
    88                     'label' => __( 'Performance' ),
     88                    'label' => __( 'Performance', $this->plugin_name ),
    8989                    'color' => 'red',
    9090                ],
  • mihdan-no-external-links/trunk/includes/Main.php

    r3007441 r3369336  
    116116        $this->define_admin_hooks();
    117117        $this->define_public_hooks();
    118 
    119118    }
    120119
     
    264263        $plugin_i18n = new I18n( $this->get_plugin_name() );
    265264
    266         $this->loader->add_action( 'plugins_loaded', $plugin_i18n, 'load_plugin_textdomain' );
     265        $this->loader->add_action( 'init', $plugin_i18n, 'load_plugin_textdomain' );
    267266
    268267    }
     
    342341            'skip_follow'             => false,
    343342            'redirect_page'           => 0,
    344             'redirect_message'        => __(
    345                 'You will be redirected in 3 seconds. If your browser does not automatically redirect you, please <a href="%linkurl%">click here</a>.',
    346                 $this->plugin_name
    347             ),
     343            'redirect_message'        => 'You will be redirected in 3 seconds. If your browser does not automatically redirect you, please <a href="%linkurl%">click here</a>.',
    348344            'output_buffer'           => $output_buffer,
    349345        );
    350346
    351347        $this->options = $this->validate_options( $options );
    352 
    353348    }
    354349
     
    547542
    548543        $this->loader->add_filter( 'set-screen-option', $this->admin, 'mask_page_set_screen_options', null, 3 );
    549         $hook_name = vsprintf(
    550             'load-%s_page_%s-masks',
    551             [ strtolower( sanitize_file_name( __( 'No External Links', $this->plugin_name ) ) ), $this->get_plugin_name() ]
    552         );
     544
     545        $hook_name = sprintf( 'load-no-external-links_page_%s-masks', $this->get_plugin_name() );
    553546
    554547        $this->loader->add_action( $hook_name, $this->admin, 'mask_page_screen_options' );
     
    556549        $this->loader->add_filter( 'set-screen-option', $this->admin, 'log_page_set_screen_options', null, 3 );
    557550
    558         $hook_name = vsprintf(
    559             'load-%s_page_%s-logs',
    560             [ strtolower( sanitize_file_name( __( 'No External Links', $this->plugin_name ) ) ), $this->get_plugin_name() ]
    561         );
     551        $hook_name = sprintf( 'load-no-external-links_page_%s-logs', $this->get_plugin_name() );
    562552
    563553        $this->loader->add_action( $hook_name, $this->admin, 'log_page_screen_options' );
  • mihdan-no-external-links/trunk/mihdan-no-external-links.php

    r3369209 r3369336  
    1111 * Plugin URI:        https://www.kobzarev.com/projects/no-external-links/
    1212 * Description:       Convert external links into internal links, site wide or post/page specific. Add NoFollow, Click logging, and more...
    13  * Version:           5.1.5
     13 * Version:           5.1.6
    1414 * Author:            Mikhail Kobzarev
    1515 * Author URI:        https://www.kobzarev.com/
     
    2929
    3030const MIHDAN_NO_EXTERNAL_LINKS_DIR     = __DIR__;
    31 const MIHDAN_NO_EXTERNAL_LINKS_VERSION = '5.1.5';
     31const MIHDAN_NO_EXTERNAL_LINKS_VERSION = '5.1.6';
    3232const MIHDAN_NO_EXTERNAL_LINKS_SLUG    = 'mihdan-no-external-links';
    3333
  • mihdan-no-external-links/trunk/readme.txt

    r3369209 r3369336  
    55Requires at least: 5.7.4
    66Tested up to: 6.8
    7 Stable tag: 5.1.5
     7Stable tag: 5.1.6
    88Requires PHP: 7.4
    99License: GPLv2 or later
     
    6363
    6464== Changelog ==
     65
     66= 5.1.6 (28.09.2025) =
     67* Resolved #[39](https://github.com/mihdan/mihdan-no-external-links/issues/39)
     68* Resolved #[40](https://github.com/mihdan/mihdan-no-external-links/issues/40)
     69* The error "Function _load_textdomain_just_in_time was called incorrectly" has been fixed
    6570
    6671= 5.1.5 (28.09.2025) =
  • mihdan-no-external-links/trunk/vendor/composer/installed.json

    r2900866 r3369336  
    197197        {
    198198            "name": "phpseclib/phpseclib",
    199             "version": "3.0.19",
    200             "version_normalized": "3.0.19.0",
     199            "version": "3.0.34",
     200            "version_normalized": "3.0.34.0",
    201201            "source": {
    202202                "type": "git",
    203203                "url": "https://github.com/phpseclib/phpseclib.git",
    204                 "reference": "cc181005cf548bfd8a4896383bb825d859259f95"
     204                "reference": "56c79f16a6ae17e42089c06a2144467acc35348a"
    205205            },
    206206            "dist": {
    207207                "type": "zip",
    208                 "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/cc181005cf548bfd8a4896383bb825d859259f95",
    209                 "reference": "cc181005cf548bfd8a4896383bb825d859259f95",
     208                "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/56c79f16a6ae17e42089c06a2144467acc35348a",
     209                "reference": "56c79f16a6ae17e42089c06a2144467acc35348a",
    210210                "shasum": ""
    211211            },
     
    225225                "ext-openssl": "Install the OpenSSL extension in order to speed up a wide variety of cryptographic operations."
    226226            },
    227             "time": "2023-03-05T17:13:09+00:00",
     227            "time": "2023-11-27T11:13:31+00:00",
    228228            "type": "library",
    229229            "installation-source": "dist",
     
    290290            "support": {
    291291                "issues": "https://github.com/phpseclib/phpseclib/issues",
    292                 "source": "https://github.com/phpseclib/phpseclib/tree/3.0.19"
     292                "source": "https://github.com/phpseclib/phpseclib/tree/3.0.34"
    293293            },
    294294            "funding": [
  • mihdan-no-external-links/trunk/vendor/composer/installed.php

    r3369209 r3369336  
    22    'root' => array(
    33        'name' => 'mihdan/mihdan-no-external-links',
    4         'pretty_version' => '5.1.5',
    5         'version' => '5.1.5.0',
    6         'reference' => 'a6d16d367a6ceb1bbe468219125173021c653f99',
     4        'pretty_version' => '5.1.6',
     5        'version' => '5.1.6.0',
     6        'reference' => 'eda3a90f6434e53d5238700433c3b2aa66195b62',
    77        'type' => 'wordpress-plugin',
    88        'install_path' => __DIR__ . '/../../',
     
    1212    'versions' => array(
    1313        'mihdan/mihdan-no-external-links' => array(
    14             'pretty_version' => '5.1.5',
    15             'version' => '5.1.5.0',
    16             'reference' => 'a6d16d367a6ceb1bbe468219125173021c653f99',
     14            'pretty_version' => '5.1.6',
     15            'version' => '5.1.6.0',
     16            'reference' => 'eda3a90f6434e53d5238700433c3b2aa66195b62',
    1717            'type' => 'wordpress-plugin',
    1818            'install_path' => __DIR__ . '/../../',
     
    4848        ),
    4949        'phpseclib/phpseclib' => array(
    50             'pretty_version' => '3.0.19',
    51             'version' => '3.0.19.0',
    52             'reference' => 'cc181005cf548bfd8a4896383bb825d859259f95',
     50            'pretty_version' => '3.0.34',
     51            'version' => '3.0.34.0',
     52            'reference' => '56c79f16a6ae17e42089c06a2144467acc35348a',
    5353            'type' => 'library',
    5454            'install_path' => __DIR__ . '/../phpseclib/phpseclib',
  • mihdan-no-external-links/trunk/vendor/phpseclib/phpseclib/BACKERS.md

    r2900866 r3369336  
    1414- Tharyrok
    1515- [cjhaas](https://github.com/cjhaas)
     16- [istiak-tridip](https://github.com/istiak-tridip)
  • mihdan-no-external-links/trunk/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/AsymmetricKey.php

    r2900866 r3369336  
    131131     * @param string $key
    132132     * @param string $password optional
    133      * @return AsymmetricKey
     133     * @return \phpseclib3\Crypt\Common\PublicKey|\phpseclib3\Crypt\Common\PrivateKey
    134134     */
    135135    public static function load($key, $password = false)
  • mihdan-no-external-links/trunk/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/Formats/Keys/PKCS8.php

    r2900866 r3369336  
    142142            case 'RC2':
    143143                $cipher = new RC2('cbc');
     144                $cipher->setKeyLength(64);
    144145                break;
    145146            case '3-KeyTripleDES':
     
    219220        switch ($algo) {
    220221            case 'desCBC':
    221                 $cipher = new TripleDES('cbc');
     222                $cipher = new DES('cbc');
    222223                break;
    223224            case 'des-EDE3-CBC':
  • mihdan-no-external-links/trunk/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/SymmetricKey.php

    r2900866 r3369336  
    669669                // PHP_OS & "\xDF\xDF\xDF" == strtoupper(substr(PHP_OS, 0, 3)), but a lot faster
    670670                case (PHP_OS & "\xDF\xDF\xDF") === 'WIN':
    671                 case (php_uname('m') & "\xDF\xDF\xDF") != 'ARM':
     671                case !(is_string(php_uname('m')) && (php_uname('m') & "\xDF\xDF\xDF") == 'ARM'):
    672672                case defined('PHP_INT_SIZE') && PHP_INT_SIZE == 8:
    673673                    self::$use_reg_intval = true;
    674674                    break;
    675                 case (php_uname('m') & "\xDF\xDF\xDF") == 'ARM':
     675                case is_string(php_uname('m')) && (php_uname('m') & "\xDF\xDF\xDF") == 'ARM':
    676676                    switch (true) {
    677677                        /* PHP 7.0.0 introduced a bug that affected 32-bit ARM processors:
     
    918918     * @param string $password
    919919     * @param string $method
    920      * @param string[] ...$func_args
     920     * @param int|string ...$func_args
    921921     * @throws \LengthException if pbkdf1 is being used and the derived key length exceeds the hash length
    922922     * @throws \RuntimeException if bcrypt is being used and a salt isn't provided
  • mihdan-no-external-links/trunk/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php

    r2900866 r3369336  
    842842            self::ENCRYPTION_NONE
    843843        ];
    844         $numSelected = 0;
     844        $encryptedCount = 0;
    845845        $selected = 0;
    846846        foreach ($masks as $mask) {
    847847            if ($padding & $mask) {
    848848                $selected = $mask;
    849                 $numSelected++;
     849                $encryptedCount++;
    850850            }
    851851        }
    852         if ($numSelected > 1) {
     852        if ($encryptedCount > 1) {
    853853            throw new InconsistentSetupException('Multiple encryption padding modes have been selected; at most only one should be selected');
    854854        }
     
    860860            self::SIGNATURE_PKCS1
    861861        ];
    862         $numSelected = 0;
     862        $signatureCount = 0;
    863863        $selected = 0;
    864864        foreach ($masks as $mask) {
    865865            if ($padding & $mask) {
    866866                $selected = $mask;
    867                 $numSelected++;
     867                $signatureCount++;
    868868            }
    869869        }
    870         if ($numSelected > 1) {
     870        if ($signatureCount > 1) {
    871871            throw new InconsistentSetupException('Multiple signature padding modes have been selected; at most only one should be selected');
    872872        }
     
    874874
    875875        $new = clone $this;
    876         $new->encryptionPadding = $encryptionPadding;
    877         $new->signaturePadding = $signaturePadding;
     876        if ($encryptedCount) {
     877            $new->encryptionPadding = $encryptionPadding;
     878        }
     879        if ($signatureCount) {
     880            $new->signaturePadding = $signaturePadding;
     881        }
    878882        return $new;
    879883    }
  • mihdan-no-external-links/trunk/vendor/phpseclib/phpseclib/phpseclib/Crypt/Rijndael.php

    r2782199 r3369336  
    834834        // Generating encrypt code:
    835835        $init_encrypt .= '
    836             static $tables;
    837836            if (empty($tables)) {
    838837                $tables = &$this->getTables();
     
    891890        // Generating decrypt code:
    892891        $init_decrypt .= '
    893             static $invtables;
    894892            if (empty($invtables)) {
    895893                $invtables = &$this->getInvTables();
     
    948946        $this->inline_crypt = $this->createInlineCryptFunction(
    949947            [
    950                'init_crypt'    => '',
     948               'init_crypt'    => 'static $tables; static $invtables;',
    951949               'init_encrypt'  => $init_encrypt,
    952950               'init_decrypt'  => $init_decrypt,
  • mihdan-no-external-links/trunk/vendor/phpseclib/phpseclib/phpseclib/File/ASN1.php

    r2782199 r3369336  
    2222namespace phpseclib3\File;
    2323
    24 use DateTime;
    2524use phpseclib3\Common\Functions\Strings;
    2625use phpseclib3\File\ASN1\Element;
     
    206205        }
    207206
    208         return [self::decode_ber($encoded)];
     207        return [$decoded];
    209208    }
    210209
     
    14041403                    }
    14051404                    break;
    1406                 case ($c & 0x80000000) != 0:
     1405                case ($c & (PHP_INT_SIZE == 8 ? 0x80000000 : (1 << 31))) != 0:
    14071406                    return false;
    14081407                case $c >= 0x04000000:
  • mihdan-no-external-links/trunk/vendor/phpseclib/phpseclib/phpseclib/File/X509.php

    r2900866 r3369336  
    165165     * @var array
    166166     */
    167     private $CAs;
     167    private $CAs = [];
    168168
    169169    /**
     
    316316                'id-at-role' => '2.5.4.72',
    317317                'id-at-postalAddress' => '2.5.4.16',
     318                'jurisdictionOfIncorporationCountryName' => '1.3.6.1.4.1.311.60.2.1.3',
     319                'jurisdictionOfIncorporationStateOrProvinceName' => '1.3.6.1.4.1.311.60.2.1.2',
     320                'jurisdictionLocalityName' => '1.3.6.1.4.1.311.60.2.1.1',
     321                'id-at-businessCategory' => '2.5.4.15',
    318322
    319323                //'id-domainComponent' => '0.9.2342.19200300.100.1.25',
     
    10391043            foreach ($names as $name) {
    10401044                foreach ($name as $key => $value) {
    1041                     $value = str_replace(['.', '*'], ['\.', '[^.]*'], $value);
     1045                    $value = preg_quote($value);
     1046                    $value = str_replace('\*', '[^.]*', $value);
    10421047                    switch ($key) {
    10431048                        case 'dNSName':
     
    15391544    {
    15401545        switch (strtolower($propName)) {
     1546            case 'jurisdictionofincorporationcountryname':
     1547            case 'jurisdictioncountryname':
     1548            case 'jurisdictionc':
     1549                return 'jurisdictionOfIncorporationCountryName';
     1550            case 'jurisdictionofincorporationstateorprovincename':
     1551            case 'jurisdictionstateorprovincename':
     1552            case 'jurisdictionst':
     1553                return 'jurisdictionOfIncorporationStateOrProvinceName';
     1554            case 'jurisdictionlocalityname':
     1555            case 'jurisdictionl':
     1556                return 'jurisdictionLocalityName';
     1557            case 'id-at-businesscategory':
     1558            case 'businesscategory':
     1559                return 'id-at-businessCategory';
    15411560            case 'id-at-countryname':
    15421561            case 'countryname':
     
    20312050            return false;
    20322051        }
    2033         if (empty($this->CAs)) {
    2034             return $chain;
    2035         }
    20362052        while (true) {
    20372053            $currentCert = $chain[count($chain) - 1];
  • mihdan-no-external-links/trunk/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger.php

    r2900866 r3369336  
    101101        self::$mainEngine = $fqmain;
    102102
    103         if (!in_array('Default', $modexps)) {
    104             $modexps[] = 'DefaultEngine';
    105         }
    106 
    107103        $found = false;
    108104        foreach ($modexps as $modexp) {
     
    141137        if (!isset(self::$mainEngine)) {
    142138            $engines = [
    143                 ['GMP'],
     139                ['GMP', ['DefaultEngine']],
    144140                ['PHP64', ['OpenSSL']],
    145141                ['BCMath', ['OpenSSL']],
    146                 ['PHP32', ['OpenSSL']]
     142                ['PHP32', ['OpenSSL']],
     143                ['PHP64', ['DefaultEngine']],
     144                ['PHP32', ['DefaultEngine']]
    147145            ];
     146
    148147            foreach ($engines as $engine) {
    149148                try {
    150                     self::setEngine($engine[0], isset($engine[1]) ? $engine[1] : []);
    151                     break;
     149                    self::setEngine($engine[0], $engine[1]);
     150                    return;
    152151                } catch (\Exception $e) {
    153152                }
    154153            }
     154
     155            throw new \UnexpectedValueException('No valid BigInteger found. This is only possible when JIT is enabled on Windows and neither the GMP or BCMath extensions are available so either disable JIT or install GMP / BCMath');
    155156        }
    156157    }
  • mihdan-no-external-links/trunk/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/Engine.php

    r2900866 r3369336  
    645645        }
    646646
     647        if ($this->compare($n) > 0) {
     648            list(, $temp) = $this->divide($n);
     649            return $temp->powModInner($e, $n);
     650        }
     651
    647652        return $this->powModInner($e, $n);
    648653    }
  • mihdan-no-external-links/trunk/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP.php

    r2782199 r3369336  
    13271327        return array_reverse($vals);
    13281328    }
     1329
     1330    /**
     1331     * @return bool
     1332     */
     1333    protected static function testJITOnWindows()
     1334    {
     1335        // see https://github.com/php/php-src/issues/11917
     1336        if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN' && function_exists('opcache_get_status') && PHP_VERSION_ID < 80213 && !defined('PHPSECLIB_ALLOW_JIT')) {
     1337            $status = opcache_get_status();
     1338            if ($status && isset($status['jit']) && $status['jit']['enabled'] && $status['jit']['on']) {
     1339                return true;
     1340            }
     1341        }
     1342        return false;
     1343    }
    13291344}
  • mihdan-no-external-links/trunk/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP32.php

    r2782199 r3369336  
    8181            $step = count($vals) & 3;
    8282            if ($step) {
    83                 $digit = floor($digit / pow(2, 2 * $step));
     83                $digit = (int) floor($digit / pow(2, 2 * $step));
    8484            }
    8585            if ($step != 3) {
    86                 $digit &= static::MAX_DIGIT;
     86                $digit = (int) fmod($digit, static::BASE_FULL);
    8787                $i++;
    8888            }
     
    103103    public static function isValidEngine()
    104104    {
    105         return PHP_INT_SIZE >= 4;
     105        return PHP_INT_SIZE >= 4 && !self::testJITOnWindows();
    106106    }
    107107
  • mihdan-no-external-links/trunk/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP64.php

    r2782199 r3369336  
    104104    public static function isValidEngine()
    105105    {
    106         return PHP_INT_SIZE >= 8;
     106        return PHP_INT_SIZE >= 8 && !self::testJITOnWindows();
    107107    }
    108108
  • mihdan-no-external-links/trunk/vendor/phpseclib/phpseclib/phpseclib/Math/BinaryField.php

    r2782199 r3369336  
    4949    {
    5050        $m = array_shift($indices);
     51        if ($m > 571) {
     52            /* sect571r1 and sect571k1 are the largest binary curves that https://www.secg.org/sec2-v2.pdf defines
     53               altho theoretically there may be legit reasons to use binary finite fields with larger degrees
     54               imposing a limit on the maximum size is both reasonable and precedented. in particular,
     55               http://tools.ietf.org/html/rfc4253#section-6.1 (The Secure Shell (SSH) Transport Layer Protocol) says
     56               "implementations SHOULD check that the packet length is reasonable in order for the implementation to
     57                avoid denial of service and/or buffer overflow attacks" */
     58            throw new \OutOfBoundsException('Degrees larger than 571 are not supported');
     59        }
    5160        $val = str_repeat('0', $m) . '1';
    5261        foreach ($indices as $index) {
  • mihdan-no-external-links/trunk/vendor/phpseclib/phpseclib/phpseclib/Math/PrimeField/Integer.php

    r2900866 r3369336  
    264264
    265265        while (!$t->equals($one)) {
    266             for ($i == clone $one; $i->compare($m) < 0; $i = $i->add($one)) {
     266            for ($i = clone $one; $i->compare($m) < 0; $i = $i->add($one)) {
    267267                if ($t->powMod($two->pow($i), static::$modulo[$this->instanceID])->equals($one)) {
    268268                    break;
     
    313313    public function toBytes()
    314314    {
    315         $length = static::$modulo[$this->instanceID]->getLengthInBytes();
    316         return str_pad($this->value->toBytes(), $length, "\0", STR_PAD_LEFT);
     315        if (isset(static::$modulo[$this->instanceID])) {
     316            $length = static::$modulo[$this->instanceID]->getLengthInBytes();
     317            return str_pad($this->value->toBytes(), $length, "\0", STR_PAD_LEFT);
     318        }
     319        return $this->value->toBytes();
    317320    }
    318321
  • mihdan-no-external-links/trunk/vendor/phpseclib/phpseclib/phpseclib/Net/SFTP.php

    r2900866 r3369336  
    9494     * @access private
    9595     */
    96     private $packet_types = [];
     96    private static $packet_types = [];
    9797
    9898    /**
     
    103103     * @access private
    104104     */
    105     private $status_codes = [];
     105    private static $status_codes = [];
    106106
    107107    /** @var array<int, string> */
    108     private $attributes;
     108    private static $attributes;
    109109
    110110    /** @var array<int, string> */
    111     private $open_flags;
     111    private static $open_flags;
    112112
    113113    /** @var array<int, string> */
    114     private $open_flags5;
     114    private static $open_flags5;
    115115
    116116    /** @var array<int, string> */
    117     private $file_types;
     117    private static $file_types;
    118118
    119119    /**
     
    351351     * Connects to an SFTP server
    352352     *
    353      * @param string $host
     353     * $host can either be a string, representing the host, or a stream resource.
     354     *
     355     * @param mixed $host
    354356     * @param int $port
    355357     * @param int $timeout
     
    361363        $this->max_sftp_packet = 1 << 15;
    362364
    363         $this->packet_types = [
    364             1  => 'NET_SFTP_INIT',
    365             2  => 'NET_SFTP_VERSION',
    366             3  => 'NET_SFTP_OPEN',
    367             4  => 'NET_SFTP_CLOSE',
    368             5  => 'NET_SFTP_READ',
    369             6  => 'NET_SFTP_WRITE',
    370             7  => 'NET_SFTP_LSTAT',
    371             9  => 'NET_SFTP_SETSTAT',
    372             10 => 'NET_SFTP_FSETSTAT',
    373             11 => 'NET_SFTP_OPENDIR',
    374             12 => 'NET_SFTP_READDIR',
    375             13 => 'NET_SFTP_REMOVE',
    376             14 => 'NET_SFTP_MKDIR',
    377             15 => 'NET_SFTP_RMDIR',
    378             16 => 'NET_SFTP_REALPATH',
    379             17 => 'NET_SFTP_STAT',
    380             18 => 'NET_SFTP_RENAME',
    381             19 => 'NET_SFTP_READLINK',
    382             20 => 'NET_SFTP_SYMLINK',
    383             21 => 'NET_SFTP_LINK',
    384 
    385             101 => 'NET_SFTP_STATUS',
    386             102 => 'NET_SFTP_HANDLE',
    387             103 => 'NET_SFTP_DATA',
    388             104 => 'NET_SFTP_NAME',
    389             105 => 'NET_SFTP_ATTRS',
    390 
    391             200 => 'NET_SFTP_EXTENDED'
    392         ];
    393         $this->status_codes = [
    394             0 => 'NET_SFTP_STATUS_OK',
    395             1 => 'NET_SFTP_STATUS_EOF',
    396             2 => 'NET_SFTP_STATUS_NO_SUCH_FILE',
    397             3 => 'NET_SFTP_STATUS_PERMISSION_DENIED',
    398             4 => 'NET_SFTP_STATUS_FAILURE',
    399             5 => 'NET_SFTP_STATUS_BAD_MESSAGE',
    400             6 => 'NET_SFTP_STATUS_NO_CONNECTION',
    401             7 => 'NET_SFTP_STATUS_CONNECTION_LOST',
    402             8 => 'NET_SFTP_STATUS_OP_UNSUPPORTED',
    403             9 => 'NET_SFTP_STATUS_INVALID_HANDLE',
    404             10 => 'NET_SFTP_STATUS_NO_SUCH_PATH',
    405             11 => 'NET_SFTP_STATUS_FILE_ALREADY_EXISTS',
    406             12 => 'NET_SFTP_STATUS_WRITE_PROTECT',
    407             13 => 'NET_SFTP_STATUS_NO_MEDIA',
    408             14 => 'NET_SFTP_STATUS_NO_SPACE_ON_FILESYSTEM',
    409             15 => 'NET_SFTP_STATUS_QUOTA_EXCEEDED',
    410             16 => 'NET_SFTP_STATUS_UNKNOWN_PRINCIPAL',
    411             17 => 'NET_SFTP_STATUS_LOCK_CONFLICT',
    412             18 => 'NET_SFTP_STATUS_DIR_NOT_EMPTY',
    413             19 => 'NET_SFTP_STATUS_NOT_A_DIRECTORY',
    414             20 => 'NET_SFTP_STATUS_INVALID_FILENAME',
    415             21 => 'NET_SFTP_STATUS_LINK_LOOP',
    416             22 => 'NET_SFTP_STATUS_CANNOT_DELETE',
    417             23 => 'NET_SFTP_STATUS_INVALID_PARAMETER',
    418             24 => 'NET_SFTP_STATUS_FILE_IS_A_DIRECTORY',
    419             25 => 'NET_SFTP_STATUS_BYTE_RANGE_LOCK_CONFLICT',
    420             26 => 'NET_SFTP_STATUS_BYTE_RANGE_LOCK_REFUSED',
    421             27 => 'NET_SFTP_STATUS_DELETE_PENDING',
    422             28 => 'NET_SFTP_STATUS_FILE_CORRUPT',
    423             29 => 'NET_SFTP_STATUS_OWNER_INVALID',
    424             30 => 'NET_SFTP_STATUS_GROUP_INVALID',
    425             31 => 'NET_SFTP_STATUS_NO_MATCHING_BYTE_RANGE_LOCK'
    426         ];
    427         // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-7.1
    428         // the order, in this case, matters quite a lot - see \phpseclib3\Net\SFTP::_parseAttributes() to understand why
    429         $this->attributes = [
    430             0x00000001 => 'NET_SFTP_ATTR_SIZE',
    431             0x00000002 => 'NET_SFTP_ATTR_UIDGID',          // defined in SFTPv3, removed in SFTPv4+
    432             0x00000080 => 'NET_SFTP_ATTR_OWNERGROUP',      // defined in SFTPv4+
    433             0x00000004 => 'NET_SFTP_ATTR_PERMISSIONS',
    434             0x00000008 => 'NET_SFTP_ATTR_ACCESSTIME',
    435             0x00000010 => 'NET_SFTP_ATTR_CREATETIME',      // SFTPv4+
    436             0x00000020 => 'NET_SFTP_ATTR_MODIFYTIME',
    437             0x00000040 => 'NET_SFTP_ATTR_ACL',
    438             0x00000100 => 'NET_SFTP_ATTR_SUBSECOND_TIMES',
    439             0x00000200 => 'NET_SFTP_ATTR_BITS',            // SFTPv5+
    440             0x00000400 => 'NET_SFTP_ATTR_ALLOCATION_SIZE', // SFTPv6+
    441             0x00000800 => 'NET_SFTP_ATTR_TEXT_HINT',
    442             0x00001000 => 'NET_SFTP_ATTR_MIME_TYPE',
    443             0x00002000 => 'NET_SFTP_ATTR_LINK_COUNT',
    444             0x00004000 => 'NET_SFTP_ATTR_UNTRANSLATED_NAME',
    445             0x00008000 => 'NET_SFTP_ATTR_CTIME',
    446             // 0x80000000 will yield a floating point on 32-bit systems and converting floating points to integers
    447             // yields inconsistent behavior depending on how php is compiled.  so we left shift -1 (which, in
    448             // two's compliment, consists of all 1 bits) by 31.  on 64-bit systems this'll yield 0xFFFFFFFF80000000.
    449             // that's not a problem, however, and 'anded' and a 32-bit number, as all the leading 1 bits are ignored.
    450             (PHP_INT_SIZE == 4 ? -1 : 0xFFFFFFFF) => 'NET_SFTP_ATTR_EXTENDED'
    451         ];
    452         // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-6.3
    453         // the flag definitions change somewhat in SFTPv5+.  if SFTPv5+ support is added to this library, maybe name
    454         // the array for that $this->open5_flags and similarly alter the constant names.
    455         $this->open_flags = [
    456             0x00000001 => 'NET_SFTP_OPEN_READ',
    457             0x00000002 => 'NET_SFTP_OPEN_WRITE',
    458             0x00000004 => 'NET_SFTP_OPEN_APPEND',
    459             0x00000008 => 'NET_SFTP_OPEN_CREATE',
    460             0x00000010 => 'NET_SFTP_OPEN_TRUNCATE',
    461             0x00000020 => 'NET_SFTP_OPEN_EXCL',
    462             0x00000040 => 'NET_SFTP_OPEN_TEXT' // defined in SFTPv4
    463         ];
    464         // SFTPv5+ changed the flags up:
    465         // https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-13#section-8.1.1.3
    466         $this->open_flags5 = [
    467             // when SSH_FXF_ACCESS_DISPOSITION is a 3 bit field that controls how the file is opened
    468             0x00000000 => 'NET_SFTP_OPEN_CREATE_NEW',
    469             0x00000001 => 'NET_SFTP_OPEN_CREATE_TRUNCATE',
    470             0x00000002 => 'NET_SFTP_OPEN_OPEN_EXISTING',
    471             0x00000003 => 'NET_SFTP_OPEN_OPEN_OR_CREATE',
    472             0x00000004 => 'NET_SFTP_OPEN_TRUNCATE_EXISTING',
    473             // the rest of the flags are not supported
    474             0x00000008 => 'NET_SFTP_OPEN_APPEND_DATA', // "the offset field of SS_FXP_WRITE requests is ignored"
    475             0x00000010 => 'NET_SFTP_OPEN_APPEND_DATA_ATOMIC',
    476             0x00000020 => 'NET_SFTP_OPEN_TEXT_MODE',
    477             0x00000040 => 'NET_SFTP_OPEN_BLOCK_READ',
    478             0x00000080 => 'NET_SFTP_OPEN_BLOCK_WRITE',
    479             0x00000100 => 'NET_SFTP_OPEN_BLOCK_DELETE',
    480             0x00000200 => 'NET_SFTP_OPEN_BLOCK_ADVISORY',
    481             0x00000400 => 'NET_SFTP_OPEN_NOFOLLOW',
    482             0x00000800 => 'NET_SFTP_OPEN_DELETE_ON_CLOSE',
    483             0x00001000 => 'NET_SFTP_OPEN_ACCESS_AUDIT_ALARM_INFO',
    484             0x00002000 => 'NET_SFTP_OPEN_ACCESS_BACKUP',
    485             0x00004000 => 'NET_SFTP_OPEN_BACKUP_STREAM',
    486             0x00008000 => 'NET_SFTP_OPEN_OVERRIDE_OWNER',
    487         ];
    488         // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-5.2
    489         // see \phpseclib3\Net\SFTP::_parseLongname() for an explanation
    490         $this->file_types = [
    491             1 => 'NET_SFTP_TYPE_REGULAR',
    492             2 => 'NET_SFTP_TYPE_DIRECTORY',
    493             3 => 'NET_SFTP_TYPE_SYMLINK',
    494             4 => 'NET_SFTP_TYPE_SPECIAL',
    495             5 => 'NET_SFTP_TYPE_UNKNOWN',
    496             // the following types were first defined for use in SFTPv5+
    497             // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-05#section-5.2
    498             6 => 'NET_SFTP_TYPE_SOCKET',
    499             7 => 'NET_SFTP_TYPE_CHAR_DEVICE',
    500             8 => 'NET_SFTP_TYPE_BLOCK_DEVICE',
    501             9 => 'NET_SFTP_TYPE_FIFO'
    502         ];
    503         $this->define_array(
    504             $this->packet_types,
    505             $this->status_codes,
    506             $this->attributes,
    507             $this->open_flags,
    508             $this->open_flags5,
    509             $this->file_types
    510         );
     365        if (empty(self::$packet_types)) {
     366            self::$packet_types = [
     367                1  => 'NET_SFTP_INIT',
     368                2  => 'NET_SFTP_VERSION',
     369                3  => 'NET_SFTP_OPEN',
     370                4  => 'NET_SFTP_CLOSE',
     371                5  => 'NET_SFTP_READ',
     372                6  => 'NET_SFTP_WRITE',
     373                7  => 'NET_SFTP_LSTAT',
     374                9  => 'NET_SFTP_SETSTAT',
     375                10 => 'NET_SFTP_FSETSTAT',
     376                11 => 'NET_SFTP_OPENDIR',
     377                12 => 'NET_SFTP_READDIR',
     378                13 => 'NET_SFTP_REMOVE',
     379                14 => 'NET_SFTP_MKDIR',
     380                15 => 'NET_SFTP_RMDIR',
     381                16 => 'NET_SFTP_REALPATH',
     382                17 => 'NET_SFTP_STAT',
     383                18 => 'NET_SFTP_RENAME',
     384                19 => 'NET_SFTP_READLINK',
     385                20 => 'NET_SFTP_SYMLINK',
     386                21 => 'NET_SFTP_LINK',
     387
     388                101 => 'NET_SFTP_STATUS',
     389                102 => 'NET_SFTP_HANDLE',
     390                103 => 'NET_SFTP_DATA',
     391                104 => 'NET_SFTP_NAME',
     392                105 => 'NET_SFTP_ATTRS',
     393
     394                200 => 'NET_SFTP_EXTENDED'
     395            ];
     396            self::$status_codes = [
     397                0 => 'NET_SFTP_STATUS_OK',
     398                1 => 'NET_SFTP_STATUS_EOF',
     399                2 => 'NET_SFTP_STATUS_NO_SUCH_FILE',
     400                3 => 'NET_SFTP_STATUS_PERMISSION_DENIED',
     401                4 => 'NET_SFTP_STATUS_FAILURE',
     402                5 => 'NET_SFTP_STATUS_BAD_MESSAGE',
     403                6 => 'NET_SFTP_STATUS_NO_CONNECTION',
     404                7 => 'NET_SFTP_STATUS_CONNECTION_LOST',
     405                8 => 'NET_SFTP_STATUS_OP_UNSUPPORTED',
     406                9 => 'NET_SFTP_STATUS_INVALID_HANDLE',
     407                10 => 'NET_SFTP_STATUS_NO_SUCH_PATH',
     408                11 => 'NET_SFTP_STATUS_FILE_ALREADY_EXISTS',
     409                12 => 'NET_SFTP_STATUS_WRITE_PROTECT',
     410                13 => 'NET_SFTP_STATUS_NO_MEDIA',
     411                14 => 'NET_SFTP_STATUS_NO_SPACE_ON_FILESYSTEM',
     412                15 => 'NET_SFTP_STATUS_QUOTA_EXCEEDED',
     413                16 => 'NET_SFTP_STATUS_UNKNOWN_PRINCIPAL',
     414                17 => 'NET_SFTP_STATUS_LOCK_CONFLICT',
     415                18 => 'NET_SFTP_STATUS_DIR_NOT_EMPTY',
     416                19 => 'NET_SFTP_STATUS_NOT_A_DIRECTORY',
     417                20 => 'NET_SFTP_STATUS_INVALID_FILENAME',
     418                21 => 'NET_SFTP_STATUS_LINK_LOOP',
     419                22 => 'NET_SFTP_STATUS_CANNOT_DELETE',
     420                23 => 'NET_SFTP_STATUS_INVALID_PARAMETER',
     421                24 => 'NET_SFTP_STATUS_FILE_IS_A_DIRECTORY',
     422                25 => 'NET_SFTP_STATUS_BYTE_RANGE_LOCK_CONFLICT',
     423                26 => 'NET_SFTP_STATUS_BYTE_RANGE_LOCK_REFUSED',
     424                27 => 'NET_SFTP_STATUS_DELETE_PENDING',
     425                28 => 'NET_SFTP_STATUS_FILE_CORRUPT',
     426                29 => 'NET_SFTP_STATUS_OWNER_INVALID',
     427                30 => 'NET_SFTP_STATUS_GROUP_INVALID',
     428                31 => 'NET_SFTP_STATUS_NO_MATCHING_BYTE_RANGE_LOCK'
     429            ];
     430            // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-7.1
     431            // the order, in this case, matters quite a lot - see \phpseclib3\Net\SFTP::_parseAttributes() to understand why
     432            self::$attributes = [
     433                0x00000001 => 'NET_SFTP_ATTR_SIZE',
     434                0x00000002 => 'NET_SFTP_ATTR_UIDGID',          // defined in SFTPv3, removed in SFTPv4+
     435                0x00000080 => 'NET_SFTP_ATTR_OWNERGROUP',      // defined in SFTPv4+
     436                0x00000004 => 'NET_SFTP_ATTR_PERMISSIONS',
     437                0x00000008 => 'NET_SFTP_ATTR_ACCESSTIME',
     438                0x00000010 => 'NET_SFTP_ATTR_CREATETIME',      // SFTPv4+
     439                0x00000020 => 'NET_SFTP_ATTR_MODIFYTIME',
     440                0x00000040 => 'NET_SFTP_ATTR_ACL',
     441                0x00000100 => 'NET_SFTP_ATTR_SUBSECOND_TIMES',
     442                0x00000200 => 'NET_SFTP_ATTR_BITS',            // SFTPv5+
     443                0x00000400 => 'NET_SFTP_ATTR_ALLOCATION_SIZE', // SFTPv6+
     444                0x00000800 => 'NET_SFTP_ATTR_TEXT_HINT',
     445                0x00001000 => 'NET_SFTP_ATTR_MIME_TYPE',
     446                0x00002000 => 'NET_SFTP_ATTR_LINK_COUNT',
     447                0x00004000 => 'NET_SFTP_ATTR_UNTRANSLATED_NAME',
     448                0x00008000 => 'NET_SFTP_ATTR_CTIME',
     449                // 0x80000000 will yield a floating point on 32-bit systems and converting floating points to integers
     450                // yields inconsistent behavior depending on how php is compiled.  so we left shift -1 (which, in
     451                // two's compliment, consists of all 1 bits) by 31.  on 64-bit systems this'll yield 0xFFFFFFFF80000000.
     452                // that's not a problem, however, and 'anded' and a 32-bit number, as all the leading 1 bits are ignored.
     453                (PHP_INT_SIZE == 4 ? (-1 << 31) : 0x80000000) => 'NET_SFTP_ATTR_EXTENDED'
     454            ];
     455            // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-6.3
     456            // the flag definitions change somewhat in SFTPv5+.  if SFTPv5+ support is added to this library, maybe name
     457            // the array for that $this->open5_flags and similarly alter the constant names.
     458            self::$open_flags = [
     459                0x00000001 => 'NET_SFTP_OPEN_READ',
     460                0x00000002 => 'NET_SFTP_OPEN_WRITE',
     461                0x00000004 => 'NET_SFTP_OPEN_APPEND',
     462                0x00000008 => 'NET_SFTP_OPEN_CREATE',
     463                0x00000010 => 'NET_SFTP_OPEN_TRUNCATE',
     464                0x00000020 => 'NET_SFTP_OPEN_EXCL',
     465                0x00000040 => 'NET_SFTP_OPEN_TEXT' // defined in SFTPv4
     466            ];
     467            // SFTPv5+ changed the flags up:
     468            // https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-13#section-8.1.1.3
     469            self::$open_flags5 = [
     470                // when SSH_FXF_ACCESS_DISPOSITION is a 3 bit field that controls how the file is opened
     471                0x00000000 => 'NET_SFTP_OPEN_CREATE_NEW',
     472                0x00000001 => 'NET_SFTP_OPEN_CREATE_TRUNCATE',
     473                0x00000002 => 'NET_SFTP_OPEN_OPEN_EXISTING',
     474                0x00000003 => 'NET_SFTP_OPEN_OPEN_OR_CREATE',
     475                0x00000004 => 'NET_SFTP_OPEN_TRUNCATE_EXISTING',
     476                // the rest of the flags are not supported
     477                0x00000008 => 'NET_SFTP_OPEN_APPEND_DATA', // "the offset field of SS_FXP_WRITE requests is ignored"
     478                0x00000010 => 'NET_SFTP_OPEN_APPEND_DATA_ATOMIC',
     479                0x00000020 => 'NET_SFTP_OPEN_TEXT_MODE',
     480                0x00000040 => 'NET_SFTP_OPEN_BLOCK_READ',
     481                0x00000080 => 'NET_SFTP_OPEN_BLOCK_WRITE',
     482                0x00000100 => 'NET_SFTP_OPEN_BLOCK_DELETE',
     483                0x00000200 => 'NET_SFTP_OPEN_BLOCK_ADVISORY',
     484                0x00000400 => 'NET_SFTP_OPEN_NOFOLLOW',
     485                0x00000800 => 'NET_SFTP_OPEN_DELETE_ON_CLOSE',
     486                0x00001000 => 'NET_SFTP_OPEN_ACCESS_AUDIT_ALARM_INFO',
     487                0x00002000 => 'NET_SFTP_OPEN_ACCESS_BACKUP',
     488                0x00004000 => 'NET_SFTP_OPEN_BACKUP_STREAM',
     489                0x00008000 => 'NET_SFTP_OPEN_OVERRIDE_OWNER',
     490            ];
     491            // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-5.2
     492            // see \phpseclib3\Net\SFTP::_parseLongname() for an explanation
     493            self::$file_types = [
     494                1 => 'NET_SFTP_TYPE_REGULAR',
     495                2 => 'NET_SFTP_TYPE_DIRECTORY',
     496                3 => 'NET_SFTP_TYPE_SYMLINK',
     497                4 => 'NET_SFTP_TYPE_SPECIAL',
     498                5 => 'NET_SFTP_TYPE_UNKNOWN',
     499                // the following types were first defined for use in SFTPv5+
     500                // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-05#section-5.2
     501                6 => 'NET_SFTP_TYPE_SOCKET',
     502                7 => 'NET_SFTP_TYPE_CHAR_DEVICE',
     503                8 => 'NET_SFTP_TYPE_BLOCK_DEVICE',
     504                9 => 'NET_SFTP_TYPE_FIFO'
     505            ];
     506            self::define_array(
     507                self::$packet_types,
     508                self::$status_codes,
     509                self::$attributes,
     510                self::$open_flags,
     511                self::$open_flags5,
     512                self::$file_types
     513            );
     514        }
    511515
    512516        if (!defined('NET_SFTP_QUEUE_SIZE')) {
     
    544548    private function partial_init_sftp_connection()
    545549    {
    546         $this->window_size_server_to_client[self::CHANNEL] = $this->window_size;
    547 
    548         $packet = Strings::packSSH2(
    549             'CsN3',
    550             NET_SSH2_MSG_CHANNEL_OPEN,
    551             'session',
    552             self::CHANNEL,
    553             $this->window_size,
    554             0x4000
    555         );
    556 
    557         $this->send_binary_packet($packet);
    558 
    559         $this->channel_status[self::CHANNEL] = NET_SSH2_MSG_CHANNEL_OPEN;
    560 
    561         $response = $this->get_channel_packet(self::CHANNEL, true);
     550        $response = $this->openChannel(self::CHANNEL, true);
    562551        if ($response === true && $this->isTimeout()) {
    563552            return false;
     
    816805        }
    817806
    818         $error = $this->status_codes[$status];
     807        $error = self::$status_codes[$status];
    819808
    820809        if ($this->version > 2) {
     
    21392128        if ($start >= 0) {
    21402129            $offset = $start;
    2141         } elseif ($mode & self::RESUME) {
     2130        } elseif ($mode & (self::RESUME | self::RESUME_START)) {
    21422131            // if NET_SFTP_OPEN_APPEND worked as it should _size() wouldn't need to be called
    21432132            $size = $this->stat($remote_file)['size'];
     
    22112200                fseek($fp, $local_start);
    22122201                $size -= $local_start;
     2202            } elseif ($mode & self::RESUME) {
     2203                fseek($fp, $offset);
     2204                $size -= $offset;
    22132205            }
    22142206        } elseif ($dataCallback) {
     
    24982490        }
    24992491
    2500         if ($length > 0 && $length <= $offset - $start) {
    2501             if ($local_file === false) {
    2502                 $content = substr($content, 0, $length);
    2503             } else {
    2504                 ftruncate($fp, $length + $res_offset);
    2505             }
    2506         }
    2507 
    25082492        if ($fclose_check) {
    25092493            fclose($fp);
     
    28422826
    28432827    /**
     2828     * Recursively go through rawlist() output to get the total filesize
     2829     *
     2830     * @return int
     2831     */
     2832    private static function recursiveFilesize(array $files)
     2833    {
     2834        $size = 0;
     2835        foreach ($files as $name => $file) {
     2836            if ($name == '.' || $name == '..') {
     2837                continue;
     2838            }
     2839            $size += is_array($file) ?
     2840                self::recursiveFilesize($file) :
     2841                $file->size;
     2842        }
     2843        return $size;
     2844    }
     2845
     2846    /**
    28442847     * Gets file size
    28452848     *
    28462849     * @param string $path
     2850     * @param bool $recursive
    28472851     * @return mixed
    28482852     */
    2849     public function filesize($path)
    2850     {
    2851         return $this->get_stat_cache_prop($path, 'size');
     2853    public function filesize($path, $recursive = false)
     2854    {
     2855        return !$recursive || $this->filetype($path) != 'dir' ?
     2856            $this->get_stat_cache_prop($path, 'size') :
     2857            self::recursiveFilesize($this->rawlist($path, true));
    28522858    }
    28532859
     
    30423048        }
    30433049
    3044         foreach ($this->attributes as $key => $value) {
     3050        foreach (self::$attributes as $key => $value) {
    30453051            switch ($flags & $key) {
    30463052                case NET_SFTP_ATTR_UIDGID:
     
    32733279
    32743280        if (defined('NET_SFTP_LOGGING')) {
    3275             $packet_type = '-> ' . $this->packet_types[$type] .
     3281            $packet_type = '-> ' . self::$packet_types[$type] .
    32763282                           ' (' . round($stop - $start, 4) . 's)';
    32773283            $this->append_log($packet_type, $data);
     
    33773383
    33783384        if (defined('NET_SFTP_LOGGING')) {
    3379             $packet_type = '<- ' . $this->packet_types[$this->packet_type] .
     3385            $packet_type = '<- ' . self::$packet_types[$this->packet_type] .
    33803386                           ' (' . round($stop - $start, 4) . 's)';
    33813387            $this->append_log($packet_type, $packet);
     
    34213427     * 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')
    34223428     *
    3423      * @return array|string
     3429     * @return array|string|false
    34243430     */
    34253431    public function getSFTPLog()
  • mihdan-no-external-links/trunk/vendor/phpseclib/phpseclib/phpseclib/Net/SSH2.php

    r2900866 r3369336  
    554554     * @access private
    555555     */
    556     private $message_numbers = [];
     556    private static $message_numbers = [];
    557557
    558558    /**
     
    563563     * @access private
    564564     */
    565     private $disconnect_reasons = [];
     565    private static $disconnect_reasons = [];
    566566
    567567    /**
     
    572572     * @access private
    573573     */
    574     private $channel_open_failure_reasons = [];
     574    private static $channel_open_failure_reasons = [];
    575575
    576576    /**
     
    582582     * @access private
    583583     */
    584     private $terminal_modes = [];
     584    private static $terminal_modes = [];
    585585
    586586    /**
     
    592592     * @access private
    593593     */
    594     private $channel_extended_data_type_codes = [];
     594    private static $channel_extended_data_type_codes = [];
    595595
    596596    /**
     
    648648
    649649    /**
     650     * The identifier of the interactive channel which was opened most recently
     651     *
     652     * @see self::getInteractiveChannelId()
     653     * @var int
     654     */
     655    private $channel_id_last_interactive = 0;
     656
     657    /**
    650658     * Packet Size
    651659     *
     
    837845     */
    838846    private $request_pty = false;
    839 
    840     /**
    841      * Flag set while exec() is running when using enablePTY()
    842      *
    843      * @var bool
    844      */
    845     private $in_request_pty_exec = false;
    846 
    847     /**
    848      * Flag set after startSubsystem() is called
    849      *
    850      * @var bool
    851      */
    852     private $in_subsystem;
    853847
    854848    /**
     
    10951089
    10961090    /**
     1091     * How many channels are currently opened
     1092     *
     1093     * @var int
     1094     */
     1095    private $channelCount = 0;
     1096
     1097    /**
     1098     * Does the server support multiple channels? If not then error out
     1099     * when multiple channels are attempted to be opened
     1100     *
     1101     * @var bool
     1102     */
     1103    private $errorOnMultipleChannels;
     1104
     1105    /**
    10971106     * Default Constructor.
    10981107     *
     
    11061115    public function __construct($host, $port = 22, $timeout = 10)
    11071116    {
    1108         $this->message_numbers = [
    1109             1 => 'NET_SSH2_MSG_DISCONNECT',
    1110             2 => 'NET_SSH2_MSG_IGNORE',
    1111             3 => 'NET_SSH2_MSG_UNIMPLEMENTED',
    1112             4 => 'NET_SSH2_MSG_DEBUG',
    1113             5 => 'NET_SSH2_MSG_SERVICE_REQUEST',
    1114             6 => 'NET_SSH2_MSG_SERVICE_ACCEPT',
    1115             20 => 'NET_SSH2_MSG_KEXINIT',
    1116             21 => 'NET_SSH2_MSG_NEWKEYS',
    1117             30 => 'NET_SSH2_MSG_KEXDH_INIT',
    1118             31 => 'NET_SSH2_MSG_KEXDH_REPLY',
    1119             50 => 'NET_SSH2_MSG_USERAUTH_REQUEST',
    1120             51 => 'NET_SSH2_MSG_USERAUTH_FAILURE',
    1121             52 => 'NET_SSH2_MSG_USERAUTH_SUCCESS',
    1122             53 => 'NET_SSH2_MSG_USERAUTH_BANNER',
    1123 
    1124             80 => 'NET_SSH2_MSG_GLOBAL_REQUEST',
    1125             81 => 'NET_SSH2_MSG_REQUEST_SUCCESS',
    1126             82 => 'NET_SSH2_MSG_REQUEST_FAILURE',
    1127             90 => 'NET_SSH2_MSG_CHANNEL_OPEN',
    1128             91 => 'NET_SSH2_MSG_CHANNEL_OPEN_CONFIRMATION',
    1129             92 => 'NET_SSH2_MSG_CHANNEL_OPEN_FAILURE',
    1130             93 => 'NET_SSH2_MSG_CHANNEL_WINDOW_ADJUST',
    1131             94 => 'NET_SSH2_MSG_CHANNEL_DATA',
    1132             95 => 'NET_SSH2_MSG_CHANNEL_EXTENDED_DATA',
    1133             96 => 'NET_SSH2_MSG_CHANNEL_EOF',
    1134             97 => 'NET_SSH2_MSG_CHANNEL_CLOSE',
    1135             98 => 'NET_SSH2_MSG_CHANNEL_REQUEST',
    1136             99 => 'NET_SSH2_MSG_CHANNEL_SUCCESS',
    1137             100 => 'NET_SSH2_MSG_CHANNEL_FAILURE'
    1138         ];
    1139         $this->disconnect_reasons = [
    1140             1 => 'NET_SSH2_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT',
    1141             2 => 'NET_SSH2_DISCONNECT_PROTOCOL_ERROR',
    1142             3 => 'NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED',
    1143             4 => 'NET_SSH2_DISCONNECT_RESERVED',
    1144             5 => 'NET_SSH2_DISCONNECT_MAC_ERROR',
    1145             6 => 'NET_SSH2_DISCONNECT_COMPRESSION_ERROR',
    1146             7 => 'NET_SSH2_DISCONNECT_SERVICE_NOT_AVAILABLE',
    1147             8 => 'NET_SSH2_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED',
    1148             9 => 'NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE',
    1149             10 => 'NET_SSH2_DISCONNECT_CONNECTION_LOST',
    1150             11 => 'NET_SSH2_DISCONNECT_BY_APPLICATION',
    1151             12 => 'NET_SSH2_DISCONNECT_TOO_MANY_CONNECTIONS',
    1152             13 => 'NET_SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER',
    1153             14 => 'NET_SSH2_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE',
    1154             15 => 'NET_SSH2_DISCONNECT_ILLEGAL_USER_NAME'
    1155         ];
    1156         $this->channel_open_failure_reasons = [
    1157             1 => 'NET_SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED'
    1158         ];
    1159         $this->terminal_modes = [
    1160             0 => 'NET_SSH2_TTY_OP_END'
    1161         ];
    1162         $this->channel_extended_data_type_codes = [
    1163             1 => 'NET_SSH2_EXTENDED_DATA_STDERR'
    1164         ];
    1165 
    1166         $this->define_array(
    1167             $this->message_numbers,
    1168             $this->disconnect_reasons,
    1169             $this->channel_open_failure_reasons,
    1170             $this->terminal_modes,
    1171             $this->channel_extended_data_type_codes,
    1172             [60 => 'NET_SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ'],
    1173             [60 => 'NET_SSH2_MSG_USERAUTH_PK_OK'],
    1174             [60 => 'NET_SSH2_MSG_USERAUTH_INFO_REQUEST',
    1175                   61 => 'NET_SSH2_MSG_USERAUTH_INFO_RESPONSE'],
    1176             // RFC 4419 - diffie-hellman-group-exchange-sha{1,256}
    1177             [30 => 'NET_SSH2_MSG_KEXDH_GEX_REQUEST_OLD',
    1178                   31 => 'NET_SSH2_MSG_KEXDH_GEX_GROUP',
    1179                   32 => 'NET_SSH2_MSG_KEXDH_GEX_INIT',
    1180                   33 => 'NET_SSH2_MSG_KEXDH_GEX_REPLY',
    1181                   34 => 'NET_SSH2_MSG_KEXDH_GEX_REQUEST'],
    1182             // RFC 5656 - Elliptic Curves (for curve25519-sha256@libssh.org)
    1183             [30 => 'NET_SSH2_MSG_KEX_ECDH_INIT',
    1184                   31 => 'NET_SSH2_MSG_KEX_ECDH_REPLY']
    1185         );
     1117        if (empty(self::$message_numbers)) {
     1118            self::$message_numbers = [
     1119                1 => 'NET_SSH2_MSG_DISCONNECT',
     1120                2 => 'NET_SSH2_MSG_IGNORE',
     1121                3 => 'NET_SSH2_MSG_UNIMPLEMENTED',
     1122                4 => 'NET_SSH2_MSG_DEBUG',
     1123                5 => 'NET_SSH2_MSG_SERVICE_REQUEST',
     1124                6 => 'NET_SSH2_MSG_SERVICE_ACCEPT',
     1125                7 => 'NET_SSH2_MSG_EXT_INFO', // RFC 8308
     1126                20 => 'NET_SSH2_MSG_KEXINIT',
     1127                21 => 'NET_SSH2_MSG_NEWKEYS',
     1128                30 => 'NET_SSH2_MSG_KEXDH_INIT',
     1129                31 => 'NET_SSH2_MSG_KEXDH_REPLY',
     1130                50 => 'NET_SSH2_MSG_USERAUTH_REQUEST',
     1131                51 => 'NET_SSH2_MSG_USERAUTH_FAILURE',
     1132                52 => 'NET_SSH2_MSG_USERAUTH_SUCCESS',
     1133                53 => 'NET_SSH2_MSG_USERAUTH_BANNER',
     1134
     1135                80 => 'NET_SSH2_MSG_GLOBAL_REQUEST',
     1136                81 => 'NET_SSH2_MSG_REQUEST_SUCCESS',
     1137                82 => 'NET_SSH2_MSG_REQUEST_FAILURE',
     1138                90 => 'NET_SSH2_MSG_CHANNEL_OPEN',
     1139                91 => 'NET_SSH2_MSG_CHANNEL_OPEN_CONFIRMATION',
     1140                92 => 'NET_SSH2_MSG_CHANNEL_OPEN_FAILURE',
     1141                93 => 'NET_SSH2_MSG_CHANNEL_WINDOW_ADJUST',
     1142                94 => 'NET_SSH2_MSG_CHANNEL_DATA',
     1143                95 => 'NET_SSH2_MSG_CHANNEL_EXTENDED_DATA',
     1144                96 => 'NET_SSH2_MSG_CHANNEL_EOF',
     1145                97 => 'NET_SSH2_MSG_CHANNEL_CLOSE',
     1146                98 => 'NET_SSH2_MSG_CHANNEL_REQUEST',
     1147                99 => 'NET_SSH2_MSG_CHANNEL_SUCCESS',
     1148                100 => 'NET_SSH2_MSG_CHANNEL_FAILURE'
     1149            ];
     1150            self::$disconnect_reasons = [
     1151                1 => 'NET_SSH2_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT',
     1152                2 => 'NET_SSH2_DISCONNECT_PROTOCOL_ERROR',
     1153                3 => 'NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED',
     1154                4 => 'NET_SSH2_DISCONNECT_RESERVED',
     1155                5 => 'NET_SSH2_DISCONNECT_MAC_ERROR',
     1156                6 => 'NET_SSH2_DISCONNECT_COMPRESSION_ERROR',
     1157                7 => 'NET_SSH2_DISCONNECT_SERVICE_NOT_AVAILABLE',
     1158                8 => 'NET_SSH2_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED',
     1159                9 => 'NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE',
     1160                10 => 'NET_SSH2_DISCONNECT_CONNECTION_LOST',
     1161                11 => 'NET_SSH2_DISCONNECT_BY_APPLICATION',
     1162                12 => 'NET_SSH2_DISCONNECT_TOO_MANY_CONNECTIONS',
     1163                13 => 'NET_SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER',
     1164                14 => 'NET_SSH2_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE',
     1165                15 => 'NET_SSH2_DISCONNECT_ILLEGAL_USER_NAME'
     1166            ];
     1167            self::$channel_open_failure_reasons = [
     1168                1 => 'NET_SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED'
     1169            ];
     1170            self::$terminal_modes = [
     1171                0 => 'NET_SSH2_TTY_OP_END'
     1172            ];
     1173            self::$channel_extended_data_type_codes = [
     1174                1 => 'NET_SSH2_EXTENDED_DATA_STDERR'
     1175            ];
     1176
     1177            self::define_array(
     1178                self::$message_numbers,
     1179                self::$disconnect_reasons,
     1180                self::$channel_open_failure_reasons,
     1181                self::$terminal_modes,
     1182                self::$channel_extended_data_type_codes,
     1183                [60 => 'NET_SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ'],
     1184                [60 => 'NET_SSH2_MSG_USERAUTH_PK_OK'],
     1185                [60 => 'NET_SSH2_MSG_USERAUTH_INFO_REQUEST',
     1186                      61 => 'NET_SSH2_MSG_USERAUTH_INFO_RESPONSE'],
     1187                // RFC 4419 - diffie-hellman-group-exchange-sha{1,256}
     1188                [30 => 'NET_SSH2_MSG_KEXDH_GEX_REQUEST_OLD',
     1189                      31 => 'NET_SSH2_MSG_KEXDH_GEX_GROUP',
     1190                      32 => 'NET_SSH2_MSG_KEXDH_GEX_INIT',
     1191                      33 => 'NET_SSH2_MSG_KEXDH_GEX_REPLY',
     1192                      34 => 'NET_SSH2_MSG_KEXDH_GEX_REQUEST'],
     1193                // RFC 5656 - Elliptic Curves (for curve25519-sha256@libssh.org)
     1194                [30 => 'NET_SSH2_MSG_KEX_ECDH_INIT',
     1195                      31 => 'NET_SSH2_MSG_KEX_ECDH_REPLY']
     1196            );
     1197        }
    11861198
    11871199        /**
     
    12681280    {
    12691281        $this->send_kex_first = false;
     1282    }
     1283
     1284    /**
     1285     * stream_select wrapper
     1286     *
     1287     * Quoting https://stackoverflow.com/a/14262151/569976,
     1288     * "The general approach to `EINTR` is to simply handle the error and retry the operation again"
     1289     *
     1290     * This wrapper does that loop
     1291     */
     1292    private static function stream_select(&$read, &$write, &$except, $seconds, $microseconds = null)
     1293    {
     1294        $remaining = $seconds + $microseconds / 1000000;
     1295        $start = microtime(true);
     1296        while (true) {
     1297            $result = @stream_select($read, $write, $except, $seconds, $microseconds);
     1298            if ($result !== false) {
     1299                return $result;
     1300            }
     1301            $elapsed = microtime(true) - $start;
     1302            $seconds = (int) ($remaining - floor($elapsed));
     1303            $microseconds = (int) (1000000 * ($remaining - $seconds));
     1304            if ($elapsed >= $remaining) {
     1305                return false;
     1306            }
     1307        }
    12701308    }
    12711309
     
    13341372                    $sec = (int) floor($this->curTimeout);
    13351373                    $usec = (int) (1000000 * ($this->curTimeout - $sec));
    1336                     if (@stream_select($read, $write, $except, $sec, $usec) === false) {
     1374                    if (static::stream_select($read, $write, $except, $sec, $usec) === false) {
    13371375                        throw new \RuntimeException('Connection timed out whilst receiving server identification string');
    13381376                    }
     
    13881426            throw new UnableToConnectException("Cannot connect to SSH $matches[3] servers");
    13891427        }
     1428
     1429        // Ubuntu's OpenSSH from 5.8 to 6.9 didn't work with multiple channels. see
     1430        // https://bugs.launchpad.net/ubuntu/+source/openssh/+bug/1334916 for more info.
     1431        // https://lists.ubuntu.com/archives/oneiric-changes/2011-July/005772.html discusses
     1432        // when consolekit was incorporated.
     1433        // https://marc.info/?l=openssh-unix-dev&m=163409903417589&w=2 discusses some of the
     1434        // issues with how Ubuntu incorporated consolekit
     1435        $pattern = '#^SSH-2\.0-OpenSSH_([\d.]+)[^ ]* Ubuntu-.*$#';
     1436        $match = preg_match($pattern, $this->server_identifier, $matches);
     1437        $match = $match && version_compare('5.8', $matches[1], '<=');
     1438        $match = $match && version_compare('6.9', $matches[1], '>=');
     1439        $this->errorOnMultipleChannels = $match;
    13901440
    13911441        if (!$this->send_id_string_first) {
     
    14871537            SSH2::getSupportedCompressionAlgorithms();
    14881538
     1539        $kex_algorithms = array_merge($kex_algorithms, array('ext-info-c'));
     1540
    14891541        // some SSH servers have buggy implementations of some of the above algorithms
    14901542        switch (true) {
     
    15011553                        $c2s_mac_algorithms,
    15021554                        ['hmac-sha1-96', 'hmac-md5-96']
     1555                    ));
     1556                }
     1557                break;
     1558            case substr($this->server_identifier, 0, 24) == 'SSH-2.0-TurboFTP_SERVER_':
     1559                if (!isset($preferred['server_to_client']['crypt'])) {
     1560                    $s2c_encryption_algorithms = array_values(array_diff(
     1561                        $s2c_encryption_algorithms,
     1562                        ['aes128-gcm@openssh.com', 'aes256-gcm@openssh.com']
     1563                    ));
     1564                }
     1565                if (!isset($preferred['client_to_server']['crypt'])) {
     1566                    $c2s_encryption_algorithms = array_values(array_diff(
     1567                        $c2s_encryption_algorithms,
     1568                        ['aes128-gcm@openssh.com', 'aes256-gcm@openssh.com']
    15031569                    ));
    15041570                }
     
    21222188     *
    21232189     * @param string $username
    2124      * @param string|AsymmetricKey|array[]|Agent|null ...$args
     2190     * @param string|PrivateKey|array[]|Agent|null ...$args
    21252191     * @return bool
    21262192     * @see self::_login()
     
    21472213     *
    21482214     * @param string $username
    2149      * @param string ...$args
     2215     * @param string|PrivateKey|array[]|Agent|null ...$args
    21502216     * @return bool
    21512217     * @see self::_login_helper()
     
    22672333                }
    22682334                $this->disconnect_helper(NET_SSH2_DISCONNECT_CONNECTION_LOST);
    2269                 throw new ConnectionClosedException('Connection closed by server');
    2270             }
    2271 
    2272             list($type, $service) = Strings::unpackSSH2('Cs', $response);
     2335                throw $e;
     2336            }
     2337
     2338            list($type) = Strings::unpackSSH2('C', $response);
     2339
     2340            if ($type == NET_SSH2_MSG_EXT_INFO) {
     2341                list($nr_extensions) = Strings::unpackSSH2('N', $response);
     2342                for ($i = 0; $i < $nr_extensions; $i++) {
     2343                    list($extension_name, $extension_value) = Strings::unpackSSH2('ss', $response);
     2344                    if ($extension_name == 'server-sig-algs') {
     2345                        $this->supported_private_key_algorithms = explode(',', $extension_value);
     2346                    }
     2347                }
     2348
     2349                $response = $this->get_binary_packet();
     2350                list($type) = Strings::unpackSSH2('C', $response);
     2351            }
     2352
     2353            list($service) = Strings::unpackSSH2('s', $response);
     2354
    22732355            if ($type != NET_SSH2_MSG_SERVICE_ACCEPT || $service != 'ssh-userauth') {
    22742356                $this->disconnect_helper(NET_SSH2_DISCONNECT_PROTOCOL_ERROR);
     
    25462628            $algos = ['rsa-sha2-256', 'rsa-sha2-512', 'ssh-rsa'];
    25472629            if (isset($this->preferred['hostkey'])) {
    2548                 $algos = array_intersect($this->preferred['hostkey'], $algos);
     2630                $algos = array_intersect($algos, $this->preferred['hostkey']);
    25492631            }
    25502632            $algo = self::array_intersect_first($algos, $this->supported_private_key_algorithms);
     
    27302812        }
    27312813
    2732         if ($this->in_request_pty_exec) {
    2733             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.');
    2734         }
    2735 
    2736         // RFC4254 defines the (client) window size as "bytes the other party can send before it must wait for the window to
    2737         // be adjusted".  0x7FFFFFFF is, at 2GB, the max size.  technically, it should probably be decremented, but,
    2738         // honestly, if you're transferring more than 2GB, you probably shouldn't be using phpseclib, anyway.
    2739         // see http://tools.ietf.org/html/rfc4254#section-5.2 for more info
    2740         $this->window_size_server_to_client[self::CHANNEL_EXEC] = $this->window_size;
    2741         // 0x8000 is the maximum max packet size, per http://tools.ietf.org/html/rfc4253#section-6.1, although since PuTTy
    2742         // uses 0x4000, that's what will be used here, as well.
    2743         $packet_size = 0x4000;
    2744 
    2745         $packet = Strings::packSSH2(
    2746             'CsN3',
    2747             NET_SSH2_MSG_CHANNEL_OPEN,
    2748             'session',
    2749             self::CHANNEL_EXEC,
    2750             $this->window_size_server_to_client[self::CHANNEL_EXEC],
    2751             $packet_size
    2752         );
    2753         $this->send_binary_packet($packet);
    2754 
    2755         $this->channel_status[self::CHANNEL_EXEC] = NET_SSH2_MSG_CHANNEL_OPEN;
    2756 
    2757         $this->get_channel_packet(self::CHANNEL_EXEC);
     2814        //if ($this->isPTYOpen()) {
     2815        //    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.');
     2816        //}
     2817
     2818        $this->openChannel(self::CHANNEL_EXEC);
    27582819
    27592820        if ($this->request_pty === true) {
     
    27802841                throw new \RuntimeException('Unable to request pseudo-terminal');
    27812842            }
    2782 
    2783             $this->in_request_pty_exec = true;
    27842843        }
    27852844
     
    28112870        $this->channel_status[self::CHANNEL_EXEC] = NET_SSH2_MSG_CHANNEL_DATA;
    28122871
    2813         if ($this->in_request_pty_exec) {
     2872        if ($this->request_pty === true) {
     2873            $this->channel_id_last_interactive = self::CHANNEL_EXEC;
    28142874            return true;
    28152875        }
     
    28372897
    28382898    /**
    2839      * Creates an interactive shell
    2840      *
    2841      * @see self::read()
    2842      * @see self::write()
     2899     * How many channels are currently open?
     2900     *
     2901     * @return int
     2902     */
     2903    public function getOpenChannelCount()
     2904    {
     2905        return $this->channelCount;
     2906    }
     2907
     2908    /**
     2909     * Opens a channel
     2910     *
     2911     * @param string $channel
     2912     * @param bool $skip_extended
    28432913     * @return bool
    2844      * @throws \UnexpectedValueException on receipt of unexpected packets
    2845      * @throws \RuntimeException on other errors
    2846      */
    2847     private function initShell()
    2848     {
    2849         if ($this->in_request_pty_exec === true) {
    2850             return true;
    2851         }
    2852 
    2853         $this->window_size_server_to_client[self::CHANNEL_SHELL] = $this->window_size;
     2914     */
     2915    protected function openChannel($channel, $skip_extended = false)
     2916    {
     2917        if (isset($this->channel_status[$channel]) && $this->channel_status[$channel] != NET_SSH2_MSG_CHANNEL_CLOSE) {
     2918            throw new \RuntimeException('Please close the channel (' . $channel . ') before trying to open it again');
     2919        }
     2920
     2921        $this->channelCount++;
     2922
     2923        if ($this->channelCount > 1 && $this->errorOnMultipleChannels) {
     2924            throw new \RuntimeException("Ubuntu's OpenSSH from 5.8 to 6.9 doesn't work with multiple channels");
     2925        }
     2926
     2927        // RFC4254 defines the (client) window size as "bytes the other party can send before it must wait for the window to
     2928        // be adjusted".  0x7FFFFFFF is, at 2GB, the max size.  technically, it should probably be decremented, but,
     2929        // honestly, if you're transferring more than 2GB, you probably shouldn't be using phpseclib, anyway.
     2930        // see http://tools.ietf.org/html/rfc4254#section-5.2 for more info
     2931        $this->window_size_server_to_client[$channel] = $this->window_size;
     2932        // 0x8000 is the maximum max packet size, per http://tools.ietf.org/html/rfc4253#section-6.1, although since PuTTy
     2933        // uses 0x4000, that's what will be used here, as well.
    28542934        $packet_size = 0x4000;
    28552935
     
    28582938            NET_SSH2_MSG_CHANNEL_OPEN,
    28592939            'session',
    2860             self::CHANNEL_SHELL,
    2861             $this->window_size_server_to_client[self::CHANNEL_SHELL],
     2940            $channel,
     2941            $this->window_size_server_to_client[$channel],
    28622942            $packet_size
    28632943        );
     
    28652945        $this->send_binary_packet($packet);
    28662946
    2867         $this->channel_status[self::CHANNEL_SHELL] = NET_SSH2_MSG_CHANNEL_OPEN;
    2868 
    2869         $this->get_channel_packet(self::CHANNEL_SHELL);
     2947        $this->channel_status[$channel] = NET_SSH2_MSG_CHANNEL_OPEN;
     2948
     2949        return $this->get_channel_packet($channel, $skip_extended);
     2950    }
     2951
     2952    /**
     2953     * Creates an interactive shell
     2954     *
     2955     * Returns bool(true) if the shell was opened.
     2956     * Returns bool(false) if the shell was already open.
     2957     *
     2958     * @see self::isShellOpen()
     2959     * @see self::read()
     2960     * @see self::write()
     2961     * @return bool
     2962     * @throws InsufficientSetupException if not authenticated
     2963     * @throws \UnexpectedValueException on receipt of unexpected packets
     2964     * @throws \RuntimeException on other errors
     2965     */
     2966    public function openShell()
     2967    {
     2968        if (!$this->isAuthenticated()) {
     2969            throw new InsufficientSetupException('Operation disallowed prior to login()');
     2970        }
     2971
     2972        $this->openChannel(self::CHANNEL_SHELL);
    28702973
    28712974        $terminal_modes = pack('C', NET_SSH2_TTY_OP_END);
     
    29083011        $this->channel_status[self::CHANNEL_SHELL] = NET_SSH2_MSG_CHANNEL_DATA;
    29093012
     3013        $this->channel_id_last_interactive = self::CHANNEL_SHELL;
     3014
    29103015        $this->bitmap |= self::MASK_SHELL;
    29113016
     
    29143019
    29153020    /**
    2916      * Return the channel to be used with read() / write()
    2917      *
     3021     * Return the channel to be used with read(), write(), and reset(), if none were specified
     3022     * @deprecated for lack of transparency in intended channel target, to be potentially replaced
     3023     *             with method which guarantees open-ness of all yielded channels and throws
     3024     *             error for multiple open channels
    29183025     * @see self::read()
    29193026     * @see self::write()
     
    29233030    {
    29243031        switch (true) {
    2925             case $this->in_subsystem:
     3032            case $this->is_channel_status_data(self::CHANNEL_SUBSYSTEM):
    29263033                return self::CHANNEL_SUBSYSTEM;
    2927             case $this->in_request_pty_exec:
     3034            case $this->is_channel_status_data(self::CHANNEL_EXEC):
    29283035                return self::CHANNEL_EXEC;
    29293036            default:
    29303037                return self::CHANNEL_SHELL;
    29313038        }
     3039    }
     3040
     3041    /**
     3042     * Indicates the DATA status on the given channel
     3043     *
     3044     * @param int $channel The channel number to evaluate
     3045     * @return bool
     3046     */
     3047    private function is_channel_status_data($channel)
     3048    {
     3049        return isset($this->channel_status[$channel]) && $this->channel_status[$channel] == NET_SSH2_MSG_CHANNEL_DATA;
    29323050    }
    29333051
     
    29883106     * if $mode == self::READ_REGEX, a regular expression.
    29893107     *
     3108     * If not specifying a channel, an open interactive channel will be selected, or, if there are
     3109     * no open channels, an interactive shell will be created. If there are multiple open
     3110     * interactive channels, a legacy behavior will apply in which channel selection prioritizes
     3111     * an active subsystem, the exec pty, and, lastly, the shell. If using multiple interactive
     3112     * channels, callers are discouraged from relying on this legacy behavior and should specify
     3113     * the intended channel.
     3114     *
    29903115     * @see self::write()
    29913116     * @param string $expect
    2992      * @param int $mode
     3117     * @param int $mode One of the self::READ_* constants
     3118     * @param int|null $channel Channel id returned by self::getInteractiveChannelId()
    29933119     * @return string|bool|null
    29943120     * @throws \RuntimeException on connection error
    2995      */
    2996     public function read($expect = '', $mode = self::READ_SIMPLE)
    2997     {
     3121     * @throws InsufficientSetupException on unexpected channel status, possibly due to closure
     3122     */
     3123    public function read($expect = '', $mode = self::READ_SIMPLE, $channel = null)
     3124    {
     3125        if (!$this->isAuthenticated()) {
     3126            throw new InsufficientSetupException('Operation disallowed prior to login()');
     3127        }
     3128
    29983129        $this->curTimeout = $this->timeout;
    29993130        $this->is_timeout = false;
    30003131
    3001         if (!$this->isAuthenticated()) {
    3002             throw new InsufficientSetupException('Operation disallowed prior to login()');
    3003         }
    3004 
    3005         if (!($this->bitmap & self::MASK_SHELL) && !$this->initShell()) {
    3006             throw new \RuntimeException('Unable to initiate an interactive shell session');
    3007         }
    3008 
    3009         $channel = $this->get_interactive_channel();
     3132        if ($channel === null) {
     3133            $channel = $this->get_interactive_channel();
     3134        }
     3135
     3136        if (!$this->is_channel_status_data($channel) && empty($this->channel_buffers[$channel])) {
     3137            if ($channel != self::CHANNEL_SHELL) {
     3138                throw new InsufficientSetupException('Data is not available on channel');
     3139            } elseif (!$this->openShell()) {
     3140                throw new \RuntimeException('Unable to initiate an interactive shell session');
     3141            }
     3142        }
    30103143
    30113144        if ($mode == self::READ_NEXT) {
     
    30253158            $response = $this->get_channel_packet($channel);
    30263159            if ($response === true) {
    3027                 $this->in_request_pty_exec = false;
    30283160                return Strings::shift($this->interactiveBuffer, strlen($this->interactiveBuffer));
    30293161            }
     
    30353167    /**
    30363168     * Inputs a command into an interactive shell.
     3169     *
     3170     * If not specifying a channel, an open interactive channel will be selected, or, if there are
     3171     * no open channels, an interactive shell will be created. If there are multiple open
     3172     * interactive channels, a legacy behavior will apply in which channel selection prioritizes
     3173     * an active subsystem, the exec pty, and, lastly, the shell. If using multiple interactive
     3174     * channels, callers are discouraged from relying on this legacy behavior and should specify
     3175     * the intended channel.
    30373176     *
    30383177     * @see SSH2::read()
    30393178     * @param string $cmd
     3179     * @param int|null $channel Channel id returned by self::getInteractiveChannelId()
    30403180     * @return void
    30413181     * @throws \RuntimeException on connection error
    3042      */
    3043     public function write($cmd)
     3182     * @throws InsufficientSetupException on unexpected channel status, possibly due to closure
     3183     */
     3184    public function write($cmd, $channel = null)
    30443185    {
    30453186        if (!$this->isAuthenticated()) {
     
    30473188        }
    30483189
    3049         if (!($this->bitmap & self::MASK_SHELL) && !$this->initShell()) {
    3050             throw new \RuntimeException('Unable to initiate an interactive shell session');
    3051         }
    3052 
    3053         $this->send_channel_packet($this->get_interactive_channel(), $cmd);
     3190        if ($channel === null) {
     3191            $channel = $this->get_interactive_channel();
     3192        }
     3193
     3194        if (!$this->is_channel_status_data($channel)) {
     3195            if ($channel != self::CHANNEL_SHELL) {
     3196                throw new InsufficientSetupException('Data is not available on channel');
     3197            } elseif (!$this->openShell()) {
     3198                throw new \RuntimeException('Unable to initiate an interactive shell session');
     3199            }
     3200        }
     3201
     3202        $this->send_channel_packet($channel, $cmd);
    30543203    }
    30553204
     
    30693218    public function startSubsystem($subsystem)
    30703219    {
    3071         $this->window_size_server_to_client[self::CHANNEL_SUBSYSTEM] = $this->window_size;
    3072 
    3073         $packet = Strings::packSSH2(
    3074             'CsN3',
    3075             NET_SSH2_MSG_CHANNEL_OPEN,
    3076             'session',
    3077             self::CHANNEL_SUBSYSTEM,
    3078             $this->window_size,
    3079             0x4000
    3080         );
    3081 
    3082         $this->send_binary_packet($packet);
    3083 
    3084         $this->channel_status[self::CHANNEL_SUBSYSTEM] = NET_SSH2_MSG_CHANNEL_OPEN;
    3085 
    3086         $this->get_channel_packet(self::CHANNEL_SUBSYSTEM);
     3220        $this->openChannel(self::CHANNEL_SUBSYSTEM);
    30873221
    30883222        $packet = Strings::packSSH2(
     
    31043238        $this->channel_status[self::CHANNEL_SUBSYSTEM] = NET_SSH2_MSG_CHANNEL_DATA;
    31053239
    3106         $this->bitmap |= self::MASK_SHELL;
    3107         $this->in_subsystem = true;
     3240        $this->channel_id_last_interactive = self::CHANNEL_SUBSYSTEM;
    31083241
    31093242        return true;
     
    31183251    public function stopSubsystem()
    31193252    {
    3120         $this->in_subsystem = false;
    3121         $this->close_channel(self::CHANNEL_SUBSYSTEM);
     3253        if ($this->isInteractiveChannelOpen(self::CHANNEL_SUBSYSTEM)) {
     3254            $this->close_channel(self::CHANNEL_SUBSYSTEM);
     3255        }
    31223256        return true;
    31233257    }
     
    31283262     * If read() timed out you might want to just close the channel and have it auto-restart on the next read() call
    31293263     *
    3130      */
    3131     public function reset()
    3132     {
    3133         $this->close_channel($this->get_interactive_channel());
     3264     * If not specifying a channel, an open interactive channel will be selected. If there are
     3265     * multiple open interactive channels, a legacy behavior will apply in which channel selection
     3266     * prioritizes an active subsystem, the exec pty, and, lastly, the shell. If using multiple
     3267     * interactive channels, callers are discouraged from relying on this legacy behavior and
     3268     * should specify the intended channel.
     3269     *
     3270     * @param int|null $channel Channel id returned by self::getInteractiveChannelId()
     3271     * @return void
     3272     */
     3273    public function reset($channel = null)
     3274    {
     3275        if ($channel === null) {
     3276            $channel = $this->get_interactive_channel();
     3277        }
     3278        if ($this->isInteractiveChannelOpen($channel)) {
     3279            $this->close_channel($channel);
     3280        }
    31343281    }
    31353282
     
    31773324    public function isConnected()
    31783325    {
    3179         return (bool) ($this->bitmap & self::MASK_CONNECTED);
     3326        return ($this->bitmap & self::MASK_CONNECTED) && is_resource($this->fsock) && !feof($this->fsock);
    31803327    }
    31813328
     
    31883335    {
    31893336        return (bool) ($this->bitmap & self::MASK_LOGIN);
     3337    }
     3338
     3339    /**
     3340     * Is the interactive shell active?
     3341     *
     3342     * @return bool
     3343     */
     3344    public function isShellOpen()
     3345    {
     3346        return $this->isInteractiveChannelOpen(self::CHANNEL_SHELL);
     3347    }
     3348
     3349    /**
     3350     * Is the exec pty active?
     3351     *
     3352     * @return bool
     3353     */
     3354    public function isPTYOpen()
     3355    {
     3356        return $this->isInteractiveChannelOpen(self::CHANNEL_EXEC);
     3357    }
     3358
     3359    /**
     3360     * Is the given interactive channel active?
     3361     *
     3362     * @param int $channel Channel id returned by self::getInteractiveChannelId()
     3363     * @return bool
     3364     */
     3365    public function isInteractiveChannelOpen($channel)
     3366    {
     3367        return $this->isAuthenticated() && $this->is_channel_status_data($channel);
     3368    }
     3369
     3370    /**
     3371     * Returns a channel identifier, presently of the last interactive channel opened, regardless of current status.
     3372     * Returns 0 if no interactive channel has been opened.
     3373     *
     3374     * @see self::isInteractiveChannelOpen()
     3375     * @return int
     3376     */
     3377    public function getInteractiveChannelId()
     3378    {
     3379        return $this->channel_id_last_interactive;
    31903380    }
    31913381
     
    32063396        }
    32073397
    3208         $this->window_size_server_to_client[self::CHANNEL_KEEP_ALIVE] = $this->window_size;
    3209         $packet_size = 0x4000;
    3210         $packet = Strings::packSSH2(
    3211             'CsN3',
    3212             NET_SSH2_MSG_CHANNEL_OPEN,
    3213             'session',
    3214             self::CHANNEL_KEEP_ALIVE,
    3215             $this->window_size_server_to_client[self::CHANNEL_KEEP_ALIVE],
    3216             $packet_size
    3217         );
    3218 
    32193398        try {
    3220             $this->send_binary_packet($packet);
    3221 
    3222             $this->channel_status[self::CHANNEL_KEEP_ALIVE] = NET_SSH2_MSG_CHANNEL_OPEN;
    3223 
    3224             $response = $this->get_channel_packet(self::CHANNEL_KEEP_ALIVE);
     3399            $this->openChannel(self::CHANNEL_KEEP_ALIVE);
    32253400        } catch (\RuntimeException $e) {
    32263401            return $this->reconnect();
     
    32623437        $this->retry_connect = true;
    32633438        $this->get_seq_no = $this->send_seq_no = 0;
     3439        $this->channel_status = [];
     3440        $this->channel_id_last_interactive = 0;
    32643441    }
    32653442
     
    32843461            if (!$this->curTimeout) {
    32853462                if ($this->keepAlive <= 0) {
    3286                     @stream_select($read, $write, $except, null);
     3463                    static::stream_select($read, $write, $except, null);
    32873464                } else {
    3288                     if (!@stream_select($read, $write, $except, $this->keepAlive)) {
     3465                    if (!static::stream_select($read, $write, $except, $this->keepAlive)) {
    32893466                        $this->send_binary_packet(pack('CN', NET_SSH2_MSG_IGNORE, 0));
    32903467                        return $this->get_binary_packet(true);
     
    33003477
    33013478                if ($this->keepAlive > 0 && $this->keepAlive < $this->curTimeout) {
    3302                     if (!@stream_select($read, $write, $except, $this->keepAlive)) {
     3479                    if (!static::stream_select($read, $write, $except, $this->keepAlive)) {
    33033480                        $this->send_binary_packet(pack('CN', NET_SSH2_MSG_IGNORE, 0));
    33043481                        $elapsed = microtime(true) - $start;
     
    33143491
    33153492                // this can return a "stream_select(): unable to select [4]: Interrupted system call" error
    3316                 if (!@stream_select($read, $write, $except, $sec, $usec)) {
     3493                if (!static::stream_select($read, $write, $except, $sec, $usec)) {
    33173494                    $this->is_timeout = true;
    33183495                    return true;
     
    35053682        if (defined('NET_SSH2_LOGGING')) {
    35063683            $current = microtime(true);
    3507             $message_number = isset($this->message_numbers[ord($payload[0])]) ? $this->message_numbers[ord($payload[0])] : 'UNKNOWN (' . ord($payload[0]) . ')';
     3684            $message_number = isset(self::$message_numbers[ord($payload[0])]) ? self::$message_numbers[ord($payload[0])] : 'UNKNOWN (' . ord($payload[0]) . ')';
    35083685            $message_number = '<- ' . $message_number .
    35093686                              ' (since last: ' . round($current - $this->last_packet, 4) . ', network: ' . round($stop - $start, 4) . 's)';
     
    35873764                Strings::shift($payload, 1);
    35883765                list($reason_code, $message) = Strings::unpackSSH2('Ns', $payload);
    3589                 $this->errors[] = 'SSH_MSG_DISCONNECT: ' . $this->disconnect_reasons[$reason_code] . "\r\n$message";
     3766                $this->errors[] = 'SSH_MSG_DISCONNECT: ' . self::$disconnect_reasons[$reason_code] . "\r\n$message";
    35903767                $this->bitmap = 0;
    35913768                return false;
     
    37743951    public function disablePTY()
    37753952    {
    3776         if ($this->in_request_pty_exec) {
     3953        if ($this->isPTYOpen()) {
    37773954            $this->close_channel(self::CHANNEL_EXEC);
    3778             $this->in_request_pty_exec = false;
    37793955        }
    37803956        $this->request_pty = false;
     
    38023978     * - if the channel status is CHANNEL_OPEN and the response was CHANNEL_OPEN_CONFIRMATION
    38033979     * - if the channel status is CHANNEL_REQUEST and the response was CHANNEL_SUCCESS
     3980     * - if the channel status is CHANNEL_CLOSE and the response was CHANNEL_CLOSE
    38043981     *
    38053982     * bool(false) is returned if:
     
    39694146                        }
    39704147                    case NET_SSH2_MSG_CHANNEL_CLOSE:
    3971                         return $type == NET_SSH2_MSG_CHANNEL_CLOSE ? true : $this->get_channel_packet($client_channel, $skip_extended);
     4148                        if ($client_channel == $channel && $type == NET_SSH2_MSG_CHANNEL_CLOSE) {
     4149                            return true;
     4150                        }
     4151                        return $this->get_channel_packet($client_channel, $skip_extended);
    39724152                }
    39734153            }
     
    40044184                    $this->curTimeout = 5;
    40054185
    4006                     if ($this->bitmap & self::MASK_SHELL) {
    4007                         $this->bitmap &= ~self::MASK_SHELL;
    4008                     }
     4186                    $this->close_channel_bitmap($channel);
     4187
    40094188                    if ($this->channel_status[$channel] != NET_SSH2_MSG_CHANNEL_EOF) {
    40104189                        $this->send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_CLOSE, $this->server_channels[$channel]));
     
    40124191
    40134192                    $this->channel_status[$channel] = NET_SSH2_MSG_CHANNEL_CLOSE;
     4193                    $this->channelCount--;
     4194
    40144195                    if ($client_channel == $channel) {
    40154196                        return true;
     
    41584339        if (defined('NET_SSH2_LOGGING')) {
    41594340            $current = microtime(true);
    4160             $message_number = isset($this->message_numbers[ord($logged[0])]) ? $this->message_numbers[ord($logged[0])] : 'UNKNOWN (' . ord($logged[0]) . ')';
     4341            $message_number = isset(self::$message_numbers[ord($logged[0])]) ? self::$message_numbers[ord($logged[0])] : 'UNKNOWN (' . ord($logged[0]) . ')';
    41614342            $message_number = '-> ' . $message_number .
    41624343                              ' (since last: ' . round($current - $this->last_packet, 4) . ', network: ' . round($stop - $start, 4) . 's)';
     
    41674348        if (strlen($packet) != $sent) {
    41684349            $this->bitmap = 0;
    4169             throw new \RuntimeException("Only $sent of " . strlen($packet) . " bytes were sent");
     4350            $message = $sent === false ?
     4351                'Unable to write ' . strlen($packet) . ' bytes' :
     4352                "Only $sent of " . strlen($packet) . " bytes were sent";
     4353            throw new \RuntimeException($message);
    41704354        }
    41714355    }
     
    43434527
    43444528        $this->channel_status[$client_channel] = NET_SSH2_MSG_CHANNEL_CLOSE;
     4529        $this->channelCount--;
    43454530
    43464531        $this->curTimeout = 5;
    43474532
    43484533        while (!is_bool($this->get_channel_packet($client_channel))) {
    4349         }
    4350 
    4351         if ($this->is_timeout) {
    4352             $this->disconnect();
    43534534        }
    43544535
     
    43574538        }
    43584539
    4359         if ($this->bitmap & self::MASK_SHELL) {
    4360             $this->bitmap &= ~self::MASK_SHELL;
     4540        $this->close_channel_bitmap($client_channel);
     4541    }
     4542
     4543    /**
     4544     * Maintains execution state bitmap in response to channel closure
     4545     *
     4546     * @param int $client_channel The channel number to maintain closure status of
     4547     * @return void
     4548     */
     4549    private function close_channel_bitmap($client_channel)
     4550    {
     4551        switch ($client_channel) {
     4552            case self::CHANNEL_SHELL:
     4553                // Shell status has been maintained in the bitmap for backwards
     4554                //  compatibility sake, but can be removed going forward
     4555                if ($this->bitmap & self::MASK_SHELL) {
     4556                    $this->bitmap &= ~self::MASK_SHELL;
     4557                }
     4558                break;
    43614559        }
    43624560    }
     
    43964594     * @access protected
    43974595     */
    4398     protected function define_array(...$args)
     4596    protected static function define_array(...$args)
    43994597    {
    44004598        foreach ($args as $arg) {
     
    47994997            ]
    48004998        ];
     4999    }
     5000
     5001    /**
     5002     * Force multiple channels (even if phpseclib has decided to disable them)
     5003     */
     5004    public function forceMultipleChannels()
     5005    {
     5006        $this->errorOnMultipleChannels = false;
    48015007    }
    48025008
Note: See TracChangeset for help on using the changeset viewer.