SSH SFTP Updater Support - Version 0.8.4

Version Description

  • 2020/Dec/30 =

  • TWEAK: Remove obsolete references to other plugins

  • TWEAK: Replace some further deprecated jQuery styles

  • TWEAK: Update to latest 1.0.x version of phpseclib

Download this release

Release Info

Developer DavidAnderson
Plugin Icon wp plugin SSH SFTP Updater Support
Version 0.8.4
Comparing to
See all releases

Code changes from version 0.8.3 to 0.8.4

includes/ssh-sftp-updater-support-notices.php CHANGED
@@ -68,28 +68,6 @@ class SSH_SFTP_Updater_Support_Notices extends Updraft_Notices_1_0 {
68
  'supported_positions' => $this->anywhere,
69
  'validity_function' => 'is_wpo_installed',
70
  ),
71
- 'keyy' => array(
72
- 'prefix' => '',
73
- 'title' => 'Keyy: Instant and secure logon with a wave of your phone',
74
- 'text' => __("Find out more about our revolutionary new WordPress plugin.", "ssh-sftp-updater-support"),
75
- 'button_link' => 'https://getkeyy.com/',
76
- 'button_meta' => 'keyy',
77
- 'image' => 'notices/keyy_logo.png',
78
- 'dismiss_time' => 'dismiss_page_notice_until',
79
- 'supported_positions' => $this->anywhere,
80
- 'validity_function' => 'is_keyy_installed',
81
- ),
82
- 'meta-slider' => array(
83
- 'prefix' => '',
84
- 'title' => __("MetaSlider: the world's #1 slider plugin from the makers of SSH SFTP Updater Support", "ssh-sftp-updater-support"),
85
- 'text' => __("With MetaSlider, you can easily add style and flare with beautifully-designed sliders.", "ssh-sftp-updater-support"),
86
- 'button_link' => 'https://metaslider.com',
87
- 'button_meta' => 'metaslider',
88
- 'image' => 'notices/metaslider_logo.png',
89
- 'dismiss_time' => 'dismiss_page_notice_until',
90
- 'supported_positions' => $this->anywhere,
91
- 'validity_function' => 'is_metaslider_installed',
92
- ),
93
 
94
  );
95
 
@@ -130,17 +108,6 @@ class SSH_SFTP_Updater_Support_Notices extends Updraft_Notices_1_0 {
130
  return parent::is_plugin_installed($product, $also_require_active);
131
  }
132
 
133
- /**
134
- * This method will call the parent is_plugin_installed and pass in the product keyy to check if that plugin is installed if it is then we shouldn't display the notice
135
- *
136
- * @param string $product the plugin slug
137
- * @param boolean $also_require_active a bool to indicate if the plugin should also be active
138
- * @return boolean a bool to indicate if the notice should be displayed or not
139
- */
140
- protected function is_keyy_installed($product = 'keyy', $also_require_active = false) {
141
- return parent::is_plugin_installed($product, $also_require_active);
142
- }
143
-
144
  /**
145
  * This method will call the is premium function in the SSH_SFTP_Updater_Support_Notices object to check if this install is premium and if it is we won't display the notice
146
  *
@@ -149,19 +116,7 @@ class SSH_SFTP_Updater_Support_Notices extends Updraft_Notices_1_0 {
149
  protected function is_wpo_installed($product = 'wp-optimize', $also_require_active = false) {
150
  return parent::is_plugin_installed($product, $also_require_active);
151
  }
152
-
153
- /**
154
- * This method will check to see if Meta Slider plugin is installed.
155
- *
156
- * @param String $product the plugin slug
157
- * @param boolean $also_require_active a bool to indicate if the plugin should be active or not
158
- * @return boolean a bool to indicate if the notice should be displayed or not
159
- */
160
- protected function is_metaslider_installed($product = 'ml-slider', $also_require_active = false) {
161
- return parent::is_plugin_installed($product, $also_require_active);
162
- }
163
-
164
-
165
  /**
166
  * This method calls the parent verson and will work out if the user is using a non english language and if so returns true so that they can see the translation advert.
167
  *
68
  'supported_positions' => $this->anywhere,
69
  'validity_function' => 'is_wpo_installed',
70
  ),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71
 
72
  );
73
 
108
  return parent::is_plugin_installed($product, $also_require_active);
109
  }
110
 
 
 
 
 
 
 
 
 
 
 
 
111
  /**
112
  * This method will call the is premium function in the SSH_SFTP_Updater_Support_Notices object to check if this install is premium and if it is we won't display the notice
113
  *
116
  protected function is_wpo_installed($product = 'wp-optimize', $also_require_active = false) {
117
  return parent::is_plugin_installed($product, $also_require_active);
118
  }
119
+
 
 
 
 
 
 
 
 
 
 
 
 
120
  /**
121
  * This method calls the parent verson and will work out if the user is using a non english language and if so returns true so that they can see the translation advert.
122
  *
phpseclib/Crypt/RSA.php CHANGED
@@ -1603,12 +1603,12 @@ class Crypt_RSA
1603
  &$components['primes'][2]
1604
  );
1605
 
1606
- foreach ($values as &$value) {
1607
  extract(unpack('Nlength', $this->_string_shift($paddedKey, 4)));
1608
  if (strlen($paddedKey) < $length) {
1609
  return false;
1610
  }
1611
- $value = new Math_BigInteger($this->_string_shift($paddedKey, $length), -256);
1612
  }
1613
 
1614
  extract(unpack('Nlength', $this->_string_shift($paddedKey, 4)));
@@ -2846,7 +2846,7 @@ class Crypt_RSA
2846
  // if $m is larger than two million terrabytes and you're using sha1, PKCS#1 suggests a "Label too long" error
2847
  // be output.
2848
 
2849
- $emLen = ($emBits + 1) >> 3; // ie. ceil($emBits / 8);
2850
  $sLen = $this->sLen !== null ? $this->sLen : $this->hLen;
2851
 
2852
  $mHash = $this->hash->hash($m);
@@ -2924,7 +2924,7 @@ class Crypt_RSA
2924
 
2925
  // RSA verification
2926
 
2927
- $modBits = 8 * $this->k;
2928
 
2929
  $s2 = $this->_os2ip($s);
2930
  $m2 = $this->_rsavp1($s2);
@@ -2932,7 +2932,7 @@ class Crypt_RSA
2932
  user_error('Invalid signature');
2933
  return false;
2934
  }
2935
- $em = $this->_i2osp($m2, $modBits >> 3);
2936
  if ($em === false) {
2937
  user_error('Invalid signature');
2938
  return false;
1603
  &$components['primes'][2]
1604
  );
1605
 
1606
+ for ($i = 0; $i < count($values); $i++) {
1607
  extract(unpack('Nlength', $this->_string_shift($paddedKey, 4)));
1608
  if (strlen($paddedKey) < $length) {
1609
  return false;
1610
  }
1611
+ $values[$i] = new Math_BigInteger($this->_string_shift($paddedKey, $length), -256);
1612
  }
1613
 
1614
  extract(unpack('Nlength', $this->_string_shift($paddedKey, 4)));
2846
  // if $m is larger than two million terrabytes and you're using sha1, PKCS#1 suggests a "Label too long" error
2847
  // be output.
2848
 
2849
+ $emLen = ($emBits + 7) >> 3; // ie. ceil($emBits / 8);
2850
  $sLen = $this->sLen !== null ? $this->sLen : $this->hLen;
2851
 
2852
  $mHash = $this->hash->hash($m);
2924
 
2925
  // RSA verification
2926
 
2927
+ $modBits = strlen($this->modulus->toBits());
2928
 
2929
  $s2 = $this->_os2ip($s);
2930
  $m2 = $this->_rsavp1($s2);
2932
  user_error('Invalid signature');
2933
  return false;
2934
  }
2935
+ $em = $this->_i2osp($m2, $this->k);
2936
  if ($em === false) {
2937
  user_error('Invalid signature');
2938
  return false;
phpseclib/Crypt/Random.php CHANGED
@@ -105,7 +105,10 @@ if (!function_exists('crypt_random_string')) {
105
  $fp = @fopen('/dev/urandom', 'rb');
106
  }
107
  if ($fp !== true && $fp !== false) { // surprisingly faster than !is_bool() or is_resource()
108
- return fread($fp, $length);
 
 
 
109
  }
110
  // method 3. pretty much does the same thing as method 2 per the following url:
111
  // https://github.com/php/php-src/blob/7014a0eb6d1611151a286c0ff4f2238f92c120d6/ext/mcrypt/mcrypt.c#L1391
105
  $fp = @fopen('/dev/urandom', 'rb');
106
  }
107
  if ($fp !== true && $fp !== false) { // surprisingly faster than !is_bool() or is_resource()
108
+ $temp = fread($fp, $length);
109
+ if (strlen($temp) == $length) {
110
+ return $temp;
111
+ }
112
  }
113
  // method 3. pretty much does the same thing as method 2 per the following url:
114
  // https://github.com/php/php-src/blob/7014a0eb6d1611151a286c0ff4f2238f92c120d6/ext/mcrypt/mcrypt.c#L1391
phpseclib/File/ANSI.php CHANGED
@@ -299,7 +299,7 @@ class File_ANSI
299
  case "\x1B[K": // Clear screen from cursor right
300
  $this->screen[$this->y] = substr($this->screen[$this->y], 0, $this->x);
301
 
302
- array_splice($this->attrs[$this->y], $this->x + 1, $this->max_x - $this->x, array_fill($this->x, $this->max_x - $this->x - 1, $this->base_attr_cell));
303
  break;
304
  case "\x1B[2K": // Clear entire line
305
  $this->screen[$this->y] = str_repeat(' ', $this->x);
299
  case "\x1B[K": // Clear screen from cursor right
300
  $this->screen[$this->y] = substr($this->screen[$this->y], 0, $this->x);
301
 
302
+ array_splice($this->attrs[$this->y], $this->x + 1, $this->max_x - $this->x, array_fill($this->x, $this->max_x - ($this->x - 1), $this->base_attr_cell));
303
  break;
304
  case "\x1B[2K": // Clear entire line
305
  $this->screen[$this->y] = str_repeat(' ', $this->x);
phpseclib/File/ASN1.php CHANGED
@@ -577,6 +577,10 @@ class File_ASN1
577
  */
578
  function asn1map($decoded, $mapping, $special = array())
