|
Moodle
2.2.1
http://www.collinsharper.com
|
00001 <?php 00002 00003 // This file is part of Moodle - http://moodle.org/ 00004 // 00005 // Moodle is free software: you can redistribute it and/or modify 00006 // it under the terms of the GNU General Public License as published by 00007 // the Free Software Foundation, either version 3 of the License, or 00008 // (at your option) any later version. 00009 // 00010 // Moodle is distributed in the hope that it will be useful, 00011 // but WITHOUT ANY WARRANTY; without even the implied warranty of 00012 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00013 // GNU General Public License for more details. 00014 // 00015 // You should have received a copy of the GNU General Public License 00016 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 00017 00036 class webdav_client { 00037 00042 private $_debug = false; 00043 private $sock; 00044 private $_server; 00045 private $_protocol = 'HTTP/1.1'; 00046 private $_port = 80; 00047 private $_path ='/'; 00048 private $_auth = false; 00049 private $_user; 00050 private $_pass; 00051 00052 private $_socket_timeout = 5; 00053 private $_errno; 00054 private $_errstr; 00055 private $_user_agent = 'Moodle WebDav Client'; 00056 private $_crlf = "\r\n"; 00057 private $_req; 00058 private $_resp_status; 00059 private $_parser; 00060 private $_xmltree; 00061 private $_tree; 00062 private $_ls = array(); 00063 private $_ls_ref; 00064 private $_ls_ref_cdata; 00065 private $_delete = array(); 00066 private $_delete_ref; 00067 private $_delete_ref_cdata; 00068 private $_lock = array(); 00069 private $_lock_ref; 00070 private $_lock_rec_cdata; 00071 private $_null = NULL; 00072 private $_header=''; 00073 private $_body=''; 00074 private $_connection_closed = false; 00075 private $_maxheaderlenth = 1000; 00076 00082 function __construct($server = '', $user = '', $pass = '', $auth = false) { 00083 if (!empty($server)) { 00084 $this->_server = $server; 00085 } 00086 if (!empty($user) && !empty($pass)) { 00087 $this->user = $user; 00088 $this->pass = $pass; 00089 } 00090 $this->_auth = $auth; 00091 } 00092 public function __set($key, $value) { 00093 $property = '_' . $key; 00094 $this->$property = $value; 00095 } 00096 00103 function set_protocol($version) { 00104 if ($version == 1) { 00105 $this->_protocol = 'HTTP/1.1'; 00106 } else { 00107 $this->_protocol = 'HTTP/1.0'; 00108 } 00109 } 00110 00117 function iso8601totime($iso8601) { 00118 /* 00119 00120 date-time = full-date "T" full-time 00121 00122 full-date = date-fullyear "-" date-month "-" date-mday 00123 full-time = partial-time time-offset 00124 00125 date-fullyear = 4DIGIT 00126 date-month = 2DIGIT ; 01-12 00127 date-mday = 2DIGIT ; 01-28, 01-29, 01-30, 01-31 based on 00128 month/year 00129 time-hour = 2DIGIT ; 00-23 00130 time-minute = 2DIGIT ; 00-59 00131 time-second = 2DIGIT ; 00-59, 00-60 based on leap second rules 00132 time-secfrac = "." 1*DIGIT 00133 time-numoffset = ("+" / "-") time-hour ":" time-minute 00134 time-offset = "Z" / time-numoffset 00135 00136 partial-time = time-hour ":" time-minute ":" time-second 00137 [time-secfrac] 00138 */ 00139 00140 $regs = array(); 00141 /* [1] [2] [3] [4] [5] [6] */ 00142 if (preg_match('/^([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})Z$/', $iso8601, $regs)) { 00143 return mktime($regs[4],$regs[5], $regs[6], $regs[2], $regs[3], $regs[1]); 00144 } 00145 // to be done: regex for partial-time...apache webdav mod never returns partial-time 00146 00147 return false; 00148 } 00149 00154 function open() { 00155 // let's try to open a socket 00156 $this->_error_log('open a socket connection'); 00157 $this->sock = fsockopen($this->_server, $this->_port, $this->_errno, $this->_errstr, $this->_socket_timeout); 00158 set_time_limit(30); 00159 if (is_resource($this->sock)) { 00160 socket_set_blocking($this->sock, true); 00161 $this->_connection_closed = false; 00162 $this->_error_log('socket is open: ' . $this->sock); 00163 return true; 00164 } else { 00165 $this->_error_log("$this->_errstr ($this->_errno)\n"); 00166 return false; 00167 } 00168 } 00169 00173 function close() { 00174 $this->_error_log('closing socket ' . $this->sock); 00175 $this->_connection_closed = true; 00176 fclose($this->sock); 00177 } 00178 00185 function check_webdav() { 00186 $resp = $this->options(); 00187 if (!$resp) { 00188 return false; 00189 } 00190 $this->_error_log($resp['header']['DAV']); 00191 // check schema 00192 if (preg_match('/1,2/', $resp['header']['DAV'])) { 00193 return true; 00194 } 00195 // otherwise return false 00196 return false; 00197 } 00198 00199 00204 function options() { 00205 $this->header_unset(); 00206 $this->create_basic_request('OPTIONS'); 00207 $this->send_request(); 00208 $this->get_respond(); 00209 $response = $this->process_respond(); 00210 // validate the response ... 00211 // check http-version 00212 if ($response['status']['http-version'] == 'HTTP/1.1' || 00213 $response['status']['http-version'] == 'HTTP/1.0') { 00214 return $response; 00215 } 00216 $this->_error_log('Response was not even http'); 00217 return false; 00218 00219 } 00220 00228 function mkcol($path) { 00229 $this->_path = $this->translate_uri($path); 00230 $this->header_unset(); 00231 $this->create_basic_request('MKCOL'); 00232 $this->send_request(); 00233 $this->get_respond(); 00234 $response = $this->process_respond(); 00235 // validate the response ... 00236 // check http-version 00237 $http_version = $response['status']['http-version']; 00238 if ($http_version == 'HTTP/1.1' || $http_version == 'HTTP/1.0') { 00253 return $response['status']['status-code']; 00254 } 00255 00256 } 00257 00265 function get($path, &$buffer) { 00266 $this->_path = $this->translate_uri($path); 00267 $this->header_unset(); 00268 $this->create_basic_request('GET'); 00269 $this->send_request(); 00270 $this->get_respond(); 00271 $response = $this->process_respond(); 00272 00273 $http_version = $response['status']['http-version']; 00274 // validate the response 00275 // check http-version 00276 if ($http_version == 'HTTP/1.1' || $http_version == 'HTTP/1.0') { 00277 // seems to be http ... proceed 00278 // We expect a 200 code 00279 if ($response['status']['status-code'] == 200 ) { 00280 $this->_error_log('returning buffer with ' . strlen($response['body']) . ' bytes.'); 00281 $buffer = $response['body']; 00282 } 00283 return $response['status']['status-code']; 00284 } 00285 // ups: no http status was returned ? 00286 return false; 00287 } 00288 00297 function put($path, $data ) { 00298 $this->_path = $this->translate_uri($path); 00299 $this->header_unset(); 00300 $this->create_basic_request('PUT'); 00301 // add more needed header information ... 00302 $this->header_add('Content-length: ' . strlen($data)); 00303 $this->header_add('Content-type: application/octet-stream'); 00304 // send header 00305 $this->send_request(); 00306 // send the rest (data) 00307 fputs($this->sock, $data); 00308 $this->get_respond(); 00309 $response = $this->process_respond(); 00310 00311 // validate the response 00312 // check http-version 00313 if ($response['status']['http-version'] == 'HTTP/1.1' || 00314 $response['status']['http-version'] == 'HTTP/1.0') { 00315 // seems to be http ... proceed 00316 // We expect a 200 or 204 status code 00317 // see rfc 2068 - 9.6 PUT... 00318 // print 'http ok<br>'; 00319 return $response['status']['status-code']; 00320 } 00321 // ups: no http status was returned ? 00322 return false; 00323 } 00324 00336 function put_file($path, $filename) { 00337 // try to open the file ... 00338 00339 00340 $handle = @fopen ($filename, 'r'); 00341 00342 if ($handle) { 00343 // $this->sock = pfsockopen ($this->_server, $this->_port, $this->_errno, $this->_errstr, $this->_socket_timeout); 00344 $this->_path = $this->translate_uri($path); 00345 $this->header_unset(); 00346 $this->create_basic_request('PUT'); 00347 // add more needed header information ... 00348 $this->header_add('Content-length: ' . filesize($filename)); 00349 $this->header_add('Content-type: application/octet-stream'); 00350 // send header 00351 $this->send_request(); 00352 while (!feof($handle)) { 00353 fputs($this->sock,fgets($handle,4096)); 00354 } 00355 fclose($handle); 00356 $this->get_respond(); 00357 $response = $this->process_respond(); 00358 00359 // validate the response 00360 // check http-version 00361 if ($response['status']['http-version'] == 'HTTP/1.1' || 00362 $response['status']['http-version'] == 'HTTP/1.0') { 00363 // seems to be http ... proceed 00364 // We expect a 200 or 204 status code 00365 // see rfc 2068 - 9.6 PUT... 00366 // print 'http ok<br>'; 00367 return $response['status']['status-code']; 00368 } 00369 // ups: no http status was returned ? 00370 return false; 00371 } else { 00372 $this->_error_log('put_file: could not open ' . $filename); 00373 return false; 00374 } 00375 00376 } 00377 00387 function get_file($srcpath, $localpath) { 00388 00389 if ($this->get($srcpath, $buffer)) { 00390 // convert utf-8 filename to iso-8859-1 00391 00392 $localpath = $this->utf_decode_path($localpath); 00393 00394 $handle = fopen ($localpath, 'w'); 00395 if ($handle) { 00396 fwrite($handle, $buffer); 00397 fclose($handle); 00398 return true; 00399 } else { 00400 return false; 00401 } 00402 } else { 00403 return false; 00404 } 00405 } 00406 00419 function copy_file($src_path, $dst_path, $overwrite) { 00420 $this->_path = $this->translate_uri($src_path); 00421 $this->header_unset(); 00422 $this->create_basic_request('COPY'); 00423 $this->header_add(sprintf('Destination: http://%s%s', $this->_server, $this->translate_uri($dst_path))); 00424 if ($overwrite) { 00425 $this->header_add('Overwrite: T'); 00426 } else { 00427 $this->header_add('Overwrite: F'); 00428 } 00429 $this->header_add(''); 00430 $this->send_request(); 00431 $this->get_respond(); 00432 $response = $this->process_respond(); 00433 // validate the response ... 00434 // check http-version 00435 if ($response['status']['http-version'] == 'HTTP/1.1' || 00436 $response['status']['http-version'] == 'HTTP/1.0') { 00437 /* seems to be http ... proceed 00438 just return what server gave us (as defined in rfc 2518) : 00439 201 (Created) - The source resource was successfully copied. The copy operation resulted in the creation of a new resource. 00440 204 (No Content) - The source resource was successfully copied to a pre-existing destination resource. 00441 403 (Forbidden) - The source and destination URIs are the same. 00442 409 (Conflict) - A resource cannot be created at the destination until one or more intermediate collections have been created. 00443 412 (Precondition Failed) - The server was unable to maintain the liveness of the properties listed in the propertybehavior XML element 00444 or the Overwrite header is "F" and the state of the destination resource is non-null. 00445 423 (Locked) - The destination resource was locked. 00446 502 (Bad Gateway) - This may occur when the destination is on another server and the destination server refuses to accept the resource. 00447 507 (Insufficient Storage) - The destination resource does not have sufficient space to record the state of the resource after the 00448 execution of this method. 00449 */ 00450 return $response['status']['status-code']; 00451 } 00452 return false; 00453 } 00454 00467 function copy_coll($src_path, $dst_path, $overwrite) { 00468 $this->_path = $this->translate_uri($src_path); 00469 $this->header_unset(); 00470 $this->create_basic_request('COPY'); 00471 $this->header_add(sprintf('Destination: http://%s%s', $this->_server, $this->translate_uri($dst_path))); 00472 $this->header_add('Depth: Infinity'); 00473 00474 $xml = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\r\n"; 00475 $xml .= "<d:propertybehavior xmlns:d=\"DAV:\">\r\n"; 00476 $xml .= " <d:keepalive>*</d:keepalive>\r\n"; 00477 $xml .= "</d:propertybehavior>\r\n"; 00478 00479 $this->header_add('Content-length: ' . strlen($xml)); 00480 $this->header_add('Content-type: application/xml'); 00481 $this->send_request(); 00482 // send also xml 00483 fputs($this->sock, $xml); 00484 $this->get_respond(); 00485 $response = $this->process_respond(); 00486 // validate the response ... 00487 // check http-version 00488 if ($response['status']['http-version'] == 'HTTP/1.1' || 00489 $response['status']['http-version'] == 'HTTP/1.0') { 00490 /* seems to be http ... proceed 00491 just return what server gave us (as defined in rfc 2518) : 00492 201 (Created) - The source resource was successfully copied. The copy operation resulted in the creation of a new resource. 00493 204 (No Content) - The source resource was successfully copied to a pre-existing destination resource. 00494 403 (Forbidden) - The source and destination URIs are the same. 00495 409 (Conflict) - A resource cannot be created at the destination until one or more intermediate collections have been created. 00496 412 (Precondition Failed) - The server was unable to maintain the liveness of the properties listed in the propertybehavior XML element 00497 or the Overwrite header is "F" and the state of the destination resource is non-null. 00498 423 (Locked) - The destination resource was locked. 00499 502 (Bad Gateway) - This may occur when the destination is on another server and the destination server refuses to accept the resource. 00500 507 (Insufficient Storage) - The destination resource does not have sufficient space to record the state of the resource after the 00501 execution of this method. 00502 */ 00503 return $response['status']['status-code']; 00504 } 00505 return false; 00506 } 00507 00518 // -------------------------------------------------------------------------- 00519 // public method move 00520 // move/rename a file/collection on webdav server 00521 function move($src_path,$dst_path, $overwrite) { 00522 00523 $this->_path = $this->translate_uri($src_path); 00524 $this->header_unset(); 00525 00526 $this->create_basic_request('MOVE'); 00527 $this->header_add(sprintf('Destination: http://%s%s', $this->_server, $this->translate_uri($dst_path))); 00528 if ($overwrite) { 00529 $this->header_add('Overwrite: T'); 00530 } else { 00531 $this->header_add('Overwrite: F'); 00532 } 00533 $this->header_add(''); 00534 00535 $this->send_request(); 00536 $this->get_respond(); 00537 $response = $this->process_respond(); 00538 // validate the response ... 00539 // check http-version 00540 if ($response['status']['http-version'] == 'HTTP/1.1' || 00541 $response['status']['http-version'] == 'HTTP/1.0') { 00542 /* seems to be http ... proceed 00543 just return what server gave us (as defined in rfc 2518) : 00544 201 (Created) - The source resource was successfully moved, and a new resource was created at the destination. 00545 204 (No Content) - The source resource was successfully moved to a pre-existing destination resource. 00546 403 (Forbidden) - The source and destination URIs are the same. 00547 409 (Conflict) - A resource cannot be created at the destination until one or more intermediate collections have been created. 00548 412 (Precondition Failed) - The server was unable to maintain the liveness of the properties listed in the propertybehavior XML element 00549 or the Overwrite header is "F" and the state of the destination resource is non-null. 00550 423 (Locked) - The source or the destination resource was locked. 00551 502 (Bad Gateway) - This may occur when the destination is on another server and the destination server refuses to accept the resource. 00552 00553 201 (Created) - The collection or structured resource was created in its entirety. 00554 403 (Forbidden) - This indicates at least one of two conditions: 1) the server does not allow the creation of collections at the given 00555 location in its namespace, or 2) the parent collection of the Request-URI exists but cannot accept members. 00556 405 (Method Not Allowed) - MKCOL can only be executed on a deleted/non-existent resource. 00557 409 (Conflict) - A collection cannot be made at the Request-URI until one or more intermediate collections have been created. 00558 415 (Unsupported Media Type)- The server does not support the request type of the body. 00559 507 (Insufficient Storage) - The resource does not have sufficient space to record the state of the resource after the execution of this method. 00560 */ 00561 return $response['status']['status-code']; 00562 } 00563 return false; 00564 } 00565 00576 function lock($path) { 00577 $this->_path = $this->translate_uri($path); 00578 $this->header_unset(); 00579 $this->create_basic_request('LOCK'); 00580 $this->header_add('Timeout: Infinite'); 00581 $this->header_add('Content-type: text/xml'); 00582 // create the xml request ... 00583 $xml = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\r\n"; 00584 $xml .= "<D:lockinfo xmlns:D='DAV:'\r\n>"; 00585 $xml .= " <D:lockscope><D:exclusive/></D:lockscope>\r\n"; 00586 $xml .= " <D:locktype><D:write/></D:locktype>\r\n"; 00587 $xml .= " <D:owner>\r\n"; 00588 $xml .= " <D:href>".($this->_user)."</D:href>\r\n"; 00589 $xml .= " </D:owner>\r\n"; 00590 $xml .= "</D:lockinfo>\r\n"; 00591 $this->header_add('Content-length: ' . strlen($xml)); 00592 $this->send_request(); 00593 // send also xml 00594 fputs($this->sock, $xml); 00595 $this->get_respond(); 00596 $response = $this->process_respond(); 00597 // validate the response ... (only basic validation) 00598 // check http-version 00599 if ($response['status']['http-version'] == 'HTTP/1.1' || 00600 $response['status']['http-version'] == 'HTTP/1.0') { 00601 /* seems to be http ... proceed 00602 rfc 2518 says: 00603 200 (OK) - The lock request succeeded and the value of the lockdiscovery property is included in the body. 00604 412 (Precondition Failed) - The included lock token was not enforceable on this resource or the server could not satisfy the 00605 request in the lockinfo XML element. 00606 423 (Locked) - The resource is locked, so the method has been rejected. 00607 */ 00608 00609 switch($response['status']['status-code']) { 00610 case 200: 00611 // collection was successfully locked... see xml response to get lock token... 00612 if (strcmp($response['header']['Content-Type'], 'text/xml; charset="utf-8"') == 0) { 00613 // ok let's get the content of the xml stuff 00614 $this->_parser = xml_parser_create_ns(); 00615 // forget old data... 00616 unset($this->_lock[$this->_parser]); 00617 unset($this->_xmltree[$this->_parser]); 00618 xml_parser_set_option($this->_parser,XML_OPTION_SKIP_WHITE,0); 00619 xml_parser_set_option($this->_parser,XML_OPTION_CASE_FOLDING,0); 00620 xml_set_object($this->_parser, $this); 00621 xml_set_element_handler($this->_parser, "_lock_startElement", "_endElement"); 00622 xml_set_character_data_handler($this->_parser, "_lock_cdata"); 00623 00624 if (!xml_parse($this->_parser, $response['body'])) { 00625 die(sprintf("XML error: %s at line %d", 00626 xml_error_string(xml_get_error_code($this->_parser)), 00627 xml_get_current_line_number($this->_parser))); 00628 } 00629 00630 // Free resources 00631 xml_parser_free($this->_parser); 00632 // add status code to array 00633 $this->_lock[$this->_parser]['status'] = 200; 00634 return $this->_lock[$this->_parser]; 00635 00636 } else { 00637 print 'Missing Content-Type: text/xml header in response.<br>'; 00638 } 00639 return false; 00640 00641 default: 00642 // hmm. not what we expected. Just return what we got from webdav server 00643 // someone else has to handle it. 00644 $this->_lock['status'] = $response['status']['status-code']; 00645 return $this->_lock; 00646 } 00647 } 00648 00649 00650 } 00651 00652 00661 function unlock($path, $locktoken) { 00662 $this->_path = $this->translate_uri($path); 00663 $this->header_unset(); 00664 $this->create_basic_request('UNLOCK'); 00665 $this->header_add(sprintf('Lock-Token: <%s>', $locktoken)); 00666 $this->send_request(); 00667 $this->get_respond(); 00668 $response = $this->process_respond(); 00669 if ($response['status']['http-version'] == 'HTTP/1.1' || 00670 $response['status']['http-version'] == 'HTTP/1.0') { 00671 /* seems to be http ... proceed 00672 rfc 2518 says: 00673 204 (OK) - The 204 (No Content) status code is used instead of 200 (OK) because there is no response entity body. 00674 */ 00675 return $response['status']['status-code']; 00676 } 00677 return false; 00678 } 00679 00687 function delete($path) { 00688 $this->_path = $this->translate_uri($path); 00689 $this->header_unset(); 00690 $this->create_basic_request('DELETE'); 00691 /* $this->header_add('Content-Length: 0'); */ 00692 $this->header_add(''); 00693 $this->send_request(); 00694 $this->get_respond(); 00695 $response = $this->process_respond(); 00696 00697 // validate the response ... 00698 // check http-version 00699 if ($response['status']['http-version'] == 'HTTP/1.1' || 00700 $response['status']['http-version'] == 'HTTP/1.0') { 00701 // seems to be http ... proceed 00702 // We expect a 207 Multi-Status status code 00703 // print 'http ok<br>'; 00704 00705 switch ($response['status']['status-code']) { 00706 case 207: 00707 // collection was NOT deleted... see xml response for reason... 00708 // next there should be a Content-Type: text/xml; charset="utf-8" header line 00709 if (strcmp($response['header']['Content-Type'], 'text/xml; charset="utf-8"') == 0) { 00710 // ok let's get the content of the xml stuff 00711 $this->_parser = xml_parser_create_ns(); 00712 // forget old data... 00713 unset($this->_delete[$this->_parser]); 00714 unset($this->_xmltree[$this->_parser]); 00715 xml_parser_set_option($this->_parser,XML_OPTION_SKIP_WHITE,0); 00716 xml_parser_set_option($this->_parser,XML_OPTION_CASE_FOLDING,0); 00717 xml_set_object($this->_parser, $this); 00718 xml_set_element_handler($this->_parser, "_delete_startElement", "_endElement"); 00719 xml_set_character_data_handler($this->_parser, "_delete_cdata"); 00720 00721 if (!xml_parse($this->_parser, $response['body'])) { 00722 die(sprintf("XML error: %s at line %d", 00723 xml_error_string(xml_get_error_code($this->_parser)), 00724 xml_get_current_line_number($this->_parser))); 00725 } 00726 00727 print "<br>"; 00728 00729 // Free resources 00730 xml_parser_free($this->_parser); 00731 $this->_delete[$this->_parser]['status'] = $response['status']['status-code']; 00732 return $this->_delete[$this->_parser]; 00733 00734 } else { 00735 print 'Missing Content-Type: text/xml header in response.<br>'; 00736 } 00737 return false; 00738 00739 default: 00740 // collection or file was successfully deleted 00741 $this->_delete['status'] = $response['status']['status-code']; 00742 return $this->_delete; 00743 00744 00745 } 00746 } 00747 00748 } 00749 00760 function ls($path) { 00761 00762 if (trim($path) == '') { 00763 $this->_error_log('Missing a path in method ls'); 00764 return false; 00765 } 00766 $this->_path = $this->translate_uri($path); 00767 00768 $this->header_unset(); 00769 $this->create_basic_request('PROPFIND'); 00770 $this->header_add('Depth: 1'); 00771 $this->header_add('Content-type: application/xml'); 00772 // create profind xml request... 00773 $xml = <<<EOD 00774 <?xml version="1.0" encoding="utf-8"?> 00775 <propfind xmlns="DAV:"><prop> 00776 <getcontentlength xmlns="DAV:"/> 00777 <getlastmodified xmlns="DAV:"/> 00778 <executable xmlns="http://apache.org/dav/props/"/> 00779 <resourcetype xmlns="DAV:"/> 00780 <checked-in xmlns="DAV:"/> 00781 <checked-out xmlns="DAV:"/> 00782 </prop></propfind> 00783 EOD; 00784 $this->header_add('Content-length: ' . strlen($xml)); 00785 $this->send_request(); 00786 $this->_error_log($xml); 00787 fputs($this->sock, $xml); 00788 $this->get_respond(); 00789 $response = $this->process_respond(); 00790 // validate the response ... (only basic validation) 00791 // check http-version 00792 if ($response['status']['http-version'] == 'HTTP/1.1' || 00793 $response['status']['http-version'] == 'HTTP/1.0') { 00794 // seems to be http ... proceed 00795 // We expect a 207 Multi-Status status code 00796 // print 'http ok<br>'; 00797 if (strcmp($response['status']['status-code'],'207') == 0 ) { 00798 // ok so far 00799 // next there should be a Content-Type: text/xml; charset="utf-8" header line 00800 if (preg_match('#(application|text)/xml;\s?charset=[\'\"]?utf-8[\'\"]?#i', $response['header']['Content-Type'])) { 00801 // ok let's get the content of the xml stuff 00802 $this->_parser = xml_parser_create_ns('UTF-8'); 00803 // forget old data... 00804 unset($this->_ls[$this->_parser]); 00805 unset($this->_xmltree[$this->_parser]); 00806 xml_parser_set_option($this->_parser,XML_OPTION_SKIP_WHITE,0); 00807 xml_parser_set_option($this->_parser,XML_OPTION_CASE_FOLDING,0); 00808 // xml_parser_set_option($this->_parser,XML_OPTION_TARGET_ENCODING,'UTF-8'); 00809 xml_set_object($this->_parser, $this); 00810 xml_set_element_handler($this->_parser, "_propfind_startElement", "_endElement"); 00811 xml_set_character_data_handler($this->_parser, "_propfind_cdata"); 00812 00813 00814 if (!xml_parse($this->_parser, $response['body'])) { 00815 die(sprintf("XML error: %s at line %d", 00816 xml_error_string(xml_get_error_code($this->_parser)), 00817 xml_get_current_line_number($this->_parser))); 00818 } 00819 00820 // Free resources 00821 xml_parser_free($this->_parser); 00822 $arr = $this->_ls[$this->_parser]; 00823 return $arr; 00824 } else { 00825 $this->_error_log('Missing Content-Type: text/xml header in response!!'); 00826 return false; 00827 } 00828 } else { 00829 // return status code ... 00830 return $response['status']['status-code']; 00831 } 00832 } 00833 00834 // response was not http 00835 $this->_error_log('Ups in method ls: error in response from server'); 00836 return false; 00837 } 00838 00839 00848 function gpi($path) { 00849 00850 // split path by last "/" 00851 $path = rtrim($path, "/"); 00852 $item = basename($path); 00853 $dir = dirname($path); 00854 00855 $list = $this->ls($dir); 00856 00857 // be sure it is an array 00858 if (is_array($list)) { 00859 foreach($list as $e) { 00860 00861 $fullpath = urldecode($e['href']); 00862 $filename = basename($fullpath); 00863 00864 if ($filename == $item && $filename != "" and $fullpath != $dir."/") { 00865 return $e; 00866 } 00867 } 00868 } 00869 return false; 00870 } 00871 00880 function is_file($path) { 00881 00882 $item = $this->gpi($path); 00883 00884 if ($item === false) { 00885 return false; 00886 } else { 00887 return ($item['resourcetype'] != 'collection'); 00888 } 00889 } 00890 00898 function is_dir($path) { 00899 00900 // be sure path is utf-8 00901 $item = $this->gpi($path); 00902 00903 if ($item === false) { 00904 return false; 00905 } else { 00906 return ($item['resourcetype'] == 'collection'); 00907 } 00908 } 00909 00910 00922 function mput($filelist) { 00923 00924 $result = true; 00925 00926 while (list($localpath, $destpath) = each($filelist)) { 00927 00928 $localpath = rtrim($localpath, "/"); 00929 $destpath = rtrim($destpath, "/"); 00930 00931 // attempt to create target path 00932 if (is_dir($localpath)) { 00933 $pathparts = explode("/", $destpath."/ "); // add one level, last level will be created as dir 00934 } else { 00935 $pathparts = explode("/", $destpath); 00936 } 00937 $checkpath = ""; 00938 for ($i=1; $i<sizeof($pathparts)-1; $i++) { 00939 $checkpath .= "/" . $pathparts[$i]; 00940 if (!($this->is_dir($checkpath))) { 00941 00942 $result &= ($this->mkcol($checkpath) == 201 ); 00943 } 00944 } 00945 00946 if ($result) { 00947 // recurse directories 00948 if (is_dir($localpath)) { 00949 $dp = opendir($localpath); 00950 $fl = array(); 00951 while($filename = readdir($dp)) { 00952 if ((is_file($localpath."/".$filename) || is_dir($localpath."/".$filename)) && $filename!="." && $filename != "..") { 00953 $fl[$localpath."/".$filename] = $destpath."/".$filename; 00954 } 00955 } 00956 $result &= $this->mput($fl); 00957 } else { 00958 $result &= ($this->put_file($destpath, $localpath) == 201); 00959 } 00960 } 00961 } 00962 return $result; 00963 } 00964 00976 function mget($filelist) { 00977 00978 $result = true; 00979 00980 while (list($remotepath, $localpath) = each($filelist)) { 00981 00982 $localpath = rtrim($localpath, "/"); 00983 $remotepath = rtrim($remotepath, "/"); 00984 00985 // attempt to create local path 00986 if ($this->is_dir($remotepath)) { 00987 $pathparts = explode("/", $localpath."/ "); // add one level, last level will be created as dir 00988 } else { 00989 $pathparts = explode("/", $localpath); 00990 } 00991 $checkpath = ""; 00992 for ($i=1; $i<sizeof($pathparts)-1; $i++) { 00993 $checkpath .= "/" . $pathparts[$i]; 00994 if (!is_dir($checkpath)) { 00995 00996 $result &= mkdir($checkpath); 00997 } 00998 } 00999 01000 if ($result) { 01001 // recurse directories 01002 if ($this->is_dir($remotepath)) { 01003 $list = $this->ls($remotepath); 01004 01005 $fl = array(); 01006 foreach($list as $e) { 01007 $fullpath = urldecode($e['href']); 01008 $filename = basename($fullpath); 01009 if ($filename != '' and $fullpath != $remotepath . '/') { 01010 $fl[$remotepath."/".$filename] = $localpath."/".$filename; 01011 } 01012 } 01013 $result &= $this->mget($fl); 01014 } else { 01015 $result &= ($this->get_file($remotepath, $localpath)); 01016 } 01017 } 01018 } 01019 return $result; 01020 } 01021 01022 // -------------------------------------------------------------------------- 01023 // private xml callback and helper functions starting here 01024 // -------------------------------------------------------------------------- 01025 01026 01036 private function _endElement($parser, $name) { 01037 // end tag was found... 01038 $this->_xmltree[$parser] = substr($this->_xmltree[$parser],0, strlen($this->_xmltree[$parser]) - (strlen($name) + 1)); 01039 } 01040 01051 private function _propfind_startElement($parser, $name, $attrs) { 01052 // lower XML Names... maybe break a RFC, don't know ... 01053 01054 $propname = strtolower($name); 01055 if (!empty($this->_xmltree[$parser])) { 01056 $this->_xmltree[$parser] .= $propname . '_'; 01057 } else { 01058 $this->_xmltree[$parser] = $propname . '_'; 01059 } 01060 01061 // translate xml tree to a flat array ... 01062 switch($this->_xmltree[$parser]) { 01063 case 'dav::multistatus_dav::response_': 01064 // new element in mu 01065 $this->_ls_ref =& $this->_ls[$parser][]; 01066 break; 01067 case 'dav::multistatus_dav::response_dav::href_': 01068 $this->_ls_ref_cdata = &$this->_ls_ref['href']; 01069 break; 01070 case 'dav::multistatus_dav::response_dav::propstat_dav::prop_dav::creationdate_': 01071 $this->_ls_ref_cdata = &$this->_ls_ref['creationdate']; 01072 break; 01073 case 'dav::multistatus_dav::response_dav::propstat_dav::prop_dav::getlastmodified_': 01074 $this->_ls_ref_cdata = &$this->_ls_ref['lastmodified']; 01075 break; 01076 case 'dav::multistatus_dav::response_dav::propstat_dav::prop_dav::getcontenttype_': 01077 $this->_ls_ref_cdata = &$this->_ls_ref['getcontenttype']; 01078 break; 01079 case 'dav::multistatus_dav::response_dav::propstat_dav::prop_dav::getcontentlength_': 01080 $this->_ls_ref_cdata = &$this->_ls_ref['getcontentlength']; 01081 break; 01082 case 'dav::multistatus_dav::response_dav::propstat_dav::prop_dav::lockdiscovery_dav::activelock_dav::depth_': 01083 $this->_ls_ref_cdata = &$this->_ls_ref['activelock_depth']; 01084 break; 01085 case 'dav::multistatus_dav::response_dav::propstat_dav::prop_dav::lockdiscovery_dav::activelock_dav::owner_dav::href_': 01086 $this->_ls_ref_cdata = &$this->_ls_ref['activelock_owner']; 01087 break; 01088 case 'dav::multistatus_dav::response_dav::propstat_dav::prop_dav::lockdiscovery_dav::activelock_dav::owner_': 01089 $this->_ls_ref_cdata = &$this->_ls_ref['activelock_owner']; 01090 break; 01091 case 'dav::multistatus_dav::response_dav::propstat_dav::prop_dav::lockdiscovery_dav::activelock_dav::timeout_': 01092 $this->_ls_ref_cdata = &$this->_ls_ref['activelock_timeout']; 01093 break; 01094 case 'dav::multistatus_dav::response_dav::propstat_dav::prop_dav::lockdiscovery_dav::activelock_dav::locktoken_dav::href_': 01095 $this->_ls_ref_cdata = &$this->_ls_ref['activelock_token']; 01096 break; 01097 case 'dav::multistatus_dav::response_dav::propstat_dav::prop_dav::lockdiscovery_dav::activelock_dav::locktype_dav::write_': 01098 $this->_ls_ref_cdata = &$this->_ls_ref['activelock_type']; 01099 $this->_ls_ref_cdata = 'write'; 01100 $this->_ls_ref_cdata = &$this->_null; 01101 break; 01102 case 'dav::multistatus_dav::response_dav::propstat_dav::prop_dav::resourcetype_dav::collection_': 01103 $this->_ls_ref_cdata = &$this->_ls_ref['resourcetype']; 01104 $this->_ls_ref_cdata = 'collection'; 01105 $this->_ls_ref_cdata = &$this->_null; 01106 break; 01107 case 'dav::multistatus_dav::response_dav::propstat_dav::status_': 01108 $this->_ls_ref_cdata = &$this->_ls_ref['status']; 01109 break; 01110 01111 default: 01112 // handle unknown xml elements... 01113 $this->_ls_ref_cdata = &$this->_ls_ref[$this->_xmltree[$parser]]; 01114 } 01115 } 01116 01127 private function _propfind_cData($parser, $cdata) { 01128 if (trim($cdata) <> '') { 01129 // cdata must be appended, because sometimes the php xml parser makes multiple calls 01130 // to _propfind_cData before the xml end tag was reached... 01131 $this->_ls_ref_cdata .= $cdata; 01132 } else { 01133 // do nothing 01134 } 01135 } 01136 01146 private function _delete_startElement($parser, $name, $attrs) { 01147 // lower XML Names... maybe break a RFC, don't know ... 01148 $propname = strtolower($name); 01149 $this->_xmltree[$parser] .= $propname . '_'; 01150 01151 // translate xml tree to a flat array ... 01152 switch($this->_xmltree[$parser]) { 01153 case 'dav::multistatus_dav::response_': 01154 // new element in mu 01155 $this->_delete_ref =& $this->_delete[$parser][]; 01156 break; 01157 case 'dav::multistatus_dav::response_dav::href_': 01158 $this->_delete_ref_cdata = &$this->_ls_ref['href']; 01159 break; 01160 01161 default: 01162 // handle unknown xml elements... 01163 $this->_delete_cdata = &$this->_delete_ref[$this->_xmltree[$parser]]; 01164 } 01165 } 01166 01167 01178 private function _delete_cData($parser, $cdata) { 01179 if (trim($cdata) <> '') { 01180 $this->_delete_ref_cdata .= $cdata; 01181 } else { 01182 // do nothing 01183 } 01184 } 01185 01186 01197 private function _lock_startElement($parser, $name, $attrs) { 01198 // lower XML Names... maybe break a RFC, don't know ... 01199 $propname = strtolower($name); 01200 $this->_xmltree[$parser] .= $propname . '_'; 01201 01202 // translate xml tree to a flat array ... 01203 /* 01204 dav::prop_dav::lockdiscovery_dav::activelock_dav::depth_= 01205 dav::prop_dav::lockdiscovery_dav::activelock_dav::owner_dav::href_= 01206 dav::prop_dav::lockdiscovery_dav::activelock_dav::timeout_= 01207 dav::prop_dav::lockdiscovery_dav::activelock_dav::locktoken_dav::href_= 01208 */ 01209 switch($this->_xmltree[$parser]) { 01210 case 'dav::prop_dav::lockdiscovery_dav::activelock_': 01211 // new element 01212 $this->_lock_ref =& $this->_lock[$parser][]; 01213 break; 01214 case 'dav::prop_dav::lockdiscovery_dav::activelock_dav::locktype_dav::write_': 01215 $this->_lock_ref_cdata = &$this->_lock_ref['locktype']; 01216 $this->_lock_cdata = 'write'; 01217 $this->_lock_cdata = &$this->_null; 01218 break; 01219 case 'dav::prop_dav::lockdiscovery_dav::activelock_dav::lockscope_dav::exclusive_': 01220 $this->_lock_ref_cdata = &$this->_lock_ref['lockscope']; 01221 $this->_lock_ref_cdata = 'exclusive'; 01222 $this->_lock_ref_cdata = &$this->_null; 01223 break; 01224 case 'dav::prop_dav::lockdiscovery_dav::activelock_dav::depth_': 01225 $this->_lock_ref_cdata = &$this->_lock_ref['depth']; 01226 break; 01227 case 'dav::prop_dav::lockdiscovery_dav::activelock_dav::owner_dav::href_': 01228 $this->_lock_ref_cdata = &$this->_lock_ref['owner']; 01229 break; 01230 case 'dav::prop_dav::lockdiscovery_dav::activelock_dav::timeout_': 01231 $this->_lock_ref_cdata = &$this->_lock_ref['timeout']; 01232 break; 01233 case 'dav::prop_dav::lockdiscovery_dav::activelock_dav::locktoken_dav::href_': 01234 $this->_lock_ref_cdata = &$this->_lock_ref['locktoken']; 01235 break; 01236 default: 01237 // handle unknown xml elements... 01238 $this->_lock_cdata = &$this->_lock_ref[$this->_xmltree[$parser]]; 01239 01240 } 01241 } 01242 01253 private function _lock_cData($parser, $cdata) { 01254 if (trim($cdata) <> '') { 01255 // $this->_error_log(($this->_xmltree[$parser]) . '='. htmlentities($cdata)); 01256 $this->_lock_ref_cdata .= $cdata; 01257 } else { 01258 // do nothing 01259 } 01260 } 01261 01262 01270 private function header_add($string) { 01271 $this->_req[] = $string; 01272 } 01273 01281 private function header_unset() { 01282 unset($this->_req); 01283 } 01284 01292 private function create_basic_request($method) { 01293 $request = ''; 01294 $this->header_add(sprintf('%s %s %s', $method, $this->_path, $this->_protocol)); 01295 $this->header_add(sprintf('Host: %s:%s', $this->_server, $this->_port)); 01296 //$request .= sprintf('Connection: Keep-Alive'); 01297 $this->header_add(sprintf('User-Agent: %s', $this->_user_agent)); 01298 $this->header_add('Connection: TE'); 01299 $this->header_add('TE: Trailers'); 01300 if ($this->_auth == 'basic') { 01301 $this->header_add(sprintf('Authorization: Basic %s', base64_encode("$this->_user:$this->_pass"))); 01302 } 01303 } 01304 01312 private function send_request() { 01313 // check if stream is declared to be open 01314 // only logical check we are not sure if socket is really still open ... 01315 if ($this->_connection_closed) { 01316 // reopen it 01317 // be sure to close the open socket. 01318 $this->close(); 01319 $this->reopen(); 01320 } 01321 01322 // convert array to string 01323 $buffer = implode("\r\n", $this->_req); 01324 $buffer .= "\r\n\r\n"; 01325 $this->_error_log($buffer); 01326 fputs($this->sock, $buffer); 01327 } 01328 01340 private function get_respond() { 01341 $this->_error_log('get_respond()'); 01342 // init vars (good coding style ;-) 01343 $buffer = ''; 01344 $header = ''; 01345 // attention: do not make max_chunk_size to big.... 01346 $max_chunk_size = 8192; 01347 // be sure we got a open ressource 01348 if (! $this->sock) { 01349 $this->_error_log('socket is not open. Can not process response'); 01350 return false; 01351 } 01352 01353 // following code maybe helps to improve socket behaviour ... more testing needed 01354 // disabled at the moment ... 01355 // socket_set_timeout($this->sock,1 ); 01356 // $socket_state = socket_get_status($this->sock); 01357 01358 // read stream one byte by another until http header ends 01359 $i = 0; 01360 $matches = array(); 01361 do { 01362 $header.=fread($this->sock, 1); 01363 $i++; 01364 } while (!preg_match('/\\r\\n\\r\\n$/',$header, $matches) && $i < $this->_maxheaderlenth); 01365 01366 $this->_error_log($header); 01367 01368 if (preg_match('/Connection: close\\r\\n/', $header)) { 01369 // This says that the server will close connection at the end of this stream. 01370 // Therefore we need to reopen the socket, before are sending the next request... 01371 $this->_error_log('Connection: close found'); 01372 $this->_connection_closed = true; 01373 } 01374 // check how to get the data on socket stream 01375 // chunked or content-length (HTTP/1.1) or 01376 // one block until feof is received (HTTP/1.0) 01377 switch(true) { 01378 case (preg_match('/Transfer\\-Encoding:\\s+chunked\\r\\n/',$header)): 01379 $this->_error_log('Getting HTTP/1.1 chunked data...'); 01380 do { 01381 $byte = ''; 01382 $chunk_size=''; 01383 do { 01384 $chunk_size.=$byte; 01385 $byte=fread($this->sock,1); 01386 // check what happens while reading, because I do not really understand how php reads the socketstream... 01387 // but so far - it seems to work here - tested with php v4.3.1 on apache 1.3.27 and Debian Linux 3.0 ... 01388 if (strlen($byte) == 0) { 01389 $this->_error_log('get_respond: warning --> read zero bytes'); 01390 } 01391 } while ($byte!="\r" and strlen($byte)>0); // till we match the Carriage Return 01392 fread($this->sock, 1); // also drop off the Line Feed 01393 $chunk_size=hexdec($chunk_size); // convert to a number in decimal system 01394 if ($chunk_size > 0) { 01395 $buffer .= fread($this->sock,$chunk_size); 01396 } 01397 fread($this->sock, 2); // ditch the CRLF that trails the chunk 01398 } while ($chunk_size); // till we reach the 0 length chunk (end marker) 01399 break; 01400 01401 // check for a specified content-length 01402 case preg_match('/Content\\-Length:\\s+([0-9]*)\\r\\n/',$header,$matches): 01403 $this->_error_log('Getting data using Content-Length '. $matches[1]); 01404 01405 // check if we the content data size is small enough to get it as one block 01406 if ($matches[1] <= $max_chunk_size ) { 01407 // only read something if Content-Length is bigger than 0 01408 if ($matches[1] > 0 ) { 01409 $buffer = fread($this->sock, $matches[1]); 01410 $loadsize = strlen($buffer); 01411 //did we realy get the full length? 01412 if ($loadsize < $matches[1]) { 01413 $max_chunk_size = $loadsize; 01414 do { 01415 $mod = $max_chunk_size % ($matches[1] - strlen($buffer)); 01416 $chunk_size = ($mod == $max_chunk_size ? $max_chunk_size : $matches[1] - strlen($buffer)); 01417 $buffer .= fread($this->sock, $chunk_size); 01418 $this->_error_log('mod: ' . $mod . ' chunk: ' . $chunk_size . ' total: ' . strlen($buffer)); 01419 } while ($mod == $max_chunk_size); 01420 break; 01421 } else { 01422 break; 01423 } 01424 } else { 01425 $buffer = ''; 01426 break; 01427 } 01428 } 01429 01430 // data is to big to handle it as one. Get it chunk per chunk... 01431 //trying to get the full length of max_chunk_size 01432 $buffer = fread($this->sock, $max_chunk_size); 01433 $loadsize = strlen($buffer); 01434 if ($loadsize < $max_chunk_size) { 01435 $max_chunk_size = $loadsize; 01436 } 01437 do { 01438 $mod = $max_chunk_size % ($matches[1] - strlen($buffer)); 01439 $chunk_size = ($mod == $max_chunk_size ? $max_chunk_size : $matches[1] - strlen($buffer)); 01440 $buffer .= fread($this->sock, $chunk_size); 01441 $this->_error_log('mod: ' . $mod . ' chunk: ' . $chunk_size . ' total: ' . strlen($buffer)); 01442 } while ($mod == $max_chunk_size); 01443 $loadsize = strlen($buffer); 01444 if ($loadsize < $matches[1]) { 01445 $buffer .= fread($this->sock, $matches[1] - $loadsize); 01446 } 01447 break; 01448 01449 // check for 204 No Content 01450 // 204 responds have no body. 01451 // Therefore we do not need to read any data from socket stream. 01452 case preg_match('/HTTP\/1\.1\ 204/',$header): 01453 // nothing to do, just proceed 01454 $this->_error_log('204 No Content found. No further data to read..'); 01455 break; 01456 default: 01457 // just get the data until foef appears... 01458 $this->_error_log('reading until feof...' . $header); 01459 socket_set_timeout($this->sock, 0, 0); 01460 while (!feof($this->sock)) { 01461 $buffer .= fread($this->sock, 4096); 01462 } 01463 // renew the socket timeout...does it do something ???? Is it needed. More debugging needed... 01464 socket_set_timeout($this->sock, $this->_socket_timeout, 0); 01465 } 01466 01467 $this->_header = $header; 01468 $this->_body = $buffer; 01469 // $this->_buffer = $header . "\r\n\r\n" . $buffer; 01470 $this->_error_log($this->_header); 01471 $this->_error_log($this->_body); 01472 } 01473 01474 01483 private function process_respond() { 01484 $lines = explode("\r\n", $this->_header); 01485 $header_done = false; 01486 // $this->_error_log($this->_buffer); 01487 // First line should be a HTTP status line (see http://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html#sec6) 01488 // Format is: HTTP-Version SP Status-Code SP Reason-Phrase CRLF 01489 list($ret_struct['status']['http-version'], 01490 $ret_struct['status']['status-code'], 01491 $ret_struct['status']['reason-phrase']) = explode(' ', $lines[0],3); 01492 01493 // print "HTTP Version: '$http_version' Status-Code: '$status_code' Reason Phrase: '$reason_phrase'<br>"; 01494 // get the response header fields 01495 // See http://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html#sec6 01496 for($i=1; $i<count($lines); $i++) { 01497 if (rtrim($lines[$i]) == '' && !$header_done) { 01498 $header_done = true; 01499 // print "--- response header end ---<br>"; 01500 01501 } 01502 if (!$header_done ) { 01503 // store all found headers in array ... 01504 list($fieldname, $fieldvalue) = explode(':', $lines[$i]); 01505 // check if this header was allready set (apache 2.0 webdav module does this....). 01506 // If so we add the the value to the end the fieldvalue, separated by comma... 01507 if (empty($ret_struct['header'])) { 01508 $ret_struct['header'] = array(); 01509 } 01510 if (empty($ret_struct['header'][$fieldname])) { 01511 $ret_struct['header'][$fieldname] = trim($fieldvalue); 01512 } else { 01513 $ret_struct['header'][$fieldname] .= ',' . trim($fieldvalue); 01514 } 01515 } 01516 } 01517 // print 'string len of response_body:'. strlen($response_body); 01518 // print '[' . htmlentities($response_body) . ']'; 01519 $ret_struct['body'] = $this->_body; 01520 $this->_error_log('process_respond: ' . var_export($ret_struct,true)); 01521 return $ret_struct; 01522 01523 } 01524 01533 private function reopen() { 01534 // let's try to reopen a socket 01535 $this->_error_log('reopen a socket connection'); 01536 return $this->open(); 01537 } 01538 01539 01549 private function translate_uri($uri) { 01550 // remove all html entities... 01551 $native_path = html_entity_decode($uri); 01552 $parts = explode('/', $native_path); 01553 for ($i = 0; $i < count($parts); $i++) { 01554 // check if part is allready utf8 01555 if (iconv('UTF-8', 'UTF-8', $parts[$i]) == $parts[$i]) { 01556 $parts[$i] = rawurlencode($parts[$i]); 01557 } else { 01558 $parts[$i] = rawurlencode(utf8_encode($parts[$i])); 01559 } 01560 } 01561 return implode('/', $parts); 01562 } 01563 01571 private function utf_decode_path($path) { 01572 $fullpath = $path; 01573 if (iconv('UTF-8', 'UTF-8', $fullpath) == $fullpath) { 01574 $this->_error_log("filename is utf-8. Needs conversion..."); 01575 $fullpath = utf8_decode($fullpath); 01576 } 01577 return $fullpath; 01578 } 01579 01587 private function _error_log($err_string) { 01588 if ($this->_debug) { 01589 error_log($err_string); 01590 } 01591 } 01592 }