579
  {
 
 
 
 
580
  if (isset($mapping['explicit']) && is_array($decoded['content'])) {
581
  $decoded = $decoded['content'][0];
582
  }
@@ -787,7 +791,14 @@ class File_ASN1
787
  case FILE_ASN1_TYPE_UTC_TIME:
788
  case FILE_ASN1_TYPE_GENERALIZED_TIME:
789
  if (class_exists('DateTime')) {
790
- if (isset($mapping['implicit'])) {
 
 
 
 
 
 
 
791
  $decoded['content'] = $this->_decodeDateTime($decoded['content'], $decoded['type']);
792
  }
793
  if (!$decoded['content']) {
@@ -795,7 +806,10 @@ class File_ASN1
795
  }
796
  return $decoded['content']->format($this->format);
797
  } else {
798
- if (isset($mapping['implicit'])) {
 
 
 
799
  $decoded['content'] = $this->_decodeUnixTime($decoded['content'], $decoded['type']);
800
  }
801
  return @date($this->format, $decoded['content']);
@@ -937,7 +951,7 @@ class File_ASN1
937
  if ($mapping['type'] == FILE_ASN1_TYPE_SET) {
938
  sort($value);
939
  }
940
- $value = implode($value, '');
941
  break;
942
  }
943
 
577
  */
578
  function asn1map($decoded, $mapping, $special = array())
579
  {
580
+ if (!is_array($decoded)) {
581
+ return false;
582
+ }
583
+
584
  if (isset($mapping['explicit']) && is_array($decoded['content'])) {
585
  $decoded = $decoded['content'][0];
586
  }
791
  case FILE_ASN1_TYPE_UTC_TIME:
792
  case FILE_ASN1_TYPE_GENERALIZED_TIME:
793
  if (class_exists('DateTime')) {
794
+ // for explicitly tagged optional stuff
795
+ if (is_array($decoded['content'])) {
796
+ $decoded['content'] = $decoded['content'][0]['content'];
797
+ }
798
+ // for implicitly tagged optional stuff
799
+ // in theory, doing isset($mapping['implicit']) would work but malformed certs do exist
800
+ // in the wild that OpenSSL decodes without issue so we'll support them as well
801
+ if (!is_object($decoded['content'])) {
802
  $decoded['content'] = $this->_decodeDateTime($decoded['content'], $decoded['type']);
803
  }
804
  if (!$decoded['content']) {
806
  }
807
  return $decoded['content']->format($this->format);
808
  } else {
809
+ if (is_array($decoded['content'])) {
810
+ $decoded['content'] = $decoded['content'][0]['content'];
811
+ }
812
+ if (!is_int($decoded['content'])) {
813
  $decoded['content'] = $this->_decodeUnixTime($decoded['content'], $decoded['type']);
814
  }
815
  return @date($this->format, $decoded['content']);
951
  if ($mapping['type'] == FILE_ASN1_TYPE_SET) {
952
  sort($value);
953
  }
954
+ $value = implode('', $value);
955
  break;
956
  }
957
 
phpseclib/File/X509.php CHANGED
@@ -982,6 +982,13 @@ class File_X509
982
  'children' => $AccessDescription
983
  );
984
 
 
 
 
 
 
 
 
985
  $this->SubjectAltName = $GeneralNames;
986
 
987
  $this->PrivateKeyUsagePeriod = array(
@@ -1650,7 +1657,10 @@ class File_X509
1650
  corresponding to the extension type identified by extnID */
1651
  $map = $this->_getMapping($id);
1652
  if (!is_bool($map)) {
1653
- $mapped = $asn1->asn1map($decoded[0], $map, array('iPAddress' => array($this, '_decodeIP')));
 
 
 
1654
  $value = $mapped === false ? $decoded[0] : $mapped;
1655
 
1656
  if ($id == 'id-ce-certificatePolicies') {
@@ -1919,6 +1929,8 @@ class File_X509
1919
  return $this->ExtKeyUsageSyntax;
1920
  case 'id-pe-authorityInfoAccess':
1921
  return $this->AuthorityInfoAccessSyntax;
 
 
1922
  case 'id-ce-subjectAltName':
1923
  return $this->SubjectAltName;
1924
  case 'id-ce-subjectDirectoryAttributes':
@@ -2206,7 +2218,11 @@ class File_X509
2206
  }
2207
 
2208
  while (!feof($fsock)) {
2209
- $data.= fread($fsock, 1024);
 
 
 
 
2210
  }
2211
 
2212
  break;
@@ -2255,7 +2271,7 @@ class File_X509
2255
  return false;
2256
  }
2257
 
2258
- $parent = new static();
2259
  $parent->CAs = $this->CAs;
2260
  /*
2261
  "Conforming applications that support HTTP or FTP for accessing
@@ -2555,18 +2571,36 @@ class File_X509
2555
  return long2ip($ip);
2556
  }
2557
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2558
  /**
2559
  * Encodes an IP address
2560
  *
2561
  * Takes a human readable IP address into a base64-encoded "blob"
2562
  *
2563
- * @param string $ip
2564
  * @access private
2565
  * @return string
2566
  */
2567
  function _encodeIP($ip)
2568
  {
2569
- return base64_encode(pack('N', ip2long($ip)));
 
 
2570
  }
2571
 
2572
  /**
@@ -2970,7 +3004,7 @@ class File_X509
2970
  } elseif (is_object($value) && strtolower(get_class($value)) == 'file_asn1_element') {
2971
  // @codingStandardsIgnoreStart
2972
  $callback = version_compare(PHP_VERSION, '5.3.0') >= 0 ?
2973
- function ($x) { return "\x" . bin2hex($x[0]); } :
2974
  create_function('$x', 'return "\x" . bin2hex($x[0]);');
2975
  // @codingStandardsIgnoreEnd
2976
  $value = strtoupper(preg_replace_callback('#[^\x20-\x7E]#', $callback, $value->element));
982
  'children' => $AccessDescription
983
  );
984
 
985
+ $this->SubjectInfoAccessSyntax = array(
986
+ 'type' => FILE_ASN1_TYPE_SEQUENCE,
987
+ 'min' => 1,
988
+ 'max' => -1,
989
+ 'children' => $AccessDescription
990
+ );
991
+
992
  $this->SubjectAltName = $GeneralNames;
993
 
994
  $this->PrivateKeyUsagePeriod = array(
1657
  corresponding to the extension type identified by extnID */
1658
  $map = $this->_getMapping($id);
1659
  if (!is_bool($map)) {
1660
+ $decoder = $id == 'id-ce-nameConstraints' ?
1661
+ array($this, '_decodeNameConstraintIP') :
1662
+ array($this, '_decodeIP');
1663
+ $mapped = $asn1->asn1map($decoded[0], $map, array('iPAddress' => $decoder));
1664
  $value = $mapped === false ? $decoded[0] : $mapped;
1665
 
1666
  if ($id == 'id-ce-certificatePolicies') {
1929
  return $this->ExtKeyUsageSyntax;
1930
  case 'id-pe-authorityInfoAccess':
1931
  return $this->AuthorityInfoAccessSyntax;
1932
+ case 'id-pe-subjectInfoAccess':
1933
+ return $this->SubjectInfoAccessSyntax;
1934
  case 'id-ce-subjectAltName':
1935
  return $this->SubjectAltName;
1936
  case 'id-ce-subjectDirectoryAttributes':
2218
  }
2219
 
2220
  while (!feof($fsock)) {
2221
+ $temp = fread($fsock, 1024);
2222
+ if ($temp === false) {
2223
+ return false;
2224
+ }
2225
+ $data.= $temp;
2226
  }
2227
 
2228
  break;
2271
  return false;
2272
  }
2273
 
2274
+ $parent = new File_X509();
2275
  $parent->CAs = $this->CAs;
2276
  /*
2277
  "Conforming applications that support HTTP or FTP for accessing
2571
  return long2ip($ip);
2572
  }
2573
 
2574
+ /**
2575
+ * Decodes an IP address in a name constraints extension
2576
+ *
2577
+ * Takes in a base64 encoded "blob" and returns a human readable IP address / mask
2578
+ *
2579
+ * @param string $ip
2580
+ * @access private
2581
+ * @return array
2582
+ */
2583
+ function _decodeNameConstraintIP($ip)
2584
+ {
2585
+ $ip = base64_decode($ip);
2586
+ list(, $ip, $mask) = unpack('N2', $ip);
2587
+ return array(long2ip($ip), long2ip($mask));
2588
+ }
2589
+
2590
  /**
2591
  * Encodes an IP address
2592
  *
2593
  * Takes a human readable IP address into a base64-encoded "blob"
2594
  *
2595
+ * @param string|array $ip
2596
  * @access private
2597
  * @return string
2598
  */
2599
  function _encodeIP($ip)
2600
  {
2601
+ return is_string($ip) ?
2602
+ base64_encode(pack('N', ip2long($ip))) :
2603
+ base64_encode(pack('NN', ip2long($ip[0]), ip2long($ip[1])));
2604
  }
2605
 
2606
  /**
3004
  } elseif (is_object($value) && strtolower(get_class($value)) == 'file_asn1_element') {
3005
  // @codingStandardsIgnoreStart
3006
  $callback = version_compare(PHP_VERSION, '5.3.0') >= 0 ?
3007
+ eval('return function ($x) { return "\x" . bin2hex($x[0]); };') :
3008
  create_function('$x', 'return "\x" . bin2hex($x[0]);');
3009
  // @codingStandardsIgnoreEnd
3010
  $value = strtoupper(preg_replace_callback('#[^\x20-\x7E]#', $callback, $value->element));
phpseclib/Math/BigInteger.php CHANGED
@@ -556,7 +556,7 @@ class Math_BigInteger
556
  $bytes = chr(0);
557
  }
558
 
559
- if (ord($bytes[0]) & 0x80) {
560
  $bytes = chr(0) . $bytes;
561
  }
562
 
@@ -724,6 +724,7 @@ class Math_BigInteger
724
  }
725
 
726
  $temp = $this->copy();
 
727
  $temp->is_negative = false;
728
 
729
  $divisor = new Math_BigInteger();
@@ -860,7 +861,7 @@ class Math_BigInteger
860
  $opts[] = 'OpenSSL';
861
  }
862
  if (!empty($opts)) {
863
- $engine.= ' (' . implode($opts, ', ') . ')';
864
  }
865
  return array(
866
  'value' => '0x' . $this->toHex(true),
@@ -1586,7 +1587,9 @@ class Math_BigInteger
1586
  $temp_value = array($quotient_value[$q_index]);
1587
  $temp = $temp->multiply($y);
1588
  $temp_value = &$temp->value;
1589
- $temp_value = array_merge($adjust, $temp_value);
 
 
1590
 
1591
  $x = $x->subtract($temp);
1592
 
@@ -3613,7 +3616,14 @@ class Math_BigInteger
3613
  switch (MATH_BIGINTEGER_MODE) {
3614
  case MATH_BIGINTEGER_MODE_GMP:
3615
  if ($this->bitmask !== false) {
 
 
 
 
3616
  $result->value = gmp_and($result->value, $result->bitmask->value);
 
 
 
3617
  }
3618
 
3619
  return $result;
@@ -3628,6 +3638,7 @@ class Math_BigInteger
3628
  $value = &$result->value;
3629
 
3630
  if (!count($value)) {
 
3631
  return $result;
3632
  }
3633
 
556
  $bytes = chr(0);
557
  }
558
 
559
+ if ($this->precision <= 0 && (ord($bytes[0]) & 0x80)) {
560
  $bytes = chr(0) . $bytes;
561
  }
562
 
724
  }
725
 
726
  $temp = $this->copy();
727
+ $temp->bitmask = false;
728
  $temp->is_negative = false;
729
 
730
  $divisor = new Math_BigInteger();
861
  $opts[] = 'OpenSSL';
862
  }
863
  if (!empty($opts)) {
864
+ $engine.= ' (' . implode('.', $opts) . ')';
865
  }
866
  return array(
867
  'value' => '0x' . $this->toHex(true),
1587
  $temp_value = array($quotient_value[$q_index]);
1588
  $temp = $temp->multiply($y);
1589
  $temp_value = &$temp->value;
1590
+ if (count($temp_value)) {
1591
+ $temp_value = array_merge($adjust, $temp_value);
1592
+ }
1593
 
1594
  $x = $x->subtract($temp);
1595
 
3616
  switch (MATH_BIGINTEGER_MODE) {
3617
  case MATH_BIGINTEGER_MODE_GMP:
3618
  if ($this->bitmask !== false) {
3619
+ $flip = gmp_cmp($result->value, gmp_init(0)) < 0;
3620
+ if ($flip) {
3621
+ $result->value = gmp_neg($result->value);
3622
+ }
3623
  $result->value = gmp_and($result->value, $result->bitmask->value);
3624
+ if ($flip) {
3625
+ $result->value = gmp_neg($result->value);
3626
+ }
3627
  }
3628
 
3629
  return $result;
3638
  $value = &$result->value;
3639
 
3640
  if (!count($value)) {
3641
+ $result->is_negative = false;
3642
  return $result;
3643
  }
3644
 
phpseclib/Net/SFTP.php CHANGED
@@ -1,3206 +1,3240 @@
1
- <?php
2
-
3
- /**
4
- * Pure-PHP implementation of SFTP.
5
- *
6
- * PHP versions 4 and 5
7
- *
8
- * Currently only supports SFTPv2 and v3, which, according to wikipedia.org, "is the most widely used version,
9
- * implemented by the popular OpenSSH SFTP server". If you want SFTPv4/5/6 support, provide me with access
10
- * to an SFTPv4/5/6 server.
11
- *
12
- * The API for this library is modeled after the API from PHP's {@link http://php.net/book.ftp FTP extension}.
13
- *
14
- * Here's a short example of how to use this library:
15
- * <code>
16
- * <?php
17
- * include 'Net/SFTP.php';
18
- *
19
- * $sftp = new Net_SFTP('www.domain.tld');
20
- * if (!$sftp->login('username', 'password')) {
21
- * exit('Login Failed');
22
- * }
23
- *
24
- * echo $sftp->pwd() . "\r\n";
25
- * $sftp->put('filename.ext', 'hello, world!');
26
- * print_r($sftp->nlist());
27
- * ?>
28
- * </code>
29
- *
30
- * LICENSE: Permission is hereby granted, free of charge, to any person obtaining a copy
31
- * of this software and associated documentation files (the "Software"), to deal
32
- * in the Software without restriction, including without limitation the rights
33
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
34
- * copies of the Software, and to permit persons to whom the Software is
35
- * furnished to do so, subject to the following conditions:
36
- *
37
- * The above copyright notice and this permission notice shall be included in
38
- * all copies or substantial portions of the Software.
39
- *
40
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
41
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
42
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
43
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
44
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
45
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
46
- * THE SOFTWARE.
47
- *
48
- * @category Net
49
- * @package Net_SFTP
50
- * @author Jim Wigginton <terrafrost@php.net>
51
- * @copyright 2009 Jim Wigginton
52
- * @license http://www.opensource.org/licenses/mit-license.html MIT License
53
- * @link http://phpseclib.sourceforge.net
54
- */
55
-
56
- /**
57
- * Include Net_SSH2
58
- */
59
- if (!class_exists('Net_SSH2')) {
60
- include_once 'SSH2.php';
61
- }
62
-
63
- /**#@+
64
- * @access public
65
- * @see self::getLog()
66
- */
67
- /**
68
- * Returns the message numbers
69
- */
70
- define('NET_SFTP_LOG_SIMPLE', NET_SSH2_LOG_SIMPLE);
71
- /**
72
- * Returns the message content
73
- */
74
- define('NET_SFTP_LOG_COMPLEX', NET_SSH2_LOG_COMPLEX);
75
- /**
76
- * Outputs the message content in real-time.
77
- */
78
- define('NET_SFTP_LOG_REALTIME', 3);
79
- /**#@-*/
80
-
81
- /**
82
- * SFTP channel constant
83
- *
84
- * Net_SSH2::exec() uses 0 and Net_SSH2::read() / Net_SSH2::write() use 1.
85
- *
86
- * @see Net_SSH2::_send_channel_packet()
87
- * @see Net_SSH2::_get_channel_packet()
88
- * @access private
89
- */
90
- define('NET_SFTP_CHANNEL', 0x100);
91
-
92
- /**#@+
93
- * @access public
94
- * @see self::put()
95
- */
96
- /**
97
- * Reads data from a local file.
98
- */
99
- define('NET_SFTP_LOCAL_FILE', 1);
100
- /**
101
- * Reads data from a string.
102
- */
103
- // this value isn't really used anymore but i'm keeping it reserved for historical reasons
104
- define('NET_SFTP_STRING', 2);
105
- /**
106
- * Reads data from callback:
107
- * function callback($length) returns string to proceed, null for EOF
108
- */
109
- define('NET_SFTP_CALLBACK', 16);
110
- /**
111
- * Resumes an upload
112
- */
113
- define('NET_SFTP_RESUME', 4);
114
- /**
115
- * Append a local file to an already existing remote file
116
- */
117
- define('NET_SFTP_RESUME_START', 8);
118
- /**#@-*/
119
-
120
- /**
121
- * Pure-PHP implementations of SFTP.
122
- *
123
- * @package Net_SFTP
124
- * @author Jim Wigginton <terrafrost@php.net>
125
- * @access public
126
- */
127
- class Net_SFTP extends Net_SSH2
128
- {
129
- /**
130
- * Packet Types
131
- *
132
- * @see self::Net_SFTP()
133
- * @var array
134
- * @access private
135
- */
136
- var $packet_types = array();
137
-
138
- /**
139
- * Status Codes
140
- *
141
- * @see self::Net_SFTP()
142
- * @var array
143
- * @access private
144
- */
145
- var $status_codes = array();
146
-
147
- /**
148
- * The Request ID
149
- *
150
- * The request ID exists in the off chance that a packet is sent out-of-order. Of course, this library doesn't support
151
- * concurrent actions, so it's somewhat academic, here.
152
- *
153
- * @var boolean
154
- * @see self::_send_sftp_packet()
155
- * @access private
156
- */
157
- var $use_request_id = false;
158
-
159
- /**
160
- * The Packet Type
161
- *
162
- * The request ID exists in the off chance that a packet is sent out-of-order. Of course, this library doesn't support
163
- * concurrent actions, so it's somewhat academic, here.
164
- *
165
- * @var int
166
- * @see self::_get_sftp_packet()
167
- * @access private
168
- */
169
- var $packet_type = -1;
170
-
171
- /**
172
- * Packet Buffer
173
- *
174
- * @var string
175
- * @see self::_get_sftp_packet()
176
- * @access private
177
- */
178
- var $packet_buffer = '';
179
-
180
- /**
181
- * Extensions supported by the server
182
- *
183
- * @var array
184
- * @see self::_initChannel()
185
- * @access private
186
- */
187
- var $extensions = array();
188
-
189
- /**
190
- * Server SFTP version
191
- *
192
- * @var int
193
- * @see self::_initChannel()
194
- * @access private
195
- */
196
- var $version;
197
-
198
- /**
199
- * Current working directory
200
- *
201
- * @var string
202
- * @see self::realpath()
203
- * @see self::chdir()
204
- * @access private
205
- */
206
- var $pwd = false;
207
-
208
- /**
209
- * Packet Type Log
210
- *
211
- * @see self::getLog()
212
- * @var array
213
- * @access private
214
- */
215
- var $packet_type_log = array();
216
-
217
- /**
218
- * Packet Log
219
- *
220
- * @see self::getLog()
221
- * @var array
222
- * @access private
223
- */
224
- var $packet_log = array();
225
-
226
- /**
227
- * Error information
228
- *
229
- * @see self::getSFTPErrors()
230
- * @see self::getLastSFTPError()
231
- * @var array
232
- * @access private
233
- */
234
- var $sftp_errors = array();
235
-
236
- /**
237
- * Stat Cache
238
- *
239
- * Rather than always having to open a directory and close it immediately there after to see if a file is a directory
240
- * we'll cache the results.
241
- *
242
- * @see self::_update_stat_cache()
243
- * @see self::_remove_from_stat_cache()
244
- * @see self::_query_stat_cache()
245
- * @var array
246
- * @access private
247
- */
248
- var $stat_cache = array();
249
-
250
- /**
251
- * Max SFTP Packet Size
252
- *
253
- * @see self::Net_SFTP()
254
- * @see self::get()
255
- * @var array
256
- * @access private
257
- */
258
- var $max_sftp_packet;
259
-
260
- /**
261
- * Stat Cache Flag
262
- *
263
- * @see self::disableStatCache()
264
- * @see self::enableStatCache()
265
- * @var bool
266
- * @access private
267
- */
268
- var $use_stat_cache = true;
269
-
270
- /**
271
- * Sort Options
272
- *
273
- * @see self::_comparator()
274
- * @see self::setListOrder()
275
- * @var array
276
- * @access private
277
- */
278
- var $sortOptions = array();
279
-
280
- /**
281
- * Canonicalization Flag
282
- *
283
- * Determines whether or not paths should be canonicalized before being
284
- * passed on to the remote server.
285
- *
286
- * @see self::enablePathCanonicalization()
287
- * @see self::disablePathCanonicalization()
288
- * @see self::realpath()
289
- * @var bool
290
- * @access private
291
- */
292
- var $canonicalize_paths = true;
293
-
294
- /**
295
- * Request Buffers
296
- *
297
- * @see self::_get_sftp_packet()
298
- * @var array
299
- * @access private
300
- */
301
- var $requestBuffer = array();
302
-
303
- /**
304
- * Default Constructor.
305
- *
306
- * Connects to an SFTP server
307
- *
308
- * @param string $host
309
- * @param int $port
310
- * @param int $timeout
311
- * @return Net_SFTP
312
- * @access public
313
- */
314
- function __construct($host, $port = 22, $timeout = 10)
315
- {
316
- parent::__construct($host, $port, $timeout);
317
-
318
- $this->max_sftp_packet = 1 << 15;
319
-
320
- $this->packet_types = array(
321
- 1 => 'NET_SFTP_INIT',
322
- 2 => 'NET_SFTP_VERSION',
323
- /* the format of SSH_FXP_OPEN changed between SFTPv4 and SFTPv5+:
324
- SFTPv5+: http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.1.1
325
- pre-SFTPv5 : http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-6.3 */
326
- 3 => 'NET_SFTP_OPEN',
327
- 4 => 'NET_SFTP_CLOSE',
328
- 5 => 'NET_SFTP_READ',
329
- 6 => 'NET_SFTP_WRITE',
330
- 7 => 'NET_SFTP_LSTAT',
331
- 9 => 'NET_SFTP_SETSTAT',
332
- 11 => 'NET_SFTP_OPENDIR',
333
- 12 => 'NET_SFTP_READDIR',
334
- 13 => 'NET_SFTP_REMOVE',
335
- 14 => 'NET_SFTP_MKDIR',
336
- 15 => 'NET_SFTP_RMDIR',
337
- 16 => 'NET_SFTP_REALPATH',
338
- 17 => 'NET_SFTP_STAT',
339
- /* the format of SSH_FXP_RENAME changed between SFTPv4 and SFTPv5+:
340
- SFTPv5+: http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.3
341
- pre-SFTPv5 : http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-6.5 */
342
- 18 => 'NET_SFTP_RENAME',
343
- 19 => 'NET_SFTP_READLINK',
344
- 20 => 'NET_SFTP_SYMLINK',
345
-
346
- 101=> 'NET_SFTP_STATUS',
347
- 102=> 'NET_SFTP_HANDLE',
348
- /* the format of SSH_FXP_NAME changed between SFTPv3 and SFTPv4+:
349
- SFTPv4+: http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-9.4
350
- pre-SFTPv4 : http://tools.ietf.org/html/draft-ietf-secsh-filexfer-02#section-7 */
351
- 103=> 'NET_SFTP_DATA',
352
- 104=> 'NET_SFTP_NAME',
353
- 105=> 'NET_SFTP_ATTRS',
354
-
355
- 200=> 'NET_SFTP_EXTENDED'
356
- );
357
- $this->status_codes = array(
358
- 0 => 'NET_SFTP_STATUS_OK',
359
- 1 => 'NET_SFTP_STATUS_EOF',
360
- 2 => 'NET_SFTP_STATUS_NO_SUCH_FILE',
361
- 3 => 'NET_SFTP_STATUS_PERMISSION_DENIED',
362
- 4 => 'NET_SFTP_STATUS_FAILURE',
363
- 5 => 'NET_SFTP_STATUS_BAD_MESSAGE',
364
- 6 => 'NET_SFTP_STATUS_NO_CONNECTION',
365
- 7 => 'NET_SFTP_STATUS_CONNECTION_LOST',
366
- 8 => 'NET_SFTP_STATUS_OP_UNSUPPORTED',
367
- 9 => 'NET_SFTP_STATUS_INVALID_HANDLE',
368
- 10 => 'NET_SFTP_STATUS_NO_SUCH_PATH',
369
- 11 => 'NET_SFTP_STATUS_FILE_ALREADY_EXISTS',
370
- 12 => 'NET_SFTP_STATUS_WRITE_PROTECT',
371
- 13 => 'NET_SFTP_STATUS_NO_MEDIA',
372
- 14 => 'NET_SFTP_STATUS_NO_SPACE_ON_FILESYSTEM',
373
- 15 => 'NET_SFTP_STATUS_QUOTA_EXCEEDED',
374
- 16 => 'NET_SFTP_STATUS_UNKNOWN_PRINCIPAL',
375
- 17 => 'NET_SFTP_STATUS_LOCK_CONFLICT',
376
- 18 => 'NET_SFTP_STATUS_DIR_NOT_EMPTY',
377
- 19 => 'NET_SFTP_STATUS_NOT_A_DIRECTORY',
378
- 20 => 'NET_SFTP_STATUS_INVALID_FILENAME',
379
- 21 => 'NET_SFTP_STATUS_LINK_LOOP',
380
- 22 => 'NET_SFTP_STATUS_CANNOT_DELETE',
381
- 23 => 'NET_SFTP_STATUS_INVALID_PARAMETER',
382
- 24 => 'NET_SFTP_STATUS_FILE_IS_A_DIRECTORY',
383
- 25 => 'NET_SFTP_STATUS_BYTE_RANGE_LOCK_CONFLICT',
384
- 26 => 'NET_SFTP_STATUS_BYTE_RANGE_LOCK_REFUSED',
385
- 27 => 'NET_SFTP_STATUS_DELETE_PENDING',
386
- 28 => 'NET_SFTP_STATUS_FILE_CORRUPT',
387
- 29 => 'NET_SFTP_STATUS_OWNER_INVALID',
388
- 30 => 'NET_SFTP_STATUS_GROUP_INVALID',
389
- 31 => 'NET_SFTP_STATUS_NO_MATCHING_BYTE_RANGE_LOCK'
390
- );
391
- // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-7.1
392
- // the order, in this case, matters quite a lot - see Net_SFTP::_parseAttributes() to understand why
393
- $this->attributes = array(
394
- 0x00000001 => 'NET_SFTP_ATTR_SIZE',
395
- 0x00000002 => 'NET_SFTP_ATTR_UIDGID', // defined in SFTPv3, removed in SFTPv4+
396
- 0x00000004 => 'NET_SFTP_ATTR_PERMISSIONS',
397
- 0x00000008 => 'NET_SFTP_ATTR_ACCESSTIME',
398
- // 0x80000000 will yield a floating point on 32-bit systems and converting floating points to integers
399
- // yields inconsistent behavior depending on how php is compiled. so we left shift -1 (which, in
400
- // two's compliment, consists of all 1 bits) by 31. on 64-bit systems this'll yield 0xFFFFFFFF80000000.
401
- // that's not a problem, however, and 'anded' and a 32-bit number, as all the leading 1 bits are ignored.
402
- (-1 << 31) & 0xFFFFFFFF => 'NET_SFTP_ATTR_EXTENDED'
403
- );
404
- // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-6.3
405
- // the flag definitions change somewhat in SFTPv5+. if SFTPv5+ support is added to this library, maybe name
406
- // the array for that $this->open5_flags and similarly alter the constant names.
407
- $this->open_flags = array(
408
- 0x00000001 => 'NET_SFTP_OPEN_READ',
409
- 0x00000002 => 'NET_SFTP_OPEN_WRITE',
410
- 0x00000004 => 'NET_SFTP_OPEN_APPEND',
411
- 0x00000008 => 'NET_SFTP_OPEN_CREATE',
412
- 0x00000010 => 'NET_SFTP_OPEN_TRUNCATE',
413
- 0x00000020 => 'NET_SFTP_OPEN_EXCL'
414
- );
415
- // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-5.2
416
- // see Net_SFTP::_parseLongname() for an explanation
417
- $this->file_types = array(
418
- 1 => 'NET_SFTP_TYPE_REGULAR',
419
- 2 => 'NET_SFTP_TYPE_DIRECTORY',
420
- 3 => 'NET_SFTP_TYPE_SYMLINK',
421
- 4 => 'NET_SFTP_TYPE_SPECIAL',
422
- 5 => 'NET_SFTP_TYPE_UNKNOWN',
423
- // the followin types were first defined for use in SFTPv5+
424
- // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-05#section-5.2
425
- 6 => 'NET_SFTP_TYPE_SOCKET',
426
- 7 => 'NET_SFTP_TYPE_CHAR_DEVICE',
427
- 8 => 'NET_SFTP_TYPE_BLOCK_DEVICE',
428
- 9 => 'NET_SFTP_TYPE_FIFO'
429
- );
430
- $this->_define_array(
431
- $this->packet_types,
432
- $this->status_codes,
433
- $this->attributes,
434
- $this->open_flags,
435
- $this->file_types
436
- );
437
-
438
- if (!defined('NET_SFTP_QUEUE_SIZE')) {
439
- define('NET_SFTP_QUEUE_SIZE', 32);
440
- }
441
- }
442
-
443
- /**
444
- * PHP4 compatible Default Constructor.
445
- *
446
- * @see self::__construct()
447
- * @param string $host
448
- * @param int $port
449
- * @param int $timeout
450
- * @access public
451
- */
452
- function Net_SFTP($host, $port = 22, $timeout = 10)
453
- {
454
- $this->__construct($host, $port, $timeout);
455
- }
456
-
457
- /**
458
- * Login
459
- *
460
- * @param string $username
461
- * @param string $password
462
- * @return bool
463
- * @access public
464
- */
465
- function login($username)
466
- {
467
- $args = func_get_args();
468
- if (!call_user_func_array(array(&$this, '_login'), $args)) {
469
- return false;
470
- }
471
-
472
- $this->window_size_server_to_client[NET_SFTP_CHANNEL] = $this->window_size;
473
-
474
- $packet = pack(
475
- 'CNa*N3',
476
- NET_SSH2_MSG_CHANNEL_OPEN,
477
- strlen('session'),
478
- 'session',
479
- NET_SFTP_CHANNEL,
480
- $this->window_size,
481
- 0x4000
482
- );
483
-
484
- if (!$this->_send_binary_packet($packet)) {
485
- return false;
486
- }
487
-
488
- $this->channel_status[NET_SFTP_CHANNEL] = NET_SSH2_MSG_CHANNEL_OPEN;
489
-
490
- $response = $this->_get_channel_packet(NET_SFTP_CHANNEL, true);
491
- if ($response === false) {
492
- return false;
493
- }
494
-
495
- $packet = pack(
496
- 'CNNa*CNa*',
497
- NET_SSH2_MSG_CHANNEL_REQUEST,
498
- $this->server_channels[NET_SFTP_CHANNEL],
499
- strlen('subsystem'),
500
- 'subsystem',
501
- 1,
502
- strlen('sftp'),
503
- 'sftp'
504
- );
505
- if (!$this->_send_binary_packet($packet)) {
506
- return false;
507
- }
508
-
509
- $this->channel_status[NET_SFTP_CHANNEL] = NET_SSH2_MSG_CHANNEL_REQUEST;
510
-
511
- $response = $this->_get_channel_packet(NET_SFTP_CHANNEL, true);
512
- if ($response === false) {
513
- // from PuTTY's psftp.exe
514
- $command = "test -x /usr/lib/sftp-server && exec /usr/lib/sftp-server\n" .
515
- "test -x /usr/local/lib/sftp-server && exec /usr/local/lib/sftp-server\n" .
516
- "exec sftp-server";
517
- // we don't do $this->exec($command, false) because exec() operates on a different channel and plus the SSH_MSG_CHANNEL_OPEN that exec() does
518
- // is redundant
519
- $packet = pack(
520
- 'CNNa*CNa*',
521
- NET_SSH2_MSG_CHANNEL_REQUEST,
522
- $this->server_channels[NET_SFTP_CHANNEL],
523
- strlen('exec'),
524
- 'exec',
525
- 1,
526
- strlen($command),
527
- $command
528
- );
529
- if (!$this->_send_binary_packet($packet)) {
530
- return false;
531
- }
532
-
533
- $this->channel_status[NET_SFTP_CHANNEL] = NET_SSH2_MSG_CHANNEL_REQUEST;
534
-
535
- $response = $this->_get_channel_packet(NET_SFTP_CHANNEL, true);
536
- if ($response === false) {
537
- return false;
538
- }
539
- }
540
-
541
- $this->channel_status[NET_SFTP_CHANNEL] = NET_SSH2_MSG_CHANNEL_DATA;
542
-
543
- if (!$this->_send_sftp_packet(NET_SFTP_INIT, "\0\0\0\3")) {
544
- return false;
545
- }
546
-
547
- $response = $this->_get_sftp_packet();
548
- if ($this->packet_type != NET_SFTP_VERSION) {
549
- user_error('Expected SSH_FXP_VERSION');
550
- return false;
551
- }
552
-
553
- if (strlen($response) < 4) {
554
- return false;
555
- }
556
- extract(unpack('Nversion', $this->_string_shift($response, 4)));
557
- $this->version = $version;
558
- while (!empty($response)) {
559
- if (strlen($response) < 4) {
560
- return false;
561
- }
562
- extract(unpack('Nlength', $this->_string_shift($response, 4)));
563
- $key = $this->_string_shift($response, $length);
564
- if (strlen($response) < 4) {
565
- return false;
566
- }
567
- extract(unpack('Nlength', $this->_string_shift($response, 4)));
568
- $value = $this->_string_shift($response, $length);
569
- $this->extensions[$key] = $value;
570
- }
571
-
572
- /*
573
- SFTPv4+ defines a 'newline' extension. SFTPv3 seems to have unofficial support for it via 'newline@vandyke.com',
574
- however, I'm not sure what 'newline@vandyke.com' is supposed to do (the fact that it's unofficial means that it's
575
- not in the official SFTPv3 specs) and 'newline@vandyke.com' / 'newline' are likely not drop-in substitutes for
576
- one another due to the fact that 'newline' comes with a SSH_FXF_TEXT bitmask whereas it seems unlikely that
577
- 'newline@vandyke.com' would.
578
- */
579
- /*
580
- if (isset($this->extensions['newline@vandyke.com'])) {
581
- $this->extensions['newline'] = $this->extensions['newline@vandyke.com'];
582
- unset($this->extensions['newline@vandyke.com']);
583
- }
584
- */
585
-
586
- $this->use_request_id = true;
587
-
588
- /*
589
- A Note on SFTPv4/5/6 support:
590
- <http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-5.1> states the following:
591
-
592
- "If the client wishes to interoperate with servers that support noncontiguous version
593
- numbers it SHOULD send '3'"
594
-
595
- Given that the server only sends its version number after the client has already done so, the above
596
- seems to be suggesting that v3 should be the default version. This makes sense given that v3 is the
597
- most popular.
598
-
599
- <http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-5.5> states the following;
600
-
601
- "If the server did not send the "versions" extension, or the version-from-list was not included, the
602
- server MAY send a status response describing the failure, but MUST then close the channel without
603
- processing any further requests."
604
-
605
- So what do you do if you have a client whose initial SSH_FXP_INIT packet says it implements v3 and
606
- a server whose initial SSH_FXP_VERSION reply says it implements v4 and only v4? If it only implements
607
- v4, the "versions" extension is likely not going to have been sent so version re-negotiation as discussed
608
- in draft-ietf-secsh-filexfer-13 would be quite impossible. As such, what Net_SFTP would do is close the
609
- channel and reopen it with a new and updated SSH_FXP_INIT packet.
610
- */
611
- switch ($this->version) {
612
- case 2:
613
- case 3:
614
- break;
615
- default:
616
- return false;
617
- }
618
-
619
- $this->pwd = $this->_realpath('.');
620
-
621
- $this->_update_stat_cache($this->pwd, array());
622
-
623
- return true;
624
- }
625
-
626
- /**
627
- * Disable the stat cache
628
- *
629
- * @access public
630
- */
631
- function disableStatCache()
632
- {
633
- $this->use_stat_cache = false;
634
- }
635
-
636
- /**
637
- * Enable the stat cache
638
- *
639
- * @access public
640
- */
641
- function enableStatCache()
642
- {
643
- $this->use_stat_cache = true;
644
- }
645
-
646
- /**
647
- * Clear the stat cache
648
- *
649
- * @access public
650
- */
651
- function clearStatCache()
652
- {
653
- $this->stat_cache = array();
654
- }
655
-
656
- /**
657
- * Enable path canonicalization
658
- *
659
- * @access public
660
- */
661
- function enablePathCanonicalization()
662
- {
663
- $this->canonicalize_paths = true;
664
- }
665
-
666
- /**
667
- * Enable path canonicalization
668
- *
669
- * @access public
670
- */
671
- function disablePathCanonicalization()
672
- {
673
- $this->canonicalize_paths = false;
674
- }
675
-
676
- /**
677
- * Returns the current directory name
678
- *
679
- * @return mixed
680
- * @access public
681
- */
682
- function pwd()
683
- {
684
- return $this->pwd;
685
- }
686
-
687
- /**
688
- * Logs errors
689
- *
690
- * @param string $response
691
- * @param int $status
692
- * @access public
693
- */
694
- function _logError($response, $status = -1)
695
- {
696
- if ($status == -1) {
697
- if (strlen($response) < 4) {
698
- return;
699
- }
700
- extract(unpack('Nstatus', $this->_string_shift($response, 4)));
701
- }
702
-
703
- $error = $this->status_codes[$status];
704
-
705
- if ($this->version > 2 || strlen($response) < 4) {
706
- extract(unpack('Nlength', $this->_string_shift($response, 4)));
707
- $this->sftp_errors[] = $error . ': ' . $this->_string_shift($response, $length);
708
- } else {
709
- $this->sftp_errors[] = $error;
710
- }
711
- }
712
-
713
- /**
714
- * Returns canonicalized absolute pathname
715
- *
716
- * realpath() expands all symbolic links and resolves references to '/./', '/../' and extra '/' characters in the input
717
- * path and returns the canonicalized absolute pathname.
718
- *
719
- * @param string $path
720
- * @return mixed
721
- * @access public
722
- */
723
- function realpath($path)
724
- {
725
- return $this->_realpath($path);
726
- }
727
-
728
- /**
729
- * Canonicalize the Server-Side Path Name
730
- *
731
- * SFTP doesn't provide a mechanism by which the current working directory can be changed, so we'll emulate it. Returns
732
- * the absolute (canonicalized) path.
733
- *
734
- * If canonicalize_paths has been disabled using disablePathCanonicalization(), $path is returned as-is.
735
- *
736
- * @see self::chdir()
737
- * @see self::disablePathCanonicalization()
738
- * @param string $path
739
- * @return mixed
740
- * @access private
741
- */
742
- function _realpath($path)
743
- {
744
- if (!$this->canonicalize_paths) {
745
- return $path;
746
- }
747
-
748
- if ($this->pwd === false) {
749
- // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.9
750
- if (!$this->_send_sftp_packet(NET_SFTP_REALPATH, pack('Na*', strlen($path), $path))) {
751
- return false;
752
- }
753
-
754
- $response = $this->_get_sftp_packet();
755
- switch ($this->packet_type) {
756
- case NET_SFTP_NAME:
757
- // although SSH_FXP_NAME is implemented differently in SFTPv3 than it is in SFTPv4+, the following
758
- // should work on all SFTP versions since the only part of the SSH_FXP_NAME packet the following looks
759
- // at is the first part and that part is defined the same in SFTP versions 3 through 6.
760
- $this->_string_shift($response, 4); // skip over the count - it should be 1, anyway
761
- if (strlen($response) < 4) {
762
- return false;
763
- }
764
- extract(unpack('Nlength', $this->_string_shift($response, 4)));
765
- return $this->_string_shift($response, $length);
766
- case NET_SFTP_STATUS:
767
- $this->_logError($response);
768
- return false;
769
- default:
770
- user_error('Expected SSH_FXP_NAME or SSH_FXP_STATUS');
771
- return false;
772
- }
773
- }
774
-
775
- if ($path[0] != '/') {
776
- $path = $this->pwd . '/' . $path;
777
- }
778
-
779
- $path = explode('/', $path);
780
- $new = array();
781
- foreach ($path as $dir) {
782
- if (!strlen($dir)) {
783
- continue;
784
- }
785
- switch ($dir) {
786
- case '..':
787
- array_pop($new);
788
- case '.':
789
- break;
790
- default:
791
- $new[] = $dir;
792
- }
793
- }
794
-
795
- return '/' . implode('/', $new);
796
- }
797
-
798
- /**
799
- * Changes the current directory
800
- *
801
- * @param string $dir
802
- * @return bool
803
- * @access public
804
- */
805
- function chdir($dir)
806
- {
807
- if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
808
- return false;
809
- }
810
-
811
- // assume current dir if $dir is empty
812
- if ($dir === '') {
813
- $dir = './';
814
- // suffix a slash if needed
815
- } elseif ($dir[strlen($dir) - 1] != '/') {
816
- $dir.= '/';
817
- }
818
-
819
- $dir = $this->_realpath($dir);
820
-
821
- // confirm that $dir is, in fact, a valid directory
822
- if ($this->use_stat_cache && is_array($this->_query_stat_cache($dir))) {
823
- $this->pwd = $dir;
824
- return true;
825
- }
826
-
827
- // we could do a stat on the alleged $dir to see if it's a directory but that doesn't tell us
828
- // the currently logged in user has the appropriate permissions or not. maybe you could see if
829
- // the file's uid / gid match the currently logged in user's uid / gid but how there's no easy
830
- // way to get those with SFTP
831
-
832
- if (!$this->_send_sftp_packet(NET_SFTP_OPENDIR, pack('Na*', strlen($dir), $dir))) {
833
- return false;
834
- }
835
-
836
- // see Net_SFTP::nlist() for a more thorough explanation of the following
837
- $response = $this->_get_sftp_packet();
838
- switch ($this->packet_type) {
839
- case NET_SFTP_HANDLE:
840
- $handle = substr($response, 4);
841
- break;
842
- case NET_SFTP_STATUS:
843
- $this->_logError($response);
844
- return false;
845
- default:
846
- user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS');
847
- return false;
848
- }
849
-
850
- if (!$this->_close_handle($handle)) {
851
- return false;
852
- }
853
-
854
- $this->_update_stat_cache($dir, array());
855
-
856
- $this->pwd = $dir;
857
- return true;
858
- }
859
-
860
- /**
861
- * Returns a list of files in the given directory
862
- *
863
- * @param string $dir
864
- * @param bool $recursive
865
- * @return mixed
866
- * @access public
867
- */
868
- function nlist($dir = '.', $recursive = false)
869
- {
870
- return $this->_nlist_helper($dir, $recursive, '');
871
- }
872
-
873
- /**
874
- * Helper method for nlist
875
- *
876
- * @param string $dir
877
- * @param bool $recursive
878
- * @param string $relativeDir
879
- * @return mixed
880
- * @access private
881
- */
882
- function _nlist_helper($dir, $recursive, $relativeDir)
883
- {
884
- $files = $this->_list($dir, false);
885
-
886
- if (!$recursive || $files === false) {
887
- return $files;
888
- }
889
-
890
- $result = array();
891
- foreach ($files as $value) {
892
- if ($value == '.' || $value == '..') {
893
- if ($relativeDir == '') {
894
- $result[] = $value;
895
- }
896
- continue;
897
- }
898
- if (is_array($this->_query_stat_cache($this->_realpath($dir . '/' . $value)))) {
899
- $temp = $this->_nlist_helper($dir . '/' . $value, true, $relativeDir . $value . '/');
900
- $result = array_merge($result, $temp);
901
- } else {
902
- $result[] = $relativeDir . $value;
903
- }
904
- }
905
-
906
- return $result;
907
- }
908
-
909
- /**
910
- * Returns a detailed list of files in the given directory
911
- *
912
- * @param string $dir
913
- * @param bool $recursive
914
- * @return mixed
915
- * @access public
916
- */
917
- function rawlist($dir = '.', $recursive = false)
918
- {
919
- $files = $this->_list($dir, true);
920
- if (!$recursive || $files === false) {
921
- return $files;
922
- }
923
-
924
- static $depth = 0;
925
-
926
- foreach ($files as $key => $value) {
927
- if ($depth != 0 && $key == '..') {
928
- unset($files[$key]);
929
- continue;
930
- }
931
- $is_directory = false;
932
- if ($key != '.' && $key != '..') {
933
- if ($this->use_stat_cache) {
934
- $is_directory = is_array($this->_query_stat_cache($this->_realpath($dir . '/' . $key)));
935
- } else {
936
- $stat = $this->lstat($dir . '/' . $key);
937
- $is_directory = $stat && $stat['type'] === NET_SFTP_TYPE_DIRECTORY;
938
- }
939
- }
940
-
941
- if ($is_directory) {
942
- $depth++;
943
- $files[$key] = $this->rawlist($dir . '/' . $key, true);
944
- $depth--;
945
- } else {
946
- $files[$key] = (object) $value;
947
- }
948
- }
949
-
950
- return $files;
951
- }
952
-
953
- /**
954
- * Reads a list, be it detailed or not, of files in the given directory
955
- *
956
- * @param string $dir
957
- * @param bool $raw
958
- * @return mixed
959
- * @access private
960
- */
961
- function _list($dir, $raw = true)
962
- {
963
- if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
964
- return false;
965
- }
966
-
967
- $dir = $this->_realpath($dir . '/');
968
- if ($dir === false) {
969
- return false;
970
- }
971
-
972
- // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.1.2
973
- if (!$this->_send_sftp_packet(NET_SFTP_OPENDIR, pack('Na*', strlen($dir), $dir))) {
974
- return false;
975
- }
976
-
977
- $response = $this->_get_sftp_packet();
978
- switch ($this->packet_type) {
979
- case NET_SFTP_HANDLE:
980
- // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-9.2
981
- // since 'handle' is the last field in the SSH_FXP_HANDLE packet, we'll just remove the first four bytes that
982
- // represent the length of the string and leave it at that
983
- $handle = substr($response, 4);
984
- break;
985
- case NET_SFTP_STATUS:
986
- // presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED
987
- $this->_logError($response);
988
- return false;
989
- default:
990
- user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS');
991
- return false;
992
- }
993
-
994
- $this->_update_stat_cache($dir, array());
995
-
996
- $contents = array();
997
- while (true) {
998
- // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.2.2
999
- // why multiple SSH_FXP_READDIR packets would be sent when the response to a single one can span arbitrarily many
1000
- // SSH_MSG_CHANNEL_DATA messages is not known to me.
1001
- if (!$this->_send_sftp_packet(NET_SFTP_READDIR, pack('Na*', strlen($handle), $handle))) {
1002
- return false;
1003
- }
1004
-
1005
- $response = $this->_get_sftp_packet();
1006
- switch ($this->packet_type) {
1007
- case NET_SFTP_NAME:
1008
- if (strlen($response) < 4) {
1009
- return false;
1010
- }
1011
- extract(unpack('Ncount', $this->_string_shift($response, 4)));
1012
- for ($i = 0; $i < $count; $i++) {
1013
- if (strlen($response) < 4) {
1014
- return false;
1015
- }
1016
- extract(unpack('Nlength', $this->_string_shift($response, 4)));
1017
- $shortname = $this->_string_shift($response, $length);
1018
- if (strlen($response) < 4) {
1019
- return false;
1020
- }
1021
- extract(unpack('Nlength', $this->_string_shift($response, 4)));
1022
- $longname = $this->_string_shift($response, $length);
1023
- $attributes = $this->_parseAttributes($response);
1024
- if (!isset($attributes['type'])) {
1025
- $fileType = $this->_parseLongname($longname);
1026
- if ($fileType) {
1027
- $attributes['type'] = $fileType;
1028
- }
1029
- }
1030
- $contents[$shortname] = $attributes + array('filename' => $shortname);
1031
-
1032
- if (isset($attributes['type']) && $attributes['type'] == NET_SFTP_TYPE_DIRECTORY && ($shortname != '.' && $shortname != '..')) {
1033
- $this->_update_stat_cache($dir . '/' . $shortname, array());
1034
- } else {
1035
- if ($shortname == '..') {
1036
- $temp = $this->_realpath($dir . '/..') . '/.';
1037
- } else {
1038
- $temp = $dir . '/' . $shortname;
1039
- }
1040
- $this->_update_stat_cache($temp, (object) array('lstat' => $attributes));
1041
- }
1042
- // SFTPv6 has an optional boolean end-of-list field, but we'll ignore that, since the
1043
- // final SSH_FXP_STATUS packet should tell us that, already.
1044
- }
1045
- break;
1046
- case NET_SFTP_STATUS:
1047
- if (strlen($response) < 4) {
1048
- return false;
1049
- }
1050
- extract(unpack('Nstatus', $this->_string_shift($response, 4)));
1051
- if ($status != NET_SFTP_STATUS_EOF) {
1052
- $this->_logError($response, $status);
1053
- return false;
1054
- }
1055
- break 2;
1056
- default:
1057
- user_error('Expected SSH_FXP_NAME or SSH_FXP_STATUS');
1058
- return false;
1059
- }
1060
- }
1061
-
1062
- if (!$this->_close_handle($handle)) {
1063
- return false;
1064
- }
1065
-
1066
- if (count($this->sortOptions)) {
1067
- uasort($contents, array(&$this, '_comparator'));
1068
- }
1069
-
1070
- return $raw ? $contents : array_keys($contents);
1071
- }
1072
-
1073
- /**
1074
- * Compares two rawlist entries using parameters set by setListOrder()
1075
- *
1076
- * Intended for use with uasort()
1077
- *
1078
- * @param array $a
1079
- * @param array $b
1080
- * @return int
1081
- * @access private
1082
- */
1083
- function _comparator($a, $b)
1084
- {
1085
- switch (true) {
1086
- case $a['filename'] === '.' || $b['filename'] === '.':
1087
- if ($a['filename'] === $b['filename']) {
1088
- return 0;
1089
- }
1090
- return $a['filename'] === '.' ? -1 : 1;
1091
- case $a['filename'] === '..' || $b['filename'] === '..':
1092
- if ($a['filename'] === $b['filename']) {
1093
- return 0;
1094
- }
1095
- return $a['filename'] === '..' ? -1 : 1;
1096
- case isset($a['type']) && $a['type'] === NET_SFTP_TYPE_DIRECTORY:
1097
- if (!isset($b['type'])) {
1098
- return 1;
1099
- }
1100
- if ($b['type'] !== $a['type']) {
1101
- return -1;
1102
- }
1103
- break;
1104
- case isset($b['type']) && $b['type'] === NET_SFTP_TYPE_DIRECTORY:
1105
- return 1;
1106
- }
1107
- foreach ($this->sortOptions as $sort => $order) {
1108
- if (!isset($a[$sort]) || !isset($b[$sort])) {
1109
- if (isset($a[$sort])) {
1110
- return -1;
1111
- }
1112
- if (isset($b[$sort])) {
1113
- return 1;
1114
- }
1115
- return 0;
1116
- }
1117
- switch ($sort) {
1118
- case 'filename':
1119
- $result = strcasecmp($a['filename'], $b['filename']);
1120
- if ($result) {
1121
- return $order === SORT_DESC ? -$result : $result;
1122
- }
1123
- break;
1124
- case 'permissions':
1125
- case 'mode':
1126
- $a[$sort]&= 07777;
1127
- $b[$sort]&= 07777;
1128
- default:
1129
- if ($a[$sort] === $b[$sort]) {
1130
- break;
1131
- }
1132
- return $order === SORT_ASC ? $a[$sort] - $b[$sort] : $b[$sort] - $a[$sort];
1133
- }
1134
- }
1135
- }
1136
-
1137
- /**
1138
- * Defines how nlist() and rawlist() will be sorted - if at all.
1139
- *
1140
- * If sorting is enabled directories and files will be sorted independently with
1141
- * directories appearing before files in the resultant array that is returned.
1142
- *
1143
- * Any parameter returned by stat is a valid sort parameter for this function.
1144
- * Filename comparisons are case insensitive.
1145
- *
1146
- * Examples:
1147
- *
1148
- * $sftp->setListOrder('filename', SORT_ASC);
1149
- * $sftp->setListOrder('size', SORT_DESC, 'filename', SORT_ASC);
1150
- * $sftp->setListOrder(true);
1151
- * Separates directories from files but doesn't do any sorting beyond that
1152
- * $sftp->setListOrder();
1153
- * Don't do any sort of sorting
1154
- *
1155
- * @access public
1156
- */
1157
- function setListOrder()
1158
- {
1159
- $this->sortOptions = array();
1160
- $args = func_get_args();
1161
- if (empty($args)) {
1162
- return;
1163
- }
1164
- $len = count($args) & 0x7FFFFFFE;
1165
- for ($i = 0; $i < $len; $i+=2) {
1166
- $this->sortOptions[$args[$i]] = $args[$i + 1];
1167
- }
1168
- if (!count($this->sortOptions)) {
1169
- $this->sortOptions = array('bogus' => true);
1170
- }
1171
- }
1172
-
1173
- /**
1174
- * Returns the file size, in bytes, or false, on failure
1175
- *
1176
- * Files larger than 4GB will show up as being exactly 4GB.
1177
- *
1178
- * @param string $filename
1179
- * @return mixed
1180
- * @access public
1181
- */
1182
- function size($filename)
1183
- {
1184
- if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
1185
- return false;
1186
- }
1187
-
1188
- $result = $this->stat($filename);
1189
- if ($result === false) {
1190
- return false;
1191
- }
1192
- return isset($result['size']) ? $result['size'] : -1;
1193
- }
1194
-
1195
- /**
1196
- * Save files / directories to cache
1197
- *
1198
- * @param string $path
1199
- * @param mixed $value
1200
- * @access private
1201
- */
1202
- function _update_stat_cache($path, $value)
1203
- {
1204
- if ($this->use_stat_cache === false) {
1205
- return;
1206
- }
1207
-
1208
- // preg_replace('#^/|/(?=/)|/$#', '', $dir) == str_replace('//', '/', trim($path, '/'))
1209
- $dirs = explode('/', preg_replace('#^/|/(?=/)|/$#', '', $path));
1210
-
1211
- $temp = &$this->stat_cache;
1212
- $max = count($dirs) - 1;
1213
- foreach ($dirs as $i => $dir) {
1214
- // if $temp is an object that means one of two things.
1215
- // 1. a file was deleted and changed to a directory behind phpseclib's back
1216
- // 2. it's a symlink. when lstat is done it's unclear what it's a symlink to
1217
- if (is_object($temp)) {
1218
- $temp = array();
1219
- }
1220
- if (!isset($temp[$dir])) {
1221
- $temp[$dir] = array();
1222
- }
1223
- if ($i === $max) {
1224
- if (is_object($temp[$dir]) && is_object($value)) {
1225
- if (!isset($value->stat) && isset($temp[$dir]->stat)) {
1226
- $value->stat = $temp[$dir]->stat;
1227
- }
1228
- if (!isset($value->lstat) && isset($temp[$dir]->lstat)) {
1229
- $value->lstat = $temp[$dir]->lstat;
1230
- }
1231
- }
1232
- $temp[$dir] = $value;
1233
- break;
1234
- }
1235
- $temp = &$temp[$dir];
1236
- }
1237
- }
1238
-
1239
- /**
1240
- * Remove files / directories from cache
1241
- *
1242
- * @param string $path
1243
- * @return bool
1244
- * @access private
1245
- */
1246
- function _remove_from_stat_cache($path)
1247
- {
1248
- $dirs = explode('/', preg_replace('#^/|/(?=/)|/$#', '', $path));
1249
-
1250
- $temp = &$this->stat_cache;
1251
- $max = count($dirs) - 1;
1252
- foreach ($dirs as $i => $dir) {
1253
- if ($i === $max) {
1254
- unset($temp[$dir]);
1255
- return true;
1256
- }
1257
- if (!isset($temp[$dir])) {
1258
- return false;
1259
- }
1260
- $temp = &$temp[$dir];
1261
- }
1262
- }
1263
-
1264
- /**
1265
- * Checks cache for path
1266
- *
1267
- * Mainly used by file_exists
1268
- *
1269
- * @param string $dir
1270
- * @return mixed
1271
- * @access private
1272
- */
1273
- function _query_stat_cache($path)
1274
- {
1275
- $dirs = explode('/', preg_replace('#^/|/(?=/)|/$#', '', $path));
1276
-
1277
- $temp = &$this->stat_cache;
1278
- foreach ($dirs as $dir) {
1279
- if (!isset($temp[$dir])) {
1280
- return null;
1281
- }
1282
- $temp = &$temp[$dir];
1283
- }
1284
- return $temp;
1285
- }
1286
-
1287
- /**
1288
- * Returns general information about a file.
1289
- *
1290
- * Returns an array on success and false otherwise.
1291
- *
1292
- * @param string $filename
1293
- * @return mixed
1294
- * @access public
1295
- */
1296
- function stat($filename)
1297
- {
1298
- if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
1299
- return false;
1300
- }
1301
-
1302
- $filename = $this->_realpath($filename);
1303
- if ($filename === false) {
1304
- return false;
1305
- }
1306
-
1307
- if ($this->use_stat_cache) {
1308
- $result = $this->_query_stat_cache($filename);
1309
- if (is_array($result) && isset($result['.']) && isset($result['.']->stat)) {
1310
- return $result['.']->stat;
1311
- }
1312
- if (is_object($result) && isset($result->stat)) {
1313
- return $result->stat;
1314
- }
1315
- }
1316
-
1317
- $stat = $this->_stat($filename, NET_SFTP_STAT);
1318
- if ($stat === false) {
1319
- $this->_remove_from_stat_cache($filename);
1320
- return false;
1321
- }
1322
- if (isset($stat['type'])) {
1323
- if ($stat['type'] == NET_SFTP_TYPE_DIRECTORY) {
1324
- $filename.= '/.';
1325
- }
1326
- $this->_update_stat_cache($filename, (object) array('stat' => $stat));
1327
- return $stat;
1328
- }
1329
-
1330
- $pwd = $this->pwd;
1331
- $stat['type'] = $this->chdir($filename) ?
1332
- NET_SFTP_TYPE_DIRECTORY :
1333
- NET_SFTP_TYPE_REGULAR;
1334
- $this->pwd = $pwd;
1335
-
1336
- if ($stat['type'] == NET_SFTP_TYPE_DIRECTORY) {
1337
- $filename.= '/.';
1338
- }
1339
- $this->_update_stat_cache($filename, (object) array('stat' => $stat));
1340
-
1341
- return $stat;
1342
- }
1343
-
1344
- /**
1345
- * Returns general information about a file or symbolic link.
1346
- *
1347
- * Returns an array on success and false otherwise.
1348
- *
1349
- * @param string $filename
1350
- * @return mixed
1351
- * @access public
1352
- */
1353
- function lstat($filename)
1354
- {
1355
- if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
1356
- return false;
1357
- }
1358
-
1359
- $filename = $this->_realpath($filename);
1360
- if ($filename === false) {
1361
- return false;
1362
- }
1363
-
1364
- if ($this->use_stat_cache) {
1365
- $result = $this->_query_stat_cache($filename);
1366
- if (is_array($result) && isset($result['.']) && isset($result['.']->lstat)) {
1367
- return $result['.']->lstat;
1368
- }
1369
- if (is_object($result) && isset($result->lstat)) {
1370
- return $result->lstat;
1371
- }
1372
- }
1373
-
1374
- $lstat = $this->_stat($filename, NET_SFTP_LSTAT);
1375
- if ($lstat === false) {
1376
- $this->_remove_from_stat_cache($filename);
1377
- return false;
1378
- }
1379
- if (isset($lstat['type'])) {
1380
- if ($lstat['type'] == NET_SFTP_TYPE_DIRECTORY) {
1381
- $filename.= '/.';
1382
- }
1383
- $this->_update_stat_cache($filename, (object) array('lstat' => $lstat));
1384
- return $lstat;
1385
- }
1386
-
1387
- $stat = $this->_stat($filename, NET_SFTP_STAT);
1388
-
1389
- if ($lstat != $stat) {
1390
- $lstat = array_merge($lstat, array('type' => NET_SFTP_TYPE_SYMLINK));
1391
- $this->_update_stat_cache($filename, (object) array('lstat' => $lstat));
1392
- return $stat;
1393
- }
1394
-
1395
- $pwd = $this->pwd;
1396
- $lstat['type'] = $this->chdir($filename) ?
1397
- NET_SFTP_TYPE_DIRECTORY :
1398
- NET_SFTP_TYPE_REGULAR;
1399
- $this->pwd = $pwd;
1400
-
1401
- if ($lstat['type'] == NET_SFTP_TYPE_DIRECTORY) {
1402
- $filename.= '/.';
1403
- }
1404
- $this->_update_stat_cache($filename, (object) array('lstat' => $lstat));
1405
-
1406
- return $lstat;
1407
- }
1408
-
1409
- /**
1410
- * Returns general information about a file or symbolic link
1411
- *
1412
- * Determines information without calling Net_SFTP::realpath().
1413
- * The second parameter can be either NET_SFTP_STAT or NET_SFTP_LSTAT.
1414
- *
1415
- * @param string $filename
1416
- * @param int $type
1417
- * @return mixed
1418
- * @access private
1419
- */
1420
- function _stat($filename, $type)
1421
- {
1422
- // SFTPv4+ adds an additional 32-bit integer field - flags - to the following:
1423
- $packet = pack('Na*', strlen($filename), $filename);
1424
- if (!$this->_send_sftp_packet($type, $packet)) {
1425
- return false;
1426
- }
1427
-
1428
- $response = $this->_get_sftp_packet();
1429
- switch ($this->packet_type) {
1430
- case NET_SFTP_ATTRS:
1431
- return $this->_parseAttributes($response);
1432
- case NET_SFTP_STATUS:
1433
- $this->_logError($response);
1434
- return false;
1435
- }
1436
-
1437
- user_error('Expected SSH_FXP_ATTRS or SSH_FXP_STATUS');
1438
- return false;
1439
- }
1440
-
1441
- /**
1442
- * Truncates a file to a given length
1443
- *
1444
- * @param string $filename
1445
- * @param int $new_size
1446
- * @return bool
1447
- * @access public
1448
- */
1449
- function truncate($filename, $new_size)
1450
- {
1451
- $attr = pack('N3', NET_SFTP_ATTR_SIZE, $new_size / 4294967296, $new_size); // 4294967296 == 0x100000000 == 1<<32
1452
-
1453
- return $this->_setstat($filename, $attr, false);
1454
- }
1455
-
1456
- /**
1457
- * Sets access and modification time of file.
1458
- *
1459
- * If the file does not exist, it will be created.
1460
- *
1461
- * @param string $filename
1462
- * @param int $time
1463
- * @param int $atime
1464
- * @return bool
1465
- * @access public
1466
- */
1467
- function touch($filename, $time = null, $atime = null)
1468
- {
1469
- if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
1470
- return false;
1471
- }
1472
-
1473
- $filename = $this->_realpath($filename);
1474
- if ($filename === false) {
1475
- return false;
1476
- }
1477
-
1478
- if (!isset($time)) {
1479
- $time = time();
1480
- }
1481
- if (!isset($atime)) {
1482
- $atime = $time;
1483
- }
1484
-
1485
- $flags = NET_SFTP_OPEN_WRITE | NET_SFTP_OPEN_CREATE | NET_SFTP_OPEN_EXCL;
1486
- $attr = pack('N3', NET_SFTP_ATTR_ACCESSTIME, $time, $atime);
1487
- $packet = pack('Na*Na*', strlen($filename), $filename, $flags, $attr);
1488
- if (!$this->_send_sftp_packet(NET_SFTP_OPEN, $packet)) {
1489
- return false;
1490
- }
1491
-
1492
- $response = $this->_get_sftp_packet();
1493
- switch ($this->packet_type) {
1494
- case NET_SFTP_HANDLE:
1495
- return $this->_close_handle(substr($response, 4));
1496
- case NET_SFTP_STATUS:
1497
- $this->_logError($response);
1498
- break;
1499
- default:
1500
- user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS');
1501
- return false;
1502
- }
1503
-
1504
- return $this->_setstat($filename, $attr, false);
1505
- }
1506
-
1507
- /**
1508
- * Changes file or directory owner
1509
- *
1510
- * Returns true on success or false on error.
1511
- *
1512
- * @param string $filename
1513
- * @param int $uid
1514
- * @param bool $recursive
1515
- * @return bool
1516
- * @access public
1517
- */
1518
- function chown($filename, $uid, $recursive = false)
1519
- {
1520
- // quoting from <http://www.kernel.org/doc/man-pages/online/pages/man2/chown.2.html>,
1521
- // "if the owner or group is specified as -1, then that ID is not changed"
1522
- $attr = pack('N3', NET_SFTP_ATTR_UIDGID, $uid, -1);
1523
-
1524
- return $this->_setstat($filename, $attr, $recursive);
1525
- }
1526
-
1527
- /**
1528
- * Changes file or directory group
1529
- *
1530
- * Returns true on success or false on error.
1531
- *
1532
- * @param string $filename
1533
- * @param int $gid
1534
- * @param bool $recursive
1535
- * @return bool
1536
- * @access public
1537
- */
1538
- function chgrp($filename, $gid, $recursive = false)
1539
- {
1540
- $attr = pack('N3', NET_SFTP_ATTR_UIDGID, -1, $gid);
1541
-
1542
- return $this->_setstat($filename, $attr, $recursive);
1543
- }
1544
-
1545
- /**
1546
- * Set permissions on a file.
1547
- *
1548
- * Returns the new file permissions on success or false on error.
1549
- * If $recursive is true than this just returns true or false.
1550
- *
1551
- * @param int $mode
1552
- * @param string $filename
1553
- * @param bool $recursive
1554
- * @return mixed
1555
- * @access public
1556
- */
1557
- function chmod($mode, $filename, $recursive = false)
1558
- {
1559
- if (is_string($mode) && is_int($filename)) {
1560
- $temp = $mode;
1561
- $mode = $filename;
1562
- $filename = $temp;
1563
- }
1564
-
1565
- $attr = pack('N2', NET_SFTP_ATTR_PERMISSIONS, $mode & 07777);
1566
- if (!$this->_setstat($filename, $attr, $recursive)) {
1567
- return false;
1568
- }
1569
- if ($recursive) {
1570
- return true;
1571
- }
1572
-
1573
- $filename = $this->realpath($filename);
1574
- // rather than return what the permissions *should* be, we'll return what they actually are. this will also
1575
- // tell us if the file actually exists.
1576
- // incidentally, SFTPv4+ adds an additional 32-bit integer field - flags - to the following:
1577
- $packet = pack('Na*', strlen($filename), $filename);
1578
- if (!$this->_send_sftp_packet(NET_SFTP_STAT, $packet)) {
1579
- return false;
1580
- }
1581
-
1582
- $response = $this->_get_sftp_packet();
1583
- switch ($this->packet_type) {
1584
- case NET_SFTP_ATTRS:
1585
- $attrs = $this->_parseAttributes($response);
1586
- return $attrs['permissions'];
1587
- case NET_SFTP_STATUS:
1588
- $this->_logError($response);
1589
- return false;
1590
- }
1591
-
1592
- user_error('Expected SSH_FXP_ATTRS or SSH_FXP_STATUS');
1593
- return false;
1594
- }
1595
-
1596
- /**
1597
- * Sets information about a file
1598
- *
1599
- * @param string $filename
1600
- * @param string $attr
1601
- * @param bool $recursive
1602
- * @return bool
1603
- * @access private
1604
- */
1605
- function _setstat($filename, $attr, $recursive)
1606
- {
1607
- if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
1608
- return false;
1609
- }
1610
-
1611
- $filename = $this->_realpath($filename);
1612
- if ($filename === false) {
1613
- return false;
1614
- }
1615
-
1616
- $this->_remove_from_stat_cache($filename);
1617
-
1618
- if ($recursive) {
1619
- $i = 0;
1620
- $result = $this->_setstat_recursive($filename, $attr, $i);
1621
- $this->_read_put_responses($i);
1622
- return $result;
1623
- }
1624
-
1625
- // SFTPv4+ has an additional byte field - type - that would need to be sent, as well. setting it to
1626
- // SSH_FILEXFER_TYPE_UNKNOWN might work. if not, we'd have to do an SSH_FXP_STAT before doing an SSH_FXP_SETSTAT.
1627
- if (!$this->_send_sftp_packet(NET_SFTP_SETSTAT, pack('Na*a*', strlen($filename), $filename, $attr))) {
1628
- return false;
1629
- }
1630
-
1631
- /*
1632
- "Because some systems must use separate system calls to set various attributes, it is possible that a failure
1633
- response will be returned, but yet some of the attributes may be have been successfully modified. If possible,
1634
- servers SHOULD avoid this situation; however, clients MUST be aware that this is possible."
1635
-
1636
- -- http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.6
1637
- */
1638
- $response = $this->_get_sftp_packet();
1639
- if ($this->packet_type != NET_SFTP_STATUS) {
1640
- user_error('Expected SSH_FXP_STATUS');
1641
- return false;
1642
- }
1643
-
1644
- if (strlen($response) < 4) {
1645
- return false;
1646
- }
1647
- extract(unpack('Nstatus', $this->_string_shift($response, 4)));
1648
- if ($status != NET_SFTP_STATUS_OK) {
1649
- $this->_logError($response, $status);
1650
- return false;
1651
- }
1652
-
1653
- return true;
1654
- }
1655
-
1656
- /**
1657
- * Recursively sets information on directories on the SFTP server
1658
- *
1659
- * Minimizes directory lookups and SSH_FXP_STATUS requests for speed.
1660
- *
1661
- * @param string $path
1662
- * @param string $attr
1663
- * @param int $i
1664
- * @return bool
1665
- * @access private
1666
- */
1667
- function _setstat_recursive($path, $attr, &$i)
1668
- {
1669
- if (!$this->_read_put_responses($i)) {
1670
- return false;
1671
- }
1672
- $i = 0;
1673
- $entries = $this->_list($path, true);
1674
-
1675
- if ($entries === false) {
1676
- return $this->_setstat($path, $attr, false);
1677
- }
1678
-
1679
- // normally $entries would have at least . and .. but it might not if the directories
1680
- // permissions didn't allow reading
1681
- if (empty($entries)) {
1682
- return false;
1683
- }
1684
-
1685
- unset($entries['.'], $entries['..']);
1686
- foreach ($entries as $filename => $props) {
1687
- if (!isset($props['type'])) {
1688
- return false;
1689
- }
1690
-
1691
- $temp = $path . '/' . $filename;
1692
- if ($props['type'] == NET_SFTP_TYPE_DIRECTORY) {
1693
- if (!$this->_setstat_recursive($temp, $attr, $i)) {
1694
- return false;
1695
- }
1696
- } else {
1697
- if (!$this->_send_sftp_packet(NET_SFTP_SETSTAT, pack('Na*a*', strlen($temp), $temp, $attr))) {
1698
- return false;
1699
- }
1700
-
1701
- $i++;
1702
-
1703
- if ($i >= NET_SFTP_QUEUE_SIZE) {
1704
- if (!$this->_read_put_responses($i)) {
1705
- return false;
1706
- }
1707
- $i = 0;
1708
- }
1709
- }
1710
- }
1711
-
1712
- if (!$this->_send_sftp_packet(NET_SFTP_SETSTAT, pack('Na*a*', strlen($path), $path, $attr))) {
1713
- return false;
1714
- }
1715
-
1716
- $i++;
1717
-
1718
- if ($i >= NET_SFTP_QUEUE_SIZE) {
1719
- if (!$this->_read_put_responses($i)) {
1720
- return false;
1721
- }
1722
- $i = 0;
1723
- }
1724
-
1725
- return true;
1726
- }
1727
-
1728
- /**
1729
- * Return the target of a symbolic link
1730
- *
1731
- * @param string $link
1732
- * @return mixed
1733
- * @access public
1734
- */
1735
- function readlink($link)
1736
- {
1737
- if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
1738
- return false;
1739
- }
1740
-
1741
- $link = $this->_realpath($link);
1742
-
1743
- if (!$this->_send_sftp_packet(NET_SFTP_READLINK, pack('Na*', strlen($link), $link))) {
1744
- return false;
1745
- }
1746
-
1747
- $response = $this->_get_sftp_packet();
1748
- switch ($this->packet_type) {
1749
- case NET_SFTP_NAME:
1750
- break;
1751
- case NET_SFTP_STATUS:
1752
- $this->_logError($response);
1753
- return false;
1754
- default:
1755
- user_error('Expected SSH_FXP_NAME or SSH_FXP_STATUS');
1756
- return false;
1757
- }
1758
-
1759
- if (strlen($response) < 4) {
1760
- return false;
1761
- }
1762
- extract(unpack('Ncount', $this->_string_shift($response, 4)));
1763
- // the file isn't a symlink
1764
- if (!$count) {
1765
- return false;
1766
- }
1767
-
1768
- if (strlen($response) < 4) {
1769
- return false;
1770
- }
1771
- extract(unpack('Nlength', $this->_string_shift($response, 4)));
1772
- return $this->_string_shift($response, $length);
1773
- }
1774
-
1775
- /**
1776
- * Create a symlink
1777
- *
1778
- * symlink() creates a symbolic link to the existing target with the specified name link.
1779
- *
1780
- * @param string $target
1781
- * @param string $link
1782
- * @return bool
1783
- * @access public
1784
- */
1785
- function symlink($target, $link)
1786
- {
1787
- if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
1788
- return false;
1789
- }
1790
-
1791
- //$target = $this->_realpath($target);
1792
- $link = $this->_realpath($link);
1793
-
1794
- $packet = pack('Na*Na*', strlen($target), $target, strlen($link), $link);
1795
- if (!$this->_send_sftp_packet(NET_SFTP_SYMLINK, $packet)) {
1796
- return false;
1797
- }
1798
-
1799
- $response = $this->_get_sftp_packet();
1800
- if ($this->packet_type != NET_SFTP_STATUS) {
1801
- user_error('Expected SSH_FXP_STATUS');
1802
- return false;
1803
- }
1804
-
1805
- if (strlen($response) < 4) {
1806
- return false;
1807
- }
1808
- extract(unpack('Nstatus', $this->_string_shift($response, 4)));
1809
- if ($status != NET_SFTP_STATUS_OK) {
1810
- $this->_logError($response, $status);
1811
- return false;
1812
- }
1813
-
1814
- return true;
1815
- }
1816
-
1817
- /**
1818
- * Creates a directory.
1819
- *
1820
- * @param string $dir
1821
- * @return bool
1822
- * @access public
1823
- */
1824
- function mkdir($dir, $mode = -1, $recursive = false)
1825
- {
1826
- if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
1827
- return false;
1828
- }
1829
-
1830
- $dir = $this->_realpath($dir);
1831
- // by not providing any permissions, hopefully the server will use the logged in users umask - their
1832
- // default permissions.
1833
- $attr = $mode == -1 ? "\0\0\0\0" : pack('N2', NET_SFTP_ATTR_PERMISSIONS, $mode & 07777);
1834
-
1835
- if ($recursive) {
1836
- $dirs = explode('/', preg_replace('#/(?=/)|/$#', '', $dir));
1837
- if (empty($dirs[0])) {
1838
- array_shift($dirs);
1839
- $dirs[0] = '/' . $dirs[0];
1840
- }
1841
- for ($i = 0; $i < count($dirs); $i++) {
1842
- $temp = array_slice($dirs, 0, $i + 1);
1843
- $temp = implode('/', $temp);
1844
- $result = $this->_mkdir_helper($temp, $attr);
1845
- }
1846
- return $result;
1847
- }
1848
-
1849
- return $this->_mkdir_helper($dir, $attr);
1850
- }
1851
-
1852
- /**
1853
- * Helper function for directory creation
1854
- *
1855
- * @param string $dir
1856
- * @return bool
1857
- * @access private
1858
- */
1859
- function _mkdir_helper($dir, $attr)
1860
- {
1861
- if (!$this->_send_sftp_packet(NET_SFTP_MKDIR, pack('Na*a*', strlen($dir), $dir, $attr))) {
1862
- return false;
1863
- }
1864
-
1865
- $response = $this->_get_sftp_packet();
1866
- if ($this->packet_type != NET_SFTP_STATUS) {
1867
- user_error('Expected SSH_FXP_STATUS');
1868
- return false;
1869
- }
1870
-
1871
- if (strlen($response) < 4) {
1872
- return false;
1873
- }
1874
- extract(unpack('Nstatus', $this->_string_shift($response, 4)));
1875
- if ($status != NET_SFTP_STATUS_OK) {
1876
- $this->_logError($response, $status);
1877
- return false;
1878
- }
1879
-
1880
- return true;
1881
- }
1882
-
1883
- /**
1884
- * Removes a directory.
1885
- *
1886
- * @param string $dir
1887
- * @return bool
1888
- * @access public
1889
- */
1890
- function rmdir($dir)
1891
- {
1892
- if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
1893
- return false;
1894
- }
1895
-
1896
- $dir = $this->_realpath($dir);
1897
- if ($dir === false) {
1898
- return false;
1899
- }
1900
-
1901
- if (!$this->_send_sftp_packet(NET_SFTP_RMDIR, pack('Na*', strlen($dir), $dir))) {
1902
- return false;
1903
- }
1904
-
1905
- $response = $this->_get_sftp_packet();
1906
- if ($this->packet_type != NET_SFTP_STATUS) {
1907
- user_error('Expected SSH_FXP_STATUS');
1908
- return false;
1909
- }
1910
-
1911
- if (strlen($response) < 4) {
1912
- return false;
1913
- }
1914
- extract(unpack('Nstatus', $this->_string_shift($response, 4)));
1915
- if ($status != NET_SFTP_STATUS_OK) {
1916
- // presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED?
1917
- $this->_logError($response, $status);
1918
- return false;
1919
- }
1920
-
1921
- $this->_remove_from_stat_cache($dir);
1922
- // the following will do a soft delete, which would be useful if you deleted a file
1923
- // and then tried to do a stat on the deleted file. the above, in contrast, does
1924
- // a hard delete
1925
- //$this->_update_stat_cache($dir, false);
1926
-
1927
- return true;
1928
- }
1929
-
1930
- /**
1931
- * Uploads a file to the SFTP server.
1932
- *
1933
- * By default, Net_SFTP::put() does not read from the local filesystem. $data is dumped directly into $remote_file.
1934
- * So, for example, if you set $data to 'filename.ext' and then do Net_SFTP::get(), you will get a file, twelve bytes
1935
- * long, containing 'filename.ext' as its contents.
1936
- *
1937
- * Setting $mode to NET_SFTP_LOCAL_FILE will change the above behavior. With NET_SFTP_LOCAL_FILE, $remote_file will
1938
- * contain as many bytes as filename.ext does on your local filesystem. If your filename.ext is 1MB then that is how
1939
- * large $remote_file will be, as well.
1940
- *
1941
- * If $data is a resource then it'll be used as a resource instead.
1942
- *
1943
- *
1944
- * Setting $mode to NET_SFTP_CALLBACK will use $data as callback function, which gets only one parameter -- number
1945
- * of bytes to return, and returns a string if there is some data or null if there is no more data
1946
- *
1947
- * Currently, only binary mode is supported. As such, if the line endings need to be adjusted, you will need to take
1948
- * care of that, yourself.
1949
- *
1950
- * $mode can take an additional two parameters - NET_SFTP_RESUME and NET_SFTP_RESUME_START. These are bitwise AND'd with
1951
- * $mode. So if you want to resume upload of a 300mb file on the local file system you'd set $mode to the following:
1952
- *
1953
- * NET_SFTP_LOCAL_FILE | NET_SFTP_RESUME
1954
- *
1955
- * If you wanted to simply append the full contents of a local file to the full contents of a remote file you'd replace
1956
- * NET_SFTP_RESUME with NET_SFTP_RESUME_START.
1957
- *
1958
- * If $mode & (NET_SFTP_RESUME | NET_SFTP_RESUME_START) then NET_SFTP_RESUME_START will be assumed.
1959
- *
1960
- * $start and $local_start give you more fine grained control over this process and take precident over NET_SFTP_RESUME
1961
- * when they're non-negative. ie. $start could let you write at the end of a file (like NET_SFTP_RESUME) or in the middle
1962
- * of one. $local_start could let you start your reading from the end of a file (like NET_SFTP_RESUME_START) or in the
1963
- * middle of one.
1964
- *
1965
- * Setting $local_start to > 0 or $mode | NET_SFTP_RESUME_START doesn't do anything unless $mode | NET_SFTP_LOCAL_FILE.
1966
- *
1967
- * @param string $remote_file
1968
- * @param string|resource $data
1969
- * @param int $mode
1970
- * @param int $start
1971
- * @param int $local_start
1972
- * @param callable|null $progressCallback
1973
- * @return bool
1974
- * @access public
1975
- * @internal ASCII mode for SFTPv4/5/6 can be supported by adding a new function - Net_SFTP::setMode().
1976
- */
1977
- function put($remote_file, $data, $mode = NET_SFTP_STRING, $start = -1, $local_start = -1, $progressCallback = null)
1978
- {
1979
- if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
1980
- return false;
1981
- }
1982
-
1983
- $remote_file = $this->_realpath($remote_file);
1984
- if ($remote_file === false) {
1985
- return false;
1986
- }
1987
-
1988
- $this->_remove_from_stat_cache($remote_file);
1989
-
1990
- $flags = NET_SFTP_OPEN_WRITE | NET_SFTP_OPEN_CREATE;
1991
- // according to the SFTP specs, NET_SFTP_OPEN_APPEND should "force all writes to append data at the end of the file."
1992
- // in practice, it doesn't seem to do that.
1993
- //$flags|= ($mode & NET_SFTP_RESUME) ? NET_SFTP_OPEN_APPEND : NET_SFTP_OPEN_TRUNCATE;
1994
-
1995
- if ($start >= 0) {
1996
- $offset = $start;
1997
- } elseif ($mode & NET_SFTP_RESUME) {
1998
- // if NET_SFTP_OPEN_APPEND worked as it should _size() wouldn't need to be called
1999
- $size = $this->size($remote_file);
2000
- $offset = $size !== false ? $size : 0;
2001
- } else {
2002
- $offset = 0;
2003
- $flags|= NET_SFTP_OPEN_TRUNCATE;
2004
- }
2005
-
2006
- $packet = pack('Na*N2', strlen($remote_file), $remote_file, $flags, 0);
2007
- if (!$this->_send_sftp_packet(NET_SFTP_OPEN, $packet)) {
2008
- return false;
2009
- }
2010
-
2011
- $response = $this->_get_sftp_packet();
2012
- switch ($this->packet_type) {
2013
- case NET_SFTP_HANDLE:
2014
- $handle = substr($response, 4);
2015
- break;
2016
- case NET_SFTP_STATUS:
2017
- $this->_logError($response);
2018
- return false;
2019
- default:
2020
- user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS');
2021
- return false;
2022
- }
2023
-
2024
- // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.2.3
2025
- $dataCallback = false;
2026
- switch (true) {
2027
- case $mode & NET_SFTP_CALLBACK:
2028
- if (!is_callable($data)) {
2029
- user_error("\$data should be is_callable if you set NET_SFTP_CALLBACK flag");
2030
- }
2031
- $dataCallback = $data;
2032
- // do nothing
2033
- break;
2034
- case is_resource($data):
2035
- $mode = $mode & ~NET_SFTP_LOCAL_FILE;
2036
- $info = stream_get_meta_data($data);
2037
- if ($info['wrapper_type'] == 'PHP' && $info['stream_type'] == 'Input') {
2038
- $fp = fopen('php://memory', 'w+');
2039
- stream_copy_to_stream($data, $fp);
2040
- rewind($fp);
2041
- } else {
2042
- $fp = $data;
2043
- }
2044
- break;
2045
- case $mode & NET_SFTP_LOCAL_FILE:
2046
- if (!is_file($data)) {
2047
- user_error("$data is not a valid file");
2048
- return false;
2049
- }
2050
- $fp = @fopen($data, 'rb');
2051
- if (!$fp) {
2052
- return false;
2053
- }
2054
- }
2055
-
2056
- if (isset($fp)) {
2057
- $stat = fstat($fp);
2058
- $size = !empty($stat) ? $stat['size'] : 0;
2059
-
2060
- if ($local_start >= 0) {
2061
- fseek($fp, $local_start);
2062
- $size-= $local_start;
2063
- }
2064
- } elseif ($dataCallback) {
2065
- $size = 0;
2066
- } else {
2067
- $size = strlen($data);
2068
- }
2069
-
2070
- $sent = 0;
2071
- $size = $size < 0 ? ($size & 0x7FFFFFFF) + 0x80000000 : $size;
2072
-
2073
- $sftp_packet_size = 4096; // PuTTY uses 4096
2074
- // make the SFTP packet be exactly 4096 bytes by including the bytes in the NET_SFTP_WRITE packets "header"
2075
- $sftp_packet_size-= strlen($handle) + 25;
2076
- $i = 0;
2077
- while ($dataCallback || ($size === 0 || $sent < $size)) {
2078
- if ($dataCallback) {
2079
- $temp = call_user_func($dataCallback, $sftp_packet_size);
2080
- if (is_null($temp)) {
2081
- break;
2082
- }
2083
- } else {
2084
- $temp = isset($fp) ? fread($fp, $sftp_packet_size) : substr($data, $sent, $sftp_packet_size);
2085
- if ($temp === false || $temp === '') {
2086
- break;
2087
- }
2088
- }
2089
-
2090
- $subtemp = $offset + $sent;
2091
- $packet = pack('Na*N3a*', strlen($handle), $handle, $subtemp / 4294967296, $subtemp, strlen($temp), $temp);
2092
- if (!$this->_send_sftp_packet(NET_SFTP_WRITE, $packet)) {
2093
- if ($mode & NET_SFTP_LOCAL_FILE) {
2094
- fclose($fp);
2095
- }
2096
- return false;
2097
- }
2098
- $sent+= strlen($temp);
2099
- if (is_callable($progressCallback)) {
2100
- call_user_func($progressCallback, $sent);
2101
- }
2102
-
2103
- $i++;
2104
-
2105
- if ($i == NET_SFTP_QUEUE_SIZE) {
2106
- if (!$this->_read_put_responses($i)) {
2107
- $i = 0;
2108
- break;
2109
- }
2110
- $i = 0;
2111
- }
2112
- }
2113
-
2114
- if (!$this->_read_put_responses($i)) {
2115
- if ($mode & NET_SFTP_LOCAL_FILE) {
2116
- fclose($fp);
2117
- }
2118
- $this->_close_handle($handle);
2119
- return false;
2120
- }
2121
-
2122
- if ($mode & NET_SFTP_LOCAL_FILE) {
2123
- fclose($fp);
2124
- }
2125
-
2126
- return $this->_close_handle($handle);
2127
- }
2128
-
2129
- /**
2130
- * Reads multiple successive SSH_FXP_WRITE responses
2131
- *
2132
- * Sending an SSH_FXP_WRITE packet and immediately reading its response isn't as efficient as blindly sending out $i
2133
- * SSH_FXP_WRITEs, in succession, and then reading $i responses.
2134
- *
2135
- * @param int $i
2136
- * @return bool
2137
- * @access private
2138
- */
2139
- function _read_put_responses($i)
2140
- {
2141
- while ($i--) {
2142
- $response = $this->_get_sftp_packet();
2143
- if ($this->packet_type != NET_SFTP_STATUS) {
2144
- user_error('Expected SSH_FXP_STATUS');
2145
- return false;
2146
- }
2147
-
2148
- if (strlen($response) < 4) {
2149
- return false;
2150
- }
2151
- extract(unpack('Nstatus', $this->_string_shift($response, 4)));
2152
- if ($status != NET_SFTP_STATUS_OK) {
2153
- $this->_logError($response, $status);
2154
- break;
2155
- }
2156
- }
2157
-
2158
- return $i < 0;
2159
- }
2160
-
2161
- /**
2162