From 7b58fb417925ec970a7e375f4fe994bd3b5978a9 Mon Sep 17 00:00:00 2001 From: dongsheng Date: Thu, 23 Apr 2009 09:50:30 +0000 Subject: [PATCH] "webdav plugin/MDL-16908, fix header check code, reformat code" --- lang/en_utf8/repository.php | 1 + lang/en_utf8/repository_webdav.php | 4 + lib/webdavlib.php | 3127 ++++++++++++------------ repository/webdav/repository.class.php | 63 +- repository/ws.php | 12 +- 5 files changed, 1629 insertions(+), 1578 deletions(-) diff --git a/lang/en_utf8/repository.php b/lang/en_utf8/repository.php index d9186e9da2..db6e5c76e4 100644 --- a/lang/en_utf8/repository.php +++ b/lang/en_utf8/repository.php @@ -15,6 +15,7 @@ $string['back'] = '< Back'; $string['cacheexpire'] = 'Cache expire'; $string['cachecleared'] = 'Cached files are removed'; $string['cannotinitplugin'] = 'Call plugin_init failed'; +$string['cannotdownload'] = 'Cannot download this file'; $string['clicktohide'] = 'Click here to hide'; $string['clicktoshow'] = 'Click here to show'; $string['close'] = 'Close'; diff --git a/lang/en_utf8/repository_webdav.php b/lang/en_utf8/repository_webdav.php index 85dfcfcced..115d38b23d 100644 --- a/lang/en_utf8/repository_webdav.php +++ b/lang/en_utf8/repository_webdav.php @@ -1,6 +1,10 @@ _server = $server; - } - - /** - * Set tcp port of webdav server. Default is 80. - * @param int port - */ - function set_port($port) { - $this->_port = $port; - } - - /** - * set user name for authentification - * @param string user - */ - function set_user($user) { - $this->_user = $user; - } - - /** - * Set password for authentification - * @param string pass - */ - function set_pass($pass) { - $this->_pass = $pass; - } - - /** - * set debug on (1) or off (0). - * produces a lot of debug messages in webservers error log if set to on (1). - * @param bool debug - */ - function set_debug($debug) { - $this->_debug = $debug; - } - - /** - * Set which HTTP protocol will be used. - * Value 1 defines that HTTP/1.1 should be used (Keeps Connection to webdav server alive). - * Otherwise HTTP/1.0 will be used. - * @param int version - */ - function set_protocol($version) { - if ($version == 1) { - $this->_protocol = 'HTTP/1.1'; - } else { - $this->_protocol = 'HTTP/1.0'; - } - $this->_error_log('HTTP Protocol was set to ' . $this->_protocol); - - } - - /** - * Convert ISO 8601 Date and Time Profile used in RFC 2518 to an unix timestamp. - * @access private - * @param string iso8601 - * @return unixtimestamp on sucess. Otherwise false. - */ - function iso8601totime($iso8601) { - /* - - date-time = full-date "T" full-time - - full-date = date-fullyear "-" date-month "-" date-mday - full-time = partial-time time-offset - - date-fullyear = 4DIGIT - date-month = 2DIGIT ; 01-12 - date-mday = 2DIGIT ; 01-28, 01-29, 01-30, 01-31 based on - month/year - time-hour = 2DIGIT ; 00-23 - time-minute = 2DIGIT ; 00-59 - time-second = 2DIGIT ; 00-59, 00-60 based on leap second rules - time-secfrac = "." 1*DIGIT - time-numoffset = ("+" / "-") time-hour ":" time-minute - time-offset = "Z" / time-numoffset - - partial-time = time-hour ":" time-minute ":" time-second - [time-secfrac] - */ - - $regs = array(); - /* [1] [2] [3] [4] [5] [6] */ - if (ereg('^([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})Z$', $iso8601, $regs)) { - return mktime($regs[4],$regs[5], $regs[6], $regs[2], $regs[3], $regs[1]); - } - // to be done: regex for partial-time...apache webdav mod never returns partial-time - - return false; - } - - /** - * Open's a socket to a webdav server - * @return bool true on success. Otherwise false. - */ - function open() { - // let's try to open a socket - $this->_error_log('open a socket connection'); - $this->_fp = fsockopen ($this->_server, $this->_port, $this->_errno, $this->_errstr, $this->_socket_timeout); - // set_time_limit(30); - socket_set_blocking($this->_fp, true); - if (!$this->_fp) { - $this->_error_log("$this->_errstr ($this->_errno)\n"); - return false; - } else { - $this->_connection_closed = false; - $this->_error_log('socket is open: ' . $this->_fp); - return true; - } - } - - /** - * Closes an open socket. - */ - function close() { - $this->_error_log('closing socket ' . $this->_fp); - $this->_connection_closed = true; - fclose($this->_fp); - } - - /** - * Check's if server is a webdav compliant server. - * True if server returns a DAV Element in Header and when - * schema 1,2 is supported. - * @return bool true if server is webdav server. Otherwise false. - */ - function check_webdav() { - $resp = $this->options(); - if (!$resp) { - return false; - } - $this->_error_log($resp['header']['DAV']); - // check schema - if (preg_match('/1,2/', $resp['header']['DAV'])) { - return true; - } - // otherwise return false - return false; - } - - - /** - * Get options from webdav server. - * @return array with all header fields returned from webdav server. false if server does not speak http. - */ - function options() { - $this->_header_unset(); - $this->_create_basic_request('OPTIONS'); - $this->_send_request(); - $this->_get_respond(); - $response = $this->_process_respond(); - // validate the response ... - // check http-version - if ($response['status']['http-version'] == 'HTTP/1.1' || - $response['status']['http-version'] == 'HTTP/1.0') { - return $response; - } - $this->_error_log('Response was not even http'); - return false; - - } - - /** - * Public method mkcol - * - * Creates a new collection/directory on a webdav server - * @param string path - * @return int status code received as reponse from webdav server (see rfc 2518) - */ - function mkcol($path) { - $this->_path = $this->_translate_uri($path); - $this->_header_unset(); - $this->_create_basic_request('MKCOL'); - $this->_send_request(); - $this->_get_respond(); - $response = $this->_process_respond(); - // validate the response ... - // check http-version - if ($response['status']['http-version'] == 'HTTP/1.1' || - $response['status']['http-version'] == 'HTTP/1.0') { - /* seems to be http ... proceed - just return what server gave us - rfc 2518 says: - 201 (Created) - The collection or structured resource was created in its entirety. - 403 (Forbidden) - This indicates at least one of two conditions: 1) the server does not allow the creation of collections at the given - location in its namespace, or 2) the parent collection of the Request-URI exists but cannot accept members. - 405 (Method Not Allowed) - MKCOL can only be executed on a deleted/non-existent resource. - 409 (Conflict) - A collection cannot be made at the Request-URI until one or more intermediate collections have been created. - 415 (Unsupported Media Type)- The server does not support the request type of the body. - 507 (Insufficient Storage) - The resource does not have sufficient space to record the state of the resource after the execution of this method. - */ - return $response['status']['status-code']; - } - - } - - /** - * Public method get - * - * Gets a file from a webdav collection. - * @param string path, string &buffer - * @return status code and &$buffer (by reference) with response data from server on success. False on error. - */ - function get($path, &$buffer) { - $this->_path = $this->_translate_uri($path); - $this->_header_unset(); - $this->_create_basic_request('GET'); - $this->_send_request(); - $this->_get_respond(); - $response = $this->_process_respond(); - - // validate the response - // check http-version - if ($response['status']['http-version'] == 'HTTP/1.1' || - $response['status']['http-version'] == 'HTTP/1.0') { - // seems to be http ... proceed - // We expect a 200 code - if ($response['status']['status-code'] == 200 ) { - $this->_error_log('returning buffer with ' . strlen($response['body']) . ' bytes.'); - $buffer = $response['body']; - } - return $response['status']['status-code']; - } - // ups: no http status was returned ? - return false; - } - - /** - * Public method put - * - * Puts a file into a collection. - * Data is putted as one chunk! - * @param string path, string data - * @return int status-code read from webdavserver. False on error. - */ - function put($path, $data ) { - $this->_path = $this->_translate_uri($path); - $this->_header_unset(); - $this->_create_basic_request('PUT'); - // add more needed header information ... - $this->_header_add('Content-length: ' . strlen($data)); - $this->_header_add('Content-type: application/octet-stream'); - // send header - $this->_send_request(); - // send the rest (data) - fputs($this->_fp, $data); - $this->_get_respond(); - $response = $this->_process_respond(); - - // validate the response - // check http-version - if ($response['status']['http-version'] == 'HTTP/1.1' || - $response['status']['http-version'] == 'HTTP/1.0') { - // seems to be http ... proceed - // We expect a 200 or 204 status code - // see rfc 2068 - 9.6 PUT... - // print 'http ok
'; - return $response['status']['status-code']; - } - // ups: no http status was returned ? - return false; - } - - /** - * Public method put_file - * - * Read a file as stream and puts it chunk by chunk into webdav server collection. - * - * Look at php documenation for legal filenames with fopen(); - * The filename will be translated into utf-8 if not allready in utf-8. - * - * @param string targetpath, string filename - * @return int status code. False on error. - */ - function put_file($path, $filename) { - // try to open the file ... - - - $handle = @fopen ($filename, 'r'); - - if ($handle) { - // $this->_fp = pfsockopen ($this->_server, $this->_port, $this->_errno, $this->_errstr, $this->_socket_timeout); - $this->_path = $this->_translate_uri($path); - $this->_header_unset(); - $this->_create_basic_request('PUT'); - // add more needed header information ... - $this->_header_add('Content-length: ' . filesize($filename)); - $this->_header_add('Content-type: application/octet-stream'); - // send header - $this->_send_request(); - while (!feof($handle)) { - fputs($this->_fp,fgets($handle,4096)); - } - fclose($handle); - $this->_get_respond(); - $response = $this->_process_respond(); - - // validate the response - // check http-version - if ($response['status']['http-version'] == 'HTTP/1.1' || - $response['status']['http-version'] == 'HTTP/1.0') { - // seems to be http ... proceed - // We expect a 200 or 204 status code - // see rfc 2068 - 9.6 PUT... - // print 'http ok
'; - return $response['status']['status-code']; - } - // ups: no http status was returned ? - return false; - } else { - $this->_error_log('put_file: could not open ' . $filename); - return false; - } - - } - - /** - * Public method get_file - * - * Gets a file from a collection into local filesystem. - * - * fopen() is used. - * @param string srcpath, string localpath - * @return true on success. false on error. - */ - function get_file($srcpath, $localpath) { - - if ($this->get($srcpath, $buffer)) { - // convert utf-8 filename to iso-8859-1 - - $localpath = $this->_utf_decode_path($localpath); - - $handle = fopen ($localpath, 'w'); - if ($handle) { - fwrite($handle, $buffer); - fclose($handle); - return true; - } else { - return false; - } - } else { - return false; - } - } - - /** - * Public method copy_file - * - * Copies a file on a webdav server - * - * Duplicates a file on the webdav server (serverside). - * All work is done on the webdav server. If you set param overwrite as true, - * the target will be overwritten. - * - * @param string src_path, string dest_path, bool overwrite - * @return int status code (look at rfc 2518). false on error. - */ - function copy_file($src_path, $dst_path, $overwrite) { - $this->_path = $this->_translate_uri($src_path); - $this->_header_unset(); - $this->_create_basic_request('COPY'); - $this->_header_add(sprintf('Destination: http://%s%s', $this->_server, $this->_translate_uri($dst_path))); - if ($overwrite) { - $this->_header_add('Overwrite: T'); - } else { - $this->_header_add('Overwrite: F'); - } - $this->_header_add(''); - $this->_send_request(); - $this->_get_respond(); - $response = $this->_process_respond(); - // validate the response ... - // check http-version - if ($response['status']['http-version'] == 'HTTP/1.1' || - $response['status']['http-version'] == 'HTTP/1.0') { - /* seems to be http ... proceed - just return what server gave us (as defined in rfc 2518) : - 201 (Created) - The source resource was successfully copied. The copy operation resulted in the creation of a new resource. - 204 (No Content) - The source resource was successfully copied to a pre-existing destination resource. - 403 (Forbidden) - The source and destination URIs are the same. - 409 (Conflict) - A resource cannot be created at the destination until one or more intermediate collections have been created. - 412 (Precondition Failed) - The server was unable to maintain the liveness of the properties listed in the propertybehavior XML element - or the Overwrite header is "F" and the state of the destination resource is non-null. - 423 (Locked) - The destination resource was locked. - 502 (Bad Gateway) - This may occur when the destination is on another server and the destination server refuses to accept the resource. - 507 (Insufficient Storage) - The destination resource does not have sufficient space to record the state of the resource after the - execution of this method. - */ - return $response['status']['status-code']; - } - return false; - } - -/** - * Public method copy_coll - * - * Copies a collection on a webdav server - * - * Duplicates a collection on the webdav server (serverside). - * All work is done on the webdav server. If you set param overwrite as true, - * the target will be overwritten. - * - * @param string src_path, string dest_path, bool overwrite - * @return int status code (look at rfc 2518). false on error. - */ - function copy_coll($src_path, $dst_path, $overwrite) { - $this->_path = $this->_translate_uri($src_path); - $this->_header_unset(); - $this->_create_basic_request('COPY'); - $this->_header_add(sprintf('Destination: http://%s%s', $this->_server, $this->_translate_uri($dst_path))); - $this->_header_add('Depth: Infinity'); - - $xml = "\r\n"; - $xml .= "\r\n"; - $xml .= " *\r\n"; - $xml .= "\r\n"; - - $this->_header_add('Content-length: ' . strlen($xml)); - $this->_header_add('Content-type: text/xml'); - $this->_send_request(); - // send also xml - fputs($this->_fp, $xml); - $this->_get_respond(); - $response = $this->_process_respond(); - // validate the response ... - // check http-version - if ($response['status']['http-version'] == 'HTTP/1.1' || - $response['status']['http-version'] == 'HTTP/1.0') { - /* seems to be http ... proceed - just return what server gave us (as defined in rfc 2518) : - 201 (Created) - The source resource was successfully copied. The copy operation resulted in the creation of a new resource. - 204 (No Content) - The source resource was successfully copied to a pre-existing destination resource. - 403 (Forbidden) - The source and destination URIs are the same. - 409 (Conflict) - A resource cannot be created at the destination until one or more intermediate collections have been created. - 412 (Precondition Failed) - The server was unable to maintain the liveness of the properties listed in the propertybehavior XML element - or the Overwrite header is "F" and the state of the destination resource is non-null. - 423 (Locked) - The destination resource was locked. - 502 (Bad Gateway) - This may occur when the destination is on another server and the destination server refuses to accept the resource. - 507 (Insufficient Storage) - The destination resource does not have sufficient space to record the state of the resource after the - execution of this method. - */ - return $response['status']['status-code']; - } - return false; - } - - /** - * Public method move - * - * Moves a file or collection on webdav server (serverside) - * - * If you set param overwrite as true, the target will be overwritten. - * - * @param string src_path, string dest_path, bool overwrite - * @return int status code (look at rfc 2518). false on error. - */ - // -------------------------------------------------------------------------- - // public method move - // move/rename a file/collection on webdav server - function move($src_path,$dst_path, $overwrite) { - - $this->_path = $this->_translate_uri($src_path); - $this->_header_unset(); - - $this->_create_basic_request('MOVE'); - $this->_header_add(sprintf('Destination: http://%s%s', $this->_server, $this->_translate_uri($dst_path))); - if ($overwrite) { - $this->_header_add('Overwrite: T'); - } else { - $this->_header_add('Overwrite: F'); - } - $this->_header_add(''); - - $this->_send_request(); - $this->_get_respond(); - $response = $this->_process_respond(); - // validate the response ... - // check http-version - if ($response['status']['http-version'] == 'HTTP/1.1' || - $response['status']['http-version'] == 'HTTP/1.0') { - /* seems to be http ... proceed - just return what server gave us (as defined in rfc 2518) : - 201 (Created) - The source resource was successfully moved, and a new resource was created at the destination. - 204 (No Content) - The source resource was successfully moved to a pre-existing destination resource. - 403 (Forbidden) - The source and destination URIs are the same. - 409 (Conflict) - A resource cannot be created at the destination until one or more intermediate collections have been created. - 412 (Precondition Failed) - The server was unable to maintain the liveness of the properties listed in the propertybehavior XML element - or the Overwrite header is "F" and the state of the destination resource is non-null. - 423 (Locked) - The source or the destination resource was locked. - 502 (Bad Gateway) - This may occur when the destination is on another server and the destination server refuses to accept the resource. - - 201 (Created) - The collection or structured resource was created in its entirety. - 403 (Forbidden) - This indicates at least one of two conditions: 1) the server does not allow the creation of collections at the given - location in its namespace, or 2) the parent collection of the Request-URI exists but cannot accept members. - 405 (Method Not Allowed) - MKCOL can only be executed on a deleted/non-existent resource. - 409 (Conflict) - A collection cannot be made at the Request-URI until one or more intermediate collections have been created. - 415 (Unsupported Media Type)- The server does not support the request type of the body. - 507 (Insufficient Storage) - The resource does not have sufficient space to record the state of the resource after the execution of this method. - */ - return $response['status']['status-code']; - } - return false; - } - - /** - * Public method lock - * - * Locks a file or collection. - * - * Lock uses this->_user as lock owner. - * - * @param string path - * @return int status code (look at rfc 2518). false on error. - */ - function lock($path) { - $this->_path = $this->_translate_uri($path); - $this->_header_unset(); - $this->_create_basic_request('LOCK'); - $this->_header_add('Timeout: Infinite'); - $this->_header_add('Content-type: text/xml'); - // create the xml request ... - $xml = "\r\n"; - $xml .= ""; - $xml .= " \r\n"; - $xml .= " \r\n"; - $xml .= " \r\n"; - $xml .= " ".($this->_user)."\r\n"; - $xml .= " \r\n"; - $xml .= "\r\n"; - $this->_header_add('Content-length: ' . strlen($xml)); - $this->_send_request(); - // send also xml - fputs($this->_fp, $xml); - $this->_get_respond(); - $response = $this->_process_respond(); - // validate the response ... (only basic validation) - // check http-version - if ($response['status']['http-version'] == 'HTTP/1.1' || - $response['status']['http-version'] == 'HTTP/1.0') { - /* seems to be http ... proceed - rfc 2518 says: - 200 (OK) - The lock request succeeded and the value of the lockdiscovery property is included in the body. - 412 (Precondition Failed) - The included lock token was not enforceable on this resource or the server could not satisfy the - request in the lockinfo XML element. - 423 (Locked) - The resource is locked, so the method has been rejected. - */ - - switch($response['status']['status-code']) { - case 200: - // collection was successfully locked... see xml response to get lock token... - if (strcmp($response['header']['Content-Type'], 'text/xml; charset="utf-8"') == 0) { - // ok let's get the content of the xml stuff - $this->_parser = xml_parser_create_ns(); - // forget old data... - unset($this->_lock[$this->_parser]); - unset($this->_xmltree[$this->_parser]); - xml_parser_set_option($this->_parser,XML_OPTION_SKIP_WHITE,0); - xml_parser_set_option($this->_parser,XML_OPTION_CASE_FOLDING,0); - xml_set_object($this->_parser, $this); - xml_set_element_handler($this->_parser, "_lock_startElement", "_endElement"); - xml_set_character_data_handler($this->_parser, "_lock_cdata"); - - if (!xml_parse($this->_parser, $response['body'])) { - die(sprintf("XML error: %s at line %d", - xml_error_string(xml_get_error_code($this->_parser)), - xml_get_current_line_number($this->_parser))); - } - - // Free resources - xml_parser_free($this->_parser); - // add status code to array - $this->_lock[$this->_parser]['status'] = 200; - return $this->_lock[$this->_parser]; - - } else { - print 'Missing Content-Type: text/xml header in response.
'; - } - return false; - - default: - // hmm. not what we expected. Just return what we got from webdav server - // someone else has to handle it. - $this->_lock['status'] = $response['status']['status-code']; - return $this->_lock; - } - } - - - } - - - /** - * Public method unlock - * - * Unlocks a file or collection. - * - * @param string path, string locktoken - * @return int status code (look at rfc 2518). false on error. - */ - function unlock($path, $locktoken) { - $this->_path = $this->_translate_uri($path); - $this->_header_unset(); - $this->_create_basic_request('UNLOCK'); - $this->_header_add(sprintf('Lock-Token: <%s>', $locktoken)); - $this->_send_request(); - $this->_get_respond(); - $response = $this->_process_respond(); - if ($response['status']['http-version'] == 'HTTP/1.1' || - $response['status']['http-version'] == 'HTTP/1.0') { - /* seems to be http ... proceed - rfc 2518 says: - 204 (OK) - The 204 (No Content) status code is used instead of 200 (OK) because there is no response entity body. - */ - return $response['status']['status-code']; - } - return false; - } - - /** - * Public method delete - * - * deletes a collection/directory on a webdav server - * @param string path - * @return int status code (look at rfc 2518). false on error. - */ - function delete($path) { - $this->_path = $this->_translate_uri($path); - $this->_header_unset(); - $this->_create_basic_request('DELETE'); - /* $this->_header_add('Content-Length: 0'); */ - $this->_header_add(''); - $this->_send_request(); - $this->_get_respond(); - $response = $this->_process_respond(); - - // validate the response ... - // check http-version - if ($response['status']['http-version'] == 'HTTP/1.1' || - $response['status']['http-version'] == 'HTTP/1.0') { - // seems to be http ... proceed - // We expect a 207 Multi-Status status code - // print 'http ok
'; - - switch ($response['status']['status-code']) { - case 207: - // collection was NOT deleted... see xml response for reason... - // next there should be a Content-Type: text/xml; charset="utf-8" header line - if (strcmp($response['header']['Content-Type'], 'text/xml; charset="utf-8"') == 0) { - // ok let's get the content of the xml stuff - $this->_parser = xml_parser_create_ns(); - // forget old data... - unset($this->_delete[$this->_parser]); - unset($this->_xmltree[$this->_parser]); - xml_parser_set_option($this->_parser,XML_OPTION_SKIP_WHITE,0); - xml_parser_set_option($this->_parser,XML_OPTION_CASE_FOLDING,0); - xml_set_object($this->_parser, $this); - xml_set_element_handler($this->_parser, "_delete_startElement", "_endElement"); - xml_set_character_data_handler($this->_parser, "_delete_cdata"); - - if (!xml_parse($this->_parser, $response['body'])) { - die(sprintf("XML error: %s at line %d", - xml_error_string(xml_get_error_code($this->_parser)), - xml_get_current_line_number($this->_parser))); - } - - print_r($this->_delete[$this->_parser]); - print "
"; - - // Free resources - xml_parser_free($this->_parser); - $this->_delete[$this->_parser]['status'] = $response['status']['status-code']; - return $this->_delete[$this->_parser]; - - } else { - print 'Missing Content-Type: text/xml header in response.
'; - } - return false; - - default: - // collection or file was successfully deleted - $this->_delete['status'] = $response['status']['status-code']; - return $this->_delete; - - - } - } - - } - - /** - * Public method ls - * - * Get's directory information from webdav server into flat a array using PROPFIND - * - * All filenames are UTF-8 encoded. - * Have a look at _propfind_startElement what keys are used in array returned. - * @param string path - * @return array dirinfo, false on error - */ - function ls($path) { - - if (trim($path) == '') { - $this->_error_log('Missing a path in method ls'); - return false; - } - $this->_path = $this->_translate_uri($path); - - $this->_header_unset(); - $this->_create_basic_request('PROPFIND'); - $this->_header_add('Depth: 1'); - $this->_header_add('Content-type: text/xml'); - // create profind xml request... - $xml = "\r\n"; - $xml .= "\r\n"; - // shall we get all properties ? - $xml .= " \r\n"; - // or should we better get only wanted props ? - $xml .= "\r\n"; - $this->_header_add('Content-length: ' . strlen($xml)); - $this->_send_request(); - $this->_error_log($xml); - fputs($this->_fp, $xml); - $this->_get_respond(); - $response = $this->_process_respond(); - // validate the response ... (only basic validation) - // check http-version - if ($response['status']['http-version'] == 'HTTP/1.1' || - $response['status']['http-version'] == 'HTTP/1.0') { - // seems to be http ... proceed - // We expect a 207 Multi-Status status code - // print 'http ok
'; - if (strcmp($response['status']['status-code'],'207') == 0 ) { - // ok so far - // next there should be a Content-Type: text/xml; charset="utf-8" header line - if (strcmp($response['header']['Content-Type'], 'text/xml; charset="utf-8"') == 0) { - // ok let's get the content of the xml stuff - $this->_parser = xml_parser_create_ns('UTF-8'); - // forget old data... - unset($this->_ls[$this->_parser]); - unset($this->_xmltree[$this->_parser]); - xml_parser_set_option($this->_parser,XML_OPTION_SKIP_WHITE,0); - xml_parser_set_option($this->_parser,XML_OPTION_CASE_FOLDING,0); - // xml_parser_set_option($this->_parser,XML_OPTION_TARGET_ENCODING,'UTF-8'); - xml_set_object($this->_parser, $this); - xml_set_element_handler($this->_parser, "_propfind_startElement", "_endElement"); - xml_set_character_data_handler($this->_parser, "_propfind_cdata"); - - - if (!xml_parse($this->_parser, $response['body'])) { - die(sprintf("XML error: %s at line %d", - xml_error_string(xml_get_error_code($this->_parser)), - xml_get_current_line_number($this->_parser))); - } - - // Free resources - xml_parser_free($this->_parser); - return $this->_ls[$this->_parser]; - } else { - $this->_error_log('Missing Content-Type: text/xml header in response!!'); - return false; - } - } else { - // return status code ... - return $response['status']['status-code']; - } - } - - // response was not http - $this->_error_log('Ups in method ls: error in response from server'); - return false; - } - - - /** - * Public method gpi - * - * Get's path information from webdav server for one element. - * - * @param string path - * @return array dirinfo. false on error - */ - function gpi($path) { - - // split path by last "/" - $path = rtrim($path, "/"); - $item = basename($path); - $dir = dirname($path); - - $list = $this->ls($dir); - - // be sure it is an array - if (is_array($list)) { - foreach($list as $e) { - - $fullpath = urldecode($e['href']); - $filename = basename($fullpath); - - if ($filename == $item && $filename != "" and $fullpath != $dir."/") { - return $e; - } - } - } - return false; - } - - /** - * Public method is_file - * - * Gathers whether a path points to a file or not. - * - * @param string path - * @return bool true or false - */ - function is_file($path) { - - $item = $this->gpi($path); - - if ($item === false) { - return false; - } else { - return ($item['resourcetype'] != 'collection'); - } - } - - /** - * Public method is_dir - * - * Gather whether a path points to a directory - * @param string path - * return bool true or false - */ - function is_dir($path) { - - // be sure path is utf-8 - $item = $this->gpi($path); - - if ($item === false) { - return false; - } else { - return ($item['resourcetype'] == 'collection'); - } - } - - - /** - * Public method mput - * - * Puts multiple files and/or directories onto a webdav server. - * - * Filenames should be allready UTF-8 encoded. - * Param fileList must be in format array("localpath" => "destpath"). - * - * @param array filelist - * @return bool true on success. otherwise int status code on error - */ - function mput($filelist) { - - $result = true; - - while (list($localpath, $destpath) = each($filelist)) { - - $localpath = rtrim($localpath, "/"); - $destpath = rtrim($destpath, "/"); - - // attempt to create target path - if (is_dir($localpath)) { - $pathparts = explode("/", $destpath."/ "); // add one level, last level will be created as dir - } else { - $pathparts = explode("/", $destpath); - } - $checkpath = ""; - for ($i=1; $iis_dir($checkpath))) { - - $result &= ($this->mkcol($checkpath) == 201 ); - } - } - - if ($result) { - // recurse directories - if (is_dir($localpath)) { - $dp = opendir($localpath); - $fl = array(); - while($filename = readdir($dp)) { - if ((is_file($localpath."/".$filename) || is_dir($localpath."/".$filename)) && $filename!="." && $filename != "..") { - $fl[$localpath."/".$filename] = $destpath."/".$filename; - } - } - $result &= $this->mput($fl); - } else { - $result &= ($this->put_file($destpath, $localpath) == 201); - } - } - } - return $result; - } - - /** - * Public method mget - * - * Gets multiple files and directories. - * - * FileList must be in format array("remotepath" => "localpath"). - * Filenames are UTF-8 encoded. - * - * @param array filelist - * @return bool true on succes, other int status code on error - */ - function mget($filelist) { - - $result = true; - - while (list($remotepath, $localpath) = each($filelist)) { - - $localpath = rtrim($localpath, "/"); - $remotepath = rtrim($remotepath, "/"); - - // attempt to create local path - if ($this->is_dir($remotepath)) { - $pathparts = explode("/", $localpath."/ "); // add one level, last level will be created as dir - } else { - $pathparts = explode("/", $localpath); - } - $checkpath = ""; - for ($i=1; $iis_dir($remotepath)) { - $list = $this->ls($remotepath); - - $fl = array(); - foreach($list as $e) { - $fullpath = urldecode($e['href']); - $filename = basename($fullpath); - if ($filename != '' and $fullpath != $remotepath . '/') { - $fl[$remotepath."/".$filename] = $localpath."/".$filename; - } - } - $result &= $this->mget($fl); - } else { - $result &= ($this->get_file($remotepath, $localpath)); - } - } - } - return $result; - } - - // -------------------------------------------------------------------------- - // private xml callback and helper functions starting here - // -------------------------------------------------------------------------- - - - /** - * Private method _endelement - * - * a generic endElement method (used for all xml callbacks). - * - * @param resource parser, string name - * @access private - */ - - function _endElement($parser, $name) { - // end tag was found... - $this->_xmltree[$parser] = substr($this->_xmltree[$parser],0, strlen($this->_xmltree[$parser]) - (strlen($name) + 1)); - } - - /** - * Private method _propfind_startElement - * - * Is needed by public method ls. - * - * Generic method will called by php xml_parse when a xml start element tag has been detected. - * The xml tree will translated into a flat php array for easier access. - * @param resource parser, string name, string attrs - * @access private - */ - function _propfind_startElement($parser, $name, $attrs) { - // lower XML Names... maybe break a RFC, don't know ... - - $propname = strtolower($name); + + /**#@+ + * @access private + * @var string + */ + private $_debug = false; + private $_fp; + private $_server; + private $_port = 80; + private $_path ='/'; + private $_user; + private $_protocol = 'HTTP/1.0'; + private $_pass; + private $_socket_timeout = 5; + private $_errno; + private $_errstr; + private $_user_agent = 'WebDav for PHP/MoodleBot'; + private $_crlf = "\r\n"; + private $_req; + private $_resp_status; + private $_parser; + private $_xmltree; + private $_tree; + private $_ls = array(); + private $_ls_ref; + private $_ls_ref_cdata; + private $_delete = array(); + private $_delete_ref; + private $_delete_ref_cdata; + private $_lock = array(); + private $_lock_ref; + private $_lock_rec_cdata; + private $_null = NULL; + private $_header=''; + private $_body=''; + private $_connection_closed = false; + private $_maxheaderlenth = 1000; + + /**#@-*/ + + /** + * Constructor - does nothing... + */ + function __construct() { + // do nothing here + } + + /** + * Set webdav server. FQN or IP address. + * @param string server + */ + function set_server($server) { + $this->_server = $server; + } + + /** + * Set tcp port of webdav server. Default is 80. + * @param int port + */ + function set_port($port) { + $this->_port = $port; + } + + /** + * set user name for authentification + * @param string user + */ + function set_user($user) { + $this->_user = $user; + } + + /** + * Set password for authentification + * @param string pass + */ + function set_pass($pass) { + $this->_pass = $pass; + } + + /** + * set debug on (1) or off (0). + * produces a lot of debug messages in webservers error log if set to on (1). + * @param bool debug + */ + function set_debug($debug) { + $this->_debug = $debug; + } + + /** + * Set which HTTP protocol will be used. + * Value 1 defines that HTTP/1.1 should be used (Keeps Connection to webdav server alive). + * Otherwise HTTP/1.0 will be used. + * @param int version + */ + function set_protocol($version) { + if ($version == 1) { + $this->_protocol = 'HTTP/1.1'; + } else { + $this->_protocol = 'HTTP/1.0'; + } + $this->_error_log('HTTP Protocol was set to ' . $this->_protocol); + + } + + /** + * Convert ISO 8601 Date and Time Profile used in RFC 2518 to an unix timestamp. + * @access private + * @param string iso8601 + * @return unixtimestamp on sucess. Otherwise false. + */ + function iso8601totime($iso8601) { + /* + + date-time = full-date "T" full-time + + full-date = date-fullyear "-" date-month "-" date-mday + full-time = partial-time time-offset + + date-fullyear = 4DIGIT + date-month = 2DIGIT ; 01-12 + date-mday = 2DIGIT ; 01-28, 01-29, 01-30, 01-31 based on + month/year + time-hour = 2DIGIT ; 00-23 + time-minute = 2DIGIT ; 00-59 + time-second = 2DIGIT ; 00-59, 00-60 based on leap second rules + time-secfrac = "." 1*DIGIT + time-numoffset = ("+" / "-") time-hour ":" time-minute + time-offset = "Z" / time-numoffset + + partial-time = time-hour ":" time-minute ":" time-second + [time-secfrac] + */ + + $regs = array(); + /* [1] [2] [3] [4] [5] [6] */ + if (ereg('^([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})Z$', $iso8601, $regs)) { + return mktime($regs[4],$regs[5], $regs[6], $regs[2], $regs[3], $regs[1]); + } + // to be done: regex for partial-time...apache webdav mod never returns partial-time + + return false; + } + + /** + * Open's a socket to a webdav server + * @return bool true on success. Otherwise false. + */ + function open() { + // let's try to open a socket + $this->_error_log('open a socket connection'); + $this->_fp = @fsockopen ($this->_server, $this->_port, $this->_errno, $this->_errstr, $this->_socket_timeout); + set_time_limit(30); + if (is_resource($this->_fp)) { + socket_set_blocking($this->_fp, true); + $this->_connection_closed = false; + $this->_error_log('socket is open: ' . $this->_fp); + return true; + } else { + $this->_error_log("$this->_errstr ($this->_errno)\n"); + return false; + } + } + + /** + * Closes an open socket. + */ + function close() { + $this->_error_log('closing socket ' . $this->_fp); + $this->_connection_closed = true; + fclose($this->_fp); + } + + /** + * Check's if server is a webdav compliant server. + * True if server returns a DAV Element in Header and when + * schema 1,2 is supported. + * @return bool true if server is webdav server. Otherwise false. + */ + function check_webdav() { + $resp = $this->options(); + if (!$resp) { + return false; + } + $this->_error_log($resp['header']['DAV']); + // check schema + if (preg_match('/1,2/', $resp['header']['DAV'])) { + return true; + } + // otherwise return false + return false; + } + + + /** + * Get options from webdav server. + * @return array with all header fields returned from webdav server. false if server does not speak http. + */ + function options() { + $this->_header_unset(); + $this->_create_basic_request('OPTIONS'); + $this->_send_request(); + $this->_get_respond(); + $response = $this->_process_respond(); + // validate the response ... + // check http-version + if ($response['status']['http-version'] == 'HTTP/1.1' || + $response['status']['http-version'] == 'HTTP/1.0') { + return $response; + } + $this->_error_log('Response was not even http'); + return false; + + } + + /** + * Public method mkcol + * + * Creates a new collection/directory on a webdav server + * @param string path + * @return int status code received as reponse from webdav server (see rfc 2518) + */ + function mkcol($path) { + $this->_path = $this->_translate_uri($path); + $this->_header_unset(); + $this->_create_basic_request('MKCOL'); + $this->_send_request(); + $this->_get_respond(); + $response = $this->_process_respond(); + // validate the response ... + // check http-version + if ($response['status']['http-version'] == 'HTTP/1.1' || + $response['status']['http-version'] == 'HTTP/1.0') { + /* seems to be http ... proceed + just return what server gave us + rfc 2518 says: + 201 (Created) - The collection or structured resource was created in its entirety. + 403 (Forbidden) - This indicates at least one of two conditions: 1) the server does not allow the creation of collections at the given + location in its namespace, or 2) the parent collection of the Request-URI exists but cannot accept members. + 405 (Method Not Allowed) - MKCOL can only be executed on a deleted/non-existent resource. + 409 (Conflict) - A collection cannot be made at the Request-URI until one or more intermediate collections have been created. + 415 (Unsupported Media Type)- The server does not support the request type of the body. + 507 (Insufficient Storage) - The resource does not have sufficient space to record the state of the resource after the execution of this method. + */ + return $response['status']['status-code']; + } + + } + + /** + * Public method get + * + * Gets a file from a webdav collection. + * @param string path, string &buffer + * @return status code and &$buffer (by reference) with response data from server on success. False on error. + */ + function get($path, &$buffer) { + $this->_path = $this->_translate_uri($path); + $this->_header_unset(); + $this->_create_basic_request('GET'); + $this->_send_request(); + $this->_get_respond(); + $response = $this->_process_respond(); + + // validate the response + // check http-version + if ($response['status']['http-version'] == 'HTTP/1.1' || + $response['status']['http-version'] == 'HTTP/1.0') { + // seems to be http ... proceed + // We expect a 200 code + if ($response['status']['status-code'] == 200 ) { + $this->_error_log('returning buffer with ' . strlen($response['body']) . ' bytes.'); + $buffer = $response['body']; + } + return $response['status']['status-code']; + } + // ups: no http status was returned ? + return false; + } + + /** + * Public method put + * + * Puts a file into a collection. + * Data is putted as one chunk! + * @param string path, string data + * @return int status-code read from webdavserver. False on error. + */ + function put($path, $data ) { + $this->_path = $this->_translate_uri($path); + $this->_header_unset(); + $this->_create_basic_request('PUT'); + // add more needed header information ... + $this->_header_add('Content-length: ' . strlen($data)); + $this->_header_add('Content-type: application/octet-stream'); + // send header + $this->_send_request(); + // send the rest (data) + fputs($this->_fp, $data); + $this->_get_respond(); + $response = $this->_process_respond(); + + // validate the response + // check http-version + if ($response['status']['http-version'] == 'HTTP/1.1' || + $response['status']['http-version'] == 'HTTP/1.0') { + // seems to be http ... proceed + // We expect a 200 or 204 status code + // see rfc 2068 - 9.6 PUT... + // print 'http ok
'; + return $response['status']['status-code']; + } + // ups: no http status was returned ? + return false; + } + + /** + * Public method put_file + * + * Read a file as stream and puts it chunk by chunk into webdav server collection. + * + * Look at php documenation for legal filenames with fopen(); + * The filename will be translated into utf-8 if not allready in utf-8. + * + * @param string targetpath, string filename + * @return int status code. False on error. + */ + function put_file($path, $filename) { + // try to open the file ... + + + $handle = @fopen ($filename, 'r'); + + if ($handle) { + // $this->_fp = pfsockopen ($this->_server, $this->_port, $this->_errno, $this->_errstr, $this->_socket_timeout); + $this->_path = $this->_translate_uri($path); + $this->_header_unset(); + $this->_create_basic_request('PUT'); + // add more needed header information ... + $this->_header_add('Content-length: ' . filesize($filename)); + $this->_header_add('Content-type: application/octet-stream'); + // send header + $this->_send_request(); + while (!feof($handle)) { + fputs($this->_fp,fgets($handle,4096)); + } + fclose($handle); + $this->_get_respond(); + $response = $this->_process_respond(); + + // validate the response + // check http-version + if ($response['status']['http-version'] == 'HTTP/1.1' || + $response['status']['http-version'] == 'HTTP/1.0') { + // seems to be http ... proceed + // We expect a 200 or 204 status code + // see rfc 2068 - 9.6 PUT... + // print 'http ok
'; + return $response['status']['status-code']; + } + // ups: no http status was returned ? + return false; + } else { + $this->_error_log('put_file: could not open ' . $filename); + return false; + } + + } + + /** + * Public method get_file + * + * Gets a file from a collection into local filesystem. + * + * fopen() is used. + * @param string srcpath, string localpath + * @return true on success. false on error. + */ + function get_file($srcpath, $localpath) { + + if ($this->get($srcpath, $buffer)) { + // convert utf-8 filename to iso-8859-1 + + $localpath = $this->_utf_decode_path($localpath); + + $handle = fopen ($localpath, 'w'); + if ($handle) { + fwrite($handle, $buffer); + fclose($handle); + return true; + } else { + return false; + } + } else { + return false; + } + } + + /** + * Public method copy_file + * + * Copies a file on a webdav server + * + * Duplicates a file on the webdav server (serverside). + * All work is done on the webdav server. If you set param overwrite as true, + * the target will be overwritten. + * + * @param string src_path, string dest_path, bool overwrite + * @return int status code (look at rfc 2518). false on error. + */ + function copy_file($src_path, $dst_path, $overwrite) { + $this->_path = $this->_translate_uri($src_path); + $this->_header_unset(); + $this->_create_basic_request('COPY'); + $this->_header_add(sprintf('Destination: http://%s%s', $this->_server, $this->_translate_uri($dst_path))); + if ($overwrite) { + $this->_header_add('Overwrite: T'); + } else { + $this->_header_add('Overwrite: F'); + } + $this->_header_add(''); + $this->_send_request(); + $this->_get_respond(); + $response = $this->_process_respond(); + // validate the response ... + // check http-version + if ($response['status']['http-version'] == 'HTTP/1.1' || + $response['status']['http-version'] == 'HTTP/1.0') { + /* seems to be http ... proceed + just return what server gave us (as defined in rfc 2518) : + 201 (Created) - The source resource was successfully copied. The copy operation resulted in the creation of a new resource. + 204 (No Content) - The source resource was successfully copied to a pre-existing destination resource. + 403 (Forbidden) - The source and destination URIs are the same. + 409 (Conflict) - A resource cannot be created at the destination until one or more intermediate collections have been created. + 412 (Precondition Failed) - The server was unable to maintain the liveness of the properties listed in the propertybehavior XML element + or the Overwrite header is "F" and the state of the destination resource is non-null. + 423 (Locked) - The destination resource was locked. + 502 (Bad Gateway) - This may occur when the destination is on another server and the destination server refuses to accept the resource. + 507 (Insufficient Storage) - The destination resource does not have sufficient space to record the state of the resource after the + execution of this method. + */ + return $response['status']['status-code']; + } + return false; + } + + /** + * Public method copy_coll + * + * Copies a collection on a webdav server + * + * Duplicates a collection on the webdav server (serverside). + * All work is done on the webdav server. If you set param overwrite as true, + * the target will be overwritten. + * + * @param string src_path, string dest_path, bool overwrite + * @return int status code (look at rfc 2518). false on error. + */ + function copy_coll($src_path, $dst_path, $overwrite) { + $this->_path = $this->_translate_uri($src_path); + $this->_header_unset(); + $this->_create_basic_request('COPY'); + $this->_header_add(sprintf('Destination: http://%s%s', $this->_server, $this->_translate_uri($dst_path))); + $this->_header_add('Depth: Infinity'); + + $xml = "\r\n"; + $xml .= "\r\n"; + $xml .= " *\r\n"; + $xml .= "\r\n"; + + $this->_header_add('Content-length: ' . strlen($xml)); + $this->_header_add('Content-type: application/xml'); + $this->_send_request(); + // send also xml + fputs($this->_fp, $xml); + $this->_get_respond(); + $response = $this->_process_respond(); + // validate the response ... + // check http-version + if ($response['status']['http-version'] == 'HTTP/1.1' || + $response['status']['http-version'] == 'HTTP/1.0') { + /* seems to be http ... proceed + just return what server gave us (as defined in rfc 2518) : + 201 (Created) - The source resource was successfully copied. The copy operation resulted in the creation of a new resource. + 204 (No Content) - The source resource was successfully copied to a pre-existing destination resource. + 403 (Forbidden) - The source and destination URIs are the same. + 409 (Conflict) - A resource cannot be created at the destination until one or more intermediate collections have been created. + 412 (Precondition Failed) - The server was unable to maintain the liveness of the properties listed in the propertybehavior XML element + or the Overwrite header is "F" and the state of the destination resource is non-null. + 423 (Locked) - The destination resource was locked. + 502 (Bad Gateway) - This may occur when the destination is on another server and the destination server refuses to accept the resource. + 507 (Insufficient Storage) - The destination resource does not have sufficient space to record the state of the resource after the + execution of this method. + */ + return $response['status']['status-code']; + } + return false; + } + + /** + * Public method move + * + * Moves a file or collection on webdav server (serverside) + * + * If you set param overwrite as true, the target will be overwritten. + * + * @param string src_path, string dest_path, bool overwrite + * @return int status code (look at rfc 2518). false on error. + */ + // -------------------------------------------------------------------------- + // public method move + // move/rename a file/collection on webdav server + function move($src_path,$dst_path, $overwrite) { + + $this->_path = $this->_translate_uri($src_path); + $this->_header_unset(); + + $this->_create_basic_request('MOVE'); + $this->_header_add(sprintf('Destination: http://%s%s', $this->_server, $this->_translate_uri($dst_path))); + if ($overwrite) { + $this->_header_add('Overwrite: T'); + } else { + $this->_header_add('Overwrite: F'); + } + $this->_header_add(''); + + $this->_send_request(); + $this->_get_respond(); + $response = $this->_process_respond(); + // validate the response ... + // check http-version + if ($response['status']['http-version'] == 'HTTP/1.1' || + $response['status']['http-version'] == 'HTTP/1.0') { + /* seems to be http ... proceed + just return what server gave us (as defined in rfc 2518) : + 201 (Created) - The source resource was successfully moved, and a new resource was created at the destination. + 204 (No Content) - The source resource was successfully moved to a pre-existing destination resource. + 403 (Forbidden) - The source and destination URIs are the same. + 409 (Conflict) - A resource cannot be created at the destination until one or more intermediate collections have been created. + 412 (Precondition Failed) - The server was unable to maintain the liveness of the properties listed in the propertybehavior XML element + or the Overwrite header is "F" and the state of the destination resource is non-null. + 423 (Locked) - The source or the destination resource was locked. + 502 (Bad Gateway) - This may occur when the destination is on another server and the destination server refuses to accept the resource. + + 201 (Created) - The collection or structured resource was created in its entirety. + 403 (Forbidden) - This indicates at least one of two conditions: 1) the server does not allow the creation of collections at the given + location in its namespace, or 2) the parent collection of the Request-URI exists but cannot accept members. + 405 (Method Not Allowed) - MKCOL can only be executed on a deleted/non-existent resource. + 409 (Conflict) - A collection cannot be made at the Request-URI until one or more intermediate collections have been created. + 415 (Unsupported Media Type)- The server does not support the request type of the body. + 507 (Insufficient Storage) - The resource does not have sufficient space to record the state of the resource after the execution of this method. + */ + return $response['status']['status-code']; + } + return false; + } + + /** + * Public method lock + * + * Locks a file or collection. + * + * Lock uses this->_user as lock owner. + * + * @param string path + * @return int status code (look at rfc 2518). false on error. + */ + function lock($path) { + $this->_path = $this->_translate_uri($path); + $this->_header_unset(); + $this->_create_basic_request('LOCK'); + $this->_header_add('Timeout: Infinite'); + $this->_header_add('Content-type: text/xml'); + // create the xml request ... + $xml = "\r\n"; + $xml .= ""; + $xml .= " \r\n"; + $xml .= " \r\n"; + $xml .= " \r\n"; + $xml .= " ".($this->_user)."\r\n"; + $xml .= " \r\n"; + $xml .= "\r\n"; + $this->_header_add('Content-length: ' . strlen($xml)); + $this->_send_request(); + // send also xml + fputs($this->_fp, $xml); + $this->_get_respond(); + $response = $this->_process_respond(); + // validate the response ... (only basic validation) + // check http-version + if ($response['status']['http-version'] == 'HTTP/1.1' || + $response['status']['http-version'] == 'HTTP/1.0') { + /* seems to be http ... proceed + rfc 2518 says: + 200 (OK) - The lock request succeeded and the value of the lockdiscovery property is included in the body. + 412 (Precondition Failed) - The included lock token was not enforceable on this resource or the server could not satisfy the + request in the lockinfo XML element. + 423 (Locked) - The resource is locked, so the method has been rejected. + */ + + switch($response['status']['status-code']) { + case 200: + // collection was successfully locked... see xml response to get lock token... + if (strcmp($response['header']['Content-Type'], 'text/xml; charset="utf-8"') == 0) { + // ok let's get the content of the xml stuff + $this->_parser = xml_parser_create_ns(); + // forget old data... + unset($this->_lock[$this->_parser]); + unset($this->_xmltree[$this->_parser]); + xml_parser_set_option($this->_parser,XML_OPTION_SKIP_WHITE,0); + xml_parser_set_option($this->_parser,XML_OPTION_CASE_FOLDING,0); + xml_set_object($this->_parser, $this); + xml_set_element_handler($this->_parser, "_lock_startElement", "_endElement"); + xml_set_character_data_handler($this->_parser, "_lock_cdata"); + + if (!xml_parse($this->_parser, $response['body'])) { + die(sprintf("XML error: %s at line %d", + xml_error_string(xml_get_error_code($this->_parser)), + xml_get_current_line_number($this->_parser))); + } + + // Free resources + xml_parser_free($this->_parser); + // add status code to array + $this->_lock[$this->_parser]['status'] = 200; + return $this->_lock[$this->_parser]; + + } else { + print 'Missing Content-Type: text/xml header in response.
'; + } + return false; + + default: + // hmm. not what we expected. Just return what we got from webdav server + // someone else has to handle it. + $this->_lock['status'] = $response['status']['status-code']; + return $this->_lock; + } + } + + + } + + + /** + * Public method unlock + * + * Unlocks a file or collection. + * + * @param string path, string locktoken + * @return int status code (look at rfc 2518). false on error. + */ + function unlock($path, $locktoken) { + $this->_path = $this->_translate_uri($path); + $this->_header_unset(); + $this->_create_basic_request('UNLOCK'); + $this->_header_add(sprintf('Lock-Token: <%s>', $locktoken)); + $this->_send_request(); + $this->_get_respond(); + $response = $this->_process_respond(); + if ($response['status']['http-version'] == 'HTTP/1.1' || + $response['status']['http-version'] == 'HTTP/1.0') { + /* seems to be http ... proceed + rfc 2518 says: + 204 (OK) - The 204 (No Content) status code is used instead of 200 (OK) because there is no response entity body. + */ + return $response['status']['status-code']; + } + return false; + } + + /** + * Public method delete + * + * deletes a collection/directory on a webdav server + * @param string path + * @return int status code (look at rfc 2518). false on error. + */ + function delete($path) { + $this->_path = $this->_translate_uri($path); + $this->_header_unset(); + $this->_create_basic_request('DELETE'); + /* $this->_header_add('Content-Length: 0'); */ + $this->_header_add(''); + $this->_send_request(); + $this->_get_respond(); + $response = $this->_process_respond(); + + // validate the response ... + // check http-version + if ($response['status']['http-version'] == 'HTTP/1.1' || + $response['status']['http-version'] == 'HTTP/1.0') { + // seems to be http ... proceed + // We expect a 207 Multi-Status status code + // print 'http ok
'; + + switch ($response['status']['status-code']) { + case 207: + // collection was NOT deleted... see xml response for reason... + // next there should be a Content-Type: text/xml; charset="utf-8" header line + if (strcmp($response['header']['Content-Type'], 'text/xml; charset="utf-8"') == 0) { + // ok let's get the content of the xml stuff + $this->_parser = xml_parser_create_ns(); + // forget old data... + unset($this->_delete[$this->_parser]); + unset($this->_xmltree[$this->_parser]); + xml_parser_set_option($this->_parser,XML_OPTION_SKIP_WHITE,0); + xml_parser_set_option($this->_parser,XML_OPTION_CASE_FOLDING,0); + xml_set_object($this->_parser, $this); + xml_set_element_handler($this->_parser, "_delete_startElement", "_endElement"); + xml_set_character_data_handler($this->_parser, "_delete_cdata"); + + if (!xml_parse($this->_parser, $response['body'])) { + die(sprintf("XML error: %s at line %d", + xml_error_string(xml_get_error_code($this->_parser)), + xml_get_current_line_number($this->_parser))); + } + + print "
"; + + // Free resources + xml_parser_free($this->_parser); + $this->_delete[$this->_parser]['status'] = $response['status']['status-code']; + return $this->_delete[$this->_parser]; + + } else { + print 'Missing Content-Type: text/xml header in response.
'; + } + return false; + + default: + // collection or file was successfully deleted + $this->_delete['status'] = $response['status']['status-code']; + return $this->_delete; + + + } + } + + } + + /** + * Public method ls + * + * Get's directory information from webdav server into flat a array using PROPFIND + * + * All filenames are UTF-8 encoded. + * Have a look at _propfind_startElement what keys are used in array returned. + * @param string path + * @return array dirinfo, false on error + */ + function ls($path) { + + if (trim($path) == '') { + $this->_error_log('Missing a path in method ls'); + return false; + } + $this->_path = $this->_translate_uri($path); + + $this->_header_unset(); + $this->_create_basic_request('PROPFIND'); + $this->_header_add('Depth: 1'); + $this->_header_add('Content-type: application/xml'); + // create profind xml request... + $xml = << + + + + + + + + +EOD; + $this->_header_add('Content-length: ' . strlen($xml)); + $this->_send_request(); + $this->_error_log($xml); + fputs($this->_fp, $xml); + $this->_get_respond(); + $response = $this->_process_respond(); + // validate the response ... (only basic validation) + // check http-version + if ($response['status']['http-version'] == 'HTTP/1.1' || + $response['status']['http-version'] == 'HTTP/1.0') { + // seems to be http ... proceed + // We expect a 207 Multi-Status status code + // print 'http ok
'; + if (strcmp($response['status']['status-code'],'207') == 0 ) { + // ok so far + // next there should be a Content-Type: text/xml; charset="utf-8" header line + if (preg_match('#text/xml;\s?charset=[\'\"]?utf-8[\'\"]?#i', $response['header']['Content-Type'])) { + // ok let's get the content of the xml stuff + $this->_parser = xml_parser_create_ns('UTF-8'); + // forget old data... + unset($this->_ls[$this->_parser]); + unset($this->_xmltree[$this->_parser]); + xml_parser_set_option($this->_parser,XML_OPTION_SKIP_WHITE,0); + xml_parser_set_option($this->_parser,XML_OPTION_CASE_FOLDING,0); + // xml_parser_set_option($this->_parser,XML_OPTION_TARGET_ENCODING,'UTF-8'); + xml_set_object($this->_parser, $this); + xml_set_element_handler($this->_parser, "_propfind_startElement", "_endElement"); + xml_set_character_data_handler($this->_parser, "_propfind_cdata"); + + + if (!xml_parse($this->_parser, $response['body'])) { + die(sprintf("XML error: %s at line %d", + xml_error_string(xml_get_error_code($this->_parser)), + xml_get_current_line_number($this->_parser))); + } + + // Free resources + xml_parser_free($this->_parser); + $arr = $this->_ls[$this->_parser]; + return $arr; + } else { + $this->_error_log('Missing Content-Type: text/xml header in response!!'); + return false; + } + } else { + // return status code ... + return $response['status']['status-code']; + } + } + + // response was not http + $this->_error_log('Ups in method ls: error in response from server'); + return false; + } + + + /** + * Public method gpi + * + * Get's path information from webdav server for one element. + * + * @param string path + * @return array dirinfo. false on error + */ + function gpi($path) { + + // split path by last "/" + $path = rtrim($path, "/"); + $item = basename($path); + $dir = dirname($path); + + $list = $this->ls($dir); + + // be sure it is an array + if (is_array($list)) { + foreach($list as $e) { + + $fullpath = urldecode($e['href']); + $filename = basename($fullpath); + + if ($filename == $item && $filename != "" and $fullpath != $dir."/") { + return $e; + } + } + } + return false; + } + + /** + * Public method is_file + * + * Gathers whether a path points to a file or not. + * + * @param string path + * @return bool true or false + */ + function is_file($path) { + + $item = $this->gpi($path); + + if ($item === false) { + return false; + } else { + return ($item['resourcetype'] != 'collection'); + } + } + + /** + * Public method is_dir + * + * Gather whether a path points to a directory + * @param string path + * return bool true or false + */ + function is_dir($path) { + + // be sure path is utf-8 + $item = $this->gpi($path); + + if ($item === false) { + return false; + } else { + return ($item['resourcetype'] == 'collection'); + } + } + + + /** + * Public method mput + * + * Puts multiple files and/or directories onto a webdav server. + * + * Filenames should be allready UTF-8 encoded. + * Param fileList must be in format array("localpath" => "destpath"). + * + * @param array filelist + * @return bool true on success. otherwise int status code on error + */ + function mput($filelist) { + + $result = true; + + while (list($localpath, $destpath) = each($filelist)) { + + $localpath = rtrim($localpath, "/"); + $destpath = rtrim($destpath, "/"); + + // attempt to create target path + if (is_dir($localpath)) { + $pathparts = explode("/", $destpath."/ "); // add one level, last level will be created as dir + } else { + $pathparts = explode("/", $destpath); + } + $checkpath = ""; + for ($i=1; $iis_dir($checkpath))) { + + $result &= ($this->mkcol($checkpath) == 201 ); + } + } + + if ($result) { + // recurse directories + if (is_dir($localpath)) { + $dp = opendir($localpath); + $fl = array(); + while($filename = readdir($dp)) { + if ((is_file($localpath."/".$filename) || is_dir($localpath."/".$filename)) && $filename!="." && $filename != "..") { + $fl[$localpath."/".$filename] = $destpath."/".$filename; + } + } + $result &= $this->mput($fl); + } else { + $result &= ($this->put_file($destpath, $localpath) == 201); + } + } + } + return $result; + } + + /** + * Public method mget + * + * Gets multiple files and directories. + * + * FileList must be in format array("remotepath" => "localpath"). + * Filenames are UTF-8 encoded. + * + * @param array filelist + * @return bool true on succes, other int status code on error + */ + function mget($filelist) { + + $result = true; + + while (list($remotepath, $localpath) = each($filelist)) { + + $localpath = rtrim($localpath, "/"); + $remotepath = rtrim($remotepath, "/"); + + // attempt to create local path + if ($this->is_dir($remotepath)) { + $pathparts = explode("/", $localpath."/ "); // add one level, last level will be created as dir + } else { + $pathparts = explode("/", $localpath); + } + $checkpath = ""; + for ($i=1; $iis_dir($remotepath)) { + $list = $this->ls($remotepath); + + $fl = array(); + foreach($list as $e) { + $fullpath = urldecode($e['href']); + $filename = basename($fullpath); + if ($filename != '' and $fullpath != $remotepath . '/') { + $fl[$remotepath."/".$filename] = $localpath."/".$filename; + } + } + $result &= $this->mget($fl); + } else { + $result &= ($this->get_file($remotepath, $localpath)); + } + } + } + return $result; + } + + // -------------------------------------------------------------------------- + // private xml callback and helper functions starting here + // -------------------------------------------------------------------------- + + + /** + * Private method _endelement + * + * a generic endElement method (used for all xml callbacks). + * + * @param resource parser, string name + * @access private + */ + + private function _endElement($parser, $name) { + // end tag was found... + $this->_xmltree[$parser] = substr($this->_xmltree[$parser],0, strlen($this->_xmltree[$parser]) - (strlen($name) + 1)); + } + + /** + * Private method _propfind_startElement + * + * Is needed by public method ls. + * + * Generic method will called by php xml_parse when a xml start element tag has been detected. + * The xml tree will translated into a flat php array for easier access. + * @param resource parser, string name, string attrs + * @access private + */ + private function _propfind_startElement($parser, $name, $attrs) { + // lower XML Names... maybe break a RFC, don't know ... + + $propname = strtolower($name); if (!empty($this->_xmltree[$parser])) { $this->_xmltree[$parser] .= $propname . '_'; } else { $this->_xmltree[$parser] = $propname . '_'; } - - // translate xml tree to a flat array ... - switch($this->_xmltree[$parser]) { - case 'dav::multistatus_dav::response_': - // new element in mu - $this->_ls_ref =& $this->_ls[$parser][]; - break; - case 'dav::multistatus_dav::response_dav::href_': - $this->_ls_ref_cdata = &$this->_ls_ref['href']; - break; - case 'dav::multistatus_dav::response_dav::propstat_dav::prop_dav::creationdate_': - $this->_ls_ref_cdata = &$this->_ls_ref['creationdate']; - break; - case 'dav::multistatus_dav::response_dav::propstat_dav::prop_dav::getlastmodified_': - $this->_ls_ref_cdata = &$this->_ls_ref['lastmodified']; - break; - case 'dav::multistatus_dav::response_dav::propstat_dav::prop_dav::getcontenttype_': - $this->_ls_ref_cdata = &$this->_ls_ref['getcontenttype']; - break; - case 'dav::multistatus_dav::response_dav::propstat_dav::prop_dav::getcontentlength_': - $this->_ls_ref_cdata = &$this->_ls_ref['getcontentlength']; - break; - case 'dav::multistatus_dav::response_dav::propstat_dav::prop_dav::lockdiscovery_dav::activelock_dav::depth_': - $this->_ls_ref_cdata = &$this->_ls_ref['activelock_depth']; - break; - case 'dav::multistatus_dav::response_dav::propstat_dav::prop_dav::lockdiscovery_dav::activelock_dav::owner_dav::href_': - $this->_ls_ref_cdata = &$this->_ls_ref['activelock_owner']; - break; - case 'dav::multistatus_dav::response_dav::propstat_dav::prop_dav::lockdiscovery_dav::activelock_dav::owner_': - $this->_ls_ref_cdata = &$this->_ls_ref['activelock_owner']; - break; - case 'dav::multistatus_dav::response_dav::propstat_dav::prop_dav::lockdiscovery_dav::activelock_dav::timeout_': - $this->_ls_ref_cdata = &$this->_ls_ref['activelock_timeout']; - break; - case 'dav::multistatus_dav::response_dav::propstat_dav::prop_dav::lockdiscovery_dav::activelock_dav::locktoken_dav::href_': - $this->_ls_ref_cdata = &$this->_ls_ref['activelock_token']; - break; - case 'dav::multistatus_dav::response_dav::propstat_dav::prop_dav::lockdiscovery_dav::activelock_dav::locktype_dav::write_': - $this->_ls_ref_cdata = &$this->_ls_ref['activelock_type']; - $this->_ls_ref_cdata = 'write'; - $this->_ls_ref_cdata = &$this->_null; - break; - case 'dav::multistatus_dav::response_dav::propstat_dav::prop_dav::resourcetype_dav::collection_': - $this->_ls_ref_cdata = &$this->_ls_ref['resourcetype']; - $this->_ls_ref_cdata = 'collection'; - $this->_ls_ref_cdata = &$this->_null; - break; - case 'dav::multistatus_dav::response_dav::propstat_dav::status_': - $this->_ls_ref_cdata = &$this->_ls_ref['status']; - break; - - default: - // handle unknown xml elements... - $this->_ls_ref_cdata = &$this->_ls_ref[$this->_xmltree[$parser]]; - } - } - - /** - * Private method _propfind_cData - * - * Is needed by public method ls. - * - * Will be called by php xml_set_character_data_handler() when xml data has to be handled. - * Stores data found into class var _ls_ref_cdata - * @param resource parser, string cdata - * @access private - */ - function _propfind_cData($parser, $cdata) { - if (trim($cdata) <> '') { - // cdata must be appended, because sometimes the php xml parser makes multiple calls - // to _propfind_cData before the xml end tag was reached... - $this->_ls_ref_cdata .= $cdata; - } else { - // do nothing - } - } - - /** - * Private method _delete_startElement - * - * Is used by public method delete. - * - * Will be called by php xml_parse. - * @param resource parser, string name, string attrs) - * @access private - */ - function _delete_startElement($parser, $name, $attrs) { - // lower XML Names... maybe break a RFC, don't know ... - $propname = strtolower($name); - $this->_xmltree[$parser] .= $propname . '_'; - - // translate xml tree to a flat array ... - switch($this->_xmltree[$parser]) { - case 'dav::multistatus_dav::response_': - // new element in mu - $this->_delete_ref =& $this->_delete[$parser][]; - break; - case 'dav::multistatus_dav::response_dav::href_': - $this->_delete_ref_cdata = &$this->_ls_ref['href']; - break; - - default: - // handle unknown xml elements... - $this->_delete_cdata = &$this->_delete_ref[$this->_xmltree[$parser]]; - } - } - - - /** - * Private method _delete_cData - * - * Is used by public method delete. - * - * Will be called by php xml_set_character_data_handler() when xml data has to be handled. - * Stores data found into class var _delete_ref_cdata - * @param resource parser, string cdata - * @access private - */ - function _delete_cData($parser, $cdata) { - if (trim($cdata) <> '') { - $this->_delete_ref_cdata .= $cdata; - } else { - // do nothing - } - } - - -/** - * Private method _lock_startElement - * - * Is needed by public method lock. - * - * Mmethod will called by php xml_parse when a xml start element tag has been detected. - * The xml tree will translated into a flat php array for easier access. - * @param resource parser, string name, string attrs - * @access private - */ - function _lock_startElement($parser, $name, $attrs) { - // lower XML Names... maybe break a RFC, don't know ... - $propname = strtolower($name); - $this->_xmltree[$parser] .= $propname . '_'; - - // translate xml tree to a flat array ... - /* - dav::prop_dav::lockdiscovery_dav::activelock_dav::depth_= - dav::prop_dav::lockdiscovery_dav::activelock_dav::owner_dav::href_= - dav::prop_dav::lockdiscovery_dav::activelock_dav::timeout_= - dav::prop_dav::lockdiscovery_dav::activelock_dav::locktoken_dav::href_= - */ - switch($this->_xmltree[$parser]) { - case 'dav::prop_dav::lockdiscovery_dav::activelock_': - // new element - $this->_lock_ref =& $this->_lock[$parser][]; - break; - case 'dav::prop_dav::lockdiscovery_dav::activelock_dav::locktype_dav::write_': - $this->_lock_ref_cdata = &$this->_lock_ref['locktype']; - $this->_lock_cdata = 'write'; - $this->_lock_cdata = &$this->_null; - break; - case 'dav::prop_dav::lockdiscovery_dav::activelock_dav::lockscope_dav::exclusive_': - $this->_lock_ref_cdata = &$this->_lock_ref['lockscope']; - $this->_lock_ref_cdata = 'exclusive'; - $this->_lock_ref_cdata = &$this->_null; - break; - case 'dav::prop_dav::lockdiscovery_dav::activelock_dav::depth_': - $this->_lock_ref_cdata = &$this->_lock_ref['depth']; - break; - case 'dav::prop_dav::lockdiscovery_dav::activelock_dav::owner_dav::href_': - $this->_lock_ref_cdata = &$this->_lock_ref['owner']; - break; - case 'dav::prop_dav::lockdiscovery_dav::activelock_dav::timeout_': - $this->_lock_ref_cdata = &$this->_lock_ref['timeout']; - break; - case 'dav::prop_dav::lockdiscovery_dav::activelock_dav::locktoken_dav::href_': - $this->_lock_ref_cdata = &$this->_lock_ref['locktoken']; - break; - default: - // handle unknown xml elements... - $this->_lock_cdata = &$this->_lock_ref[$this->_xmltree[$parser]]; - - } - } - - /** - * Private method _lock_cData - * - * Is used by public method lock. - * - * Will be called by php xml_set_character_data_handler() when xml data has to be handled. - * Stores data found into class var _lock_ref_cdata - * @param resource parser, string cdata - * @access private - */ - function _lock_cData($parser, $cdata) { - if (trim($cdata) <> '') { - // $this->_error_log(($this->_xmltree[$parser]) . '='. htmlentities($cdata)); - $this->_lock_ref_cdata .= $cdata; - } else { - // do nothing - } - } - - - /** - * Private method _header_add - * - * extends class var array _req - * @param string string - * @access private - */ - function _header_add($string) { - $this->_req[] = $string; - } - - /** - * Private method _header_unset - * - * unsets class var array _req - * @access private - */ - - function _header_unset() { - unset($this->_req); - } - - /** - * Private method _create_basic_request - * - * creates by using private method _header_add an general request header. - * @param string method - * @access private - */ - function _create_basic_request($method) { - $request = ''; - $this->_header_add(sprintf('%s %s %s', $method, $this->_path, $this->_protocol)); - $this->_header_add(sprintf('Host: %s', $this->_server)); - // $request .= sprintf('Connection: Keep-Alive'); - $this->_header_add(sprintf('User-Agent: %s', $this->_user_agent)); - $this->_header_add(sprintf('Authorization: Basic %s', base64_encode("$this->_user:$this->_pass"))); - } - - /** - * Private method _send_request - * - * Sends a ready formed http/webdav request to webdav server. - * - * @access private - */ - function _send_request() { - // check if stream is declared to be open - // only logical check we are not sure if socket is really still open ... - if ($this->_connection_closed) { - // reopen it - // be sure to close the open socket. - $this->close(); - $this->_reopen(); - } - - // convert array to string - $buffer = implode("\r\n", $this->_req); - $buffer .= "\r\n\r\n"; - $this->_error_log($buffer); - fputs($this->_fp, $buffer); - } - - /** - * Private method _get_respond - * - * Reads the reponse from the webdav server. - * - * Stores data into class vars _header for the header data and - * _body for the rest of the response. - * This routine is the weakest part of this class, because it very depends how php does handle a socket stream. - * If the stream is blocked for some reason php is blocked as well. - * @access private - */ - function _get_respond() { - $this->_error_log('_get_respond()'); - // init vars (good coding style ;-) - $buffer = ''; - $header = ''; - // attention: do not make max_chunk_size to big.... - $max_chunk_size = 8192; - // be sure we got a open ressource - if (! $this->_fp) { - $this->_error_log('socket is not open. Can not process response'); - return false; - } - - // following code maybe helps to improve socket behaviour ... more testing needed - // disabled at the moment ... - // socket_set_timeout($this->_fp,1 ); - // $socket_state = socket_get_status($this->_fp); - - // read stream one byte by another until http header ends - $i = 0; - do { - $header.=fread($this->_fp,1); - $i++; - } while (!preg_match('/\\r\\n\\r\\n$/',$header) && $i < $this->_maxheaderlenth); - - $this->_error_log($header); - - if (preg_match('/Connection: close\\r\\n/', $header)) { - // This says that the server will close connection at the end of this stream. - // Therefore we need to reopen the socket, before are sending the next request... - $this->_error_log('Connection: close found'); - $this->_connection_closed = true; - } - // check how to get the data on socket stream - // chunked or content-length (HTTP/1.1) or - // one block until feof is received (HTTP/1.0) - switch(true) { - case (preg_match('/Transfer\\-Encoding:\\s+chunked\\r\\n/',$header)): - $this->_error_log('Getting HTTP/1.1 chunked data...'); - do { - $byte = ''; - $chunk_size=''; - do { - $chunk_size.=$byte; - $byte=fread($this->_fp,1); - // check what happens while reading, because I do not really understand how php reads the socketstream... - // but so far - it seems to work here - tested with php v4.3.1 on apache 1.3.27 and Debian Linux 3.0 ... - if (strlen($byte) == 0) { - $this->_error_log('_get_respond: warning --> read zero bytes'); - } - } while ($byte!="\r" and strlen($byte)>0); // till we match the Carriage Return - fread($this->_fp, 1); // also drop off the Line Feed - $chunk_size=hexdec($chunk_size); // convert to a number in decimal system - if ($chunk_size > 0) { - $buffer .= fread($this->_fp,$chunk_size); - } - fread($this->_fp,2); // ditch the CRLF that trails the chunk - } while ($chunk_size); // till we reach the 0 length chunk (end marker) - break; - - // check for a specified content-length - case preg_match('/Content\\-Length:\\s+([0-9]*)\\r\\n/',$header,$matches): - $this->_error_log('Getting data using Content-Length '. $matches[1]); - // check if we the content data size is small enough to get it as one block - if ($matches[1] <= $max_chunk_size ) { - // only read something if Content-Length is bigger than 0 - if ($matches[1] > 0 ) { - $buffer = fread($this->_fp, $matches[1]); - } else { - $buffer = ''; - } - } else { - // data is to big to handle it as one. Get it chunk per chunk... - do { - $mod = $max_chunk_size % ($matches[1] - strlen($buffer)); - $chunk_size = ($mod == $max_chunk_size ? $max_chunk_size : $matches[1] - strlen($buffer)); - $buffer .= fread($this->_fp, $chunk_size); - $this->_error_log('mod: ' . $mod . ' chunk: ' . $chunk_size . ' total: ' . strlen($buffer)); - } while ($mod == $max_chunk_size); - } - break; - - // check for 204 No Content - // 204 responds have no body. - // Therefore we do not need to read any data from socket stream. - case preg_match('/HTTP\/1\.1\ 204/',$header): - // nothing to do, just proceed - $this->_error_log('204 No Content found. No further data to read..'); - break; - default: - // just get the data until foef appears... - $this->_error_log('reading until feof...' . $header); - socket_set_timeout($this->_fp,0 ); - while (!feof($this->_fp)) { - $buffer .= fread($this->_fp, 4096); - } - // renew the socket timeout...does it do something ???? Is it needed. More debugging needed... - socket_set_timeout($this->_fp, $this->_socket_timeout); - } - - $this->_header = $header; - $this->_body = $buffer; - // $this->_buffer = $header . "\r\n\r\n" . $buffer; - $this->_error_log($this->_header); - $this->_error_log($this->_body); - } - - - /** - * Private method _process_respond - * - * Processes the webdav server respond and detects its components (header, body). - * and returns data array structure. - * @return array ret_struct - * @access private - */ - function _process_respond() { - $lines = explode("\r\n", $this->_header); - $header_done = false; - // $this->_error_log($this->_buffer); - // First line should be a HTTP status line (see http://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html#sec6) - // Format is: HTTP-Version SP Status-Code SP Reason-Phrase CRLF - list($ret_struct['status']['http-version'], - $ret_struct['status']['status-code'], - $ret_struct['status']['reason-phrase']) = explode(' ', $lines[0],3); - - // print "HTTP Version: '$http_version' Status-Code: '$status_code' Reason Phrase: '$reason_phrase'
"; - // get the response header fields - // See http://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html#sec6 - for($i=1; $i"; - - } - if (!$header_done ) { - // store all found headers in array ... - list($fieldname, $fieldvalue) = explode(':', $lines[$i]); - // check if this header was allready set (apache 2.0 webdav module does this....). - // If so we add the the value to the end the fieldvalue, separated by comma... + + // translate xml tree to a flat array ... + switch($this->_xmltree[$parser]) { + case 'dav::multistatus_dav::response_': + // new element in mu + $this->_ls_ref =& $this->_ls[$parser][]; + break; + case 'dav::multistatus_dav::response_dav::href_': + $this->_ls_ref_cdata = &$this->_ls_ref['href']; + break; + case 'dav::multistatus_dav::response_dav::propstat_dav::prop_dav::creationdate_': + $this->_ls_ref_cdata = &$this->_ls_ref['creationdate']; + break; + case 'dav::multistatus_dav::response_dav::propstat_dav::prop_dav::getlastmodified_': + $this->_ls_ref_cdata = &$this->_ls_ref['lastmodified']; + break; + case 'dav::multistatus_dav::response_dav::propstat_dav::prop_dav::getcontenttype_': + $this->_ls_ref_cdata = &$this->_ls_ref['getcontenttype']; + break; + case 'dav::multistatus_dav::response_dav::propstat_dav::prop_dav::getcontentlength_': + $this->_ls_ref_cdata = &$this->_ls_ref['getcontentlength']; + break; + case 'dav::multistatus_dav::response_dav::propstat_dav::prop_dav::lockdiscovery_dav::activelock_dav::depth_': + $this->_ls_ref_cdata = &$this->_ls_ref['activelock_depth']; + break; + case 'dav::multistatus_dav::response_dav::propstat_dav::prop_dav::lockdiscovery_dav::activelock_dav::owner_dav::href_': + $this->_ls_ref_cdata = &$this->_ls_ref['activelock_owner']; + break; + case 'dav::multistatus_dav::response_dav::propstat_dav::prop_dav::lockdiscovery_dav::activelock_dav::owner_': + $this->_ls_ref_cdata = &$this->_ls_ref['activelock_owner']; + break; + case 'dav::multistatus_dav::response_dav::propstat_dav::prop_dav::lockdiscovery_dav::activelock_dav::timeout_': + $this->_ls_ref_cdata = &$this->_ls_ref['activelock_timeout']; + break; + case 'dav::multistatus_dav::response_dav::propstat_dav::prop_dav::lockdiscovery_dav::activelock_dav::locktoken_dav::href_': + $this->_ls_ref_cdata = &$this->_ls_ref['activelock_token']; + break; + case 'dav::multistatus_dav::response_dav::propstat_dav::prop_dav::lockdiscovery_dav::activelock_dav::locktype_dav::write_': + $this->_ls_ref_cdata = &$this->_ls_ref['activelock_type']; + $this->_ls_ref_cdata = 'write'; + $this->_ls_ref_cdata = &$this->_null; + break; + case 'dav::multistatus_dav::response_dav::propstat_dav::prop_dav::resourcetype_dav::collection_': + $this->_ls_ref_cdata = &$this->_ls_ref['resourcetype']; + $this->_ls_ref_cdata = 'collection'; + $this->_ls_ref_cdata = &$this->_null; + break; + case 'dav::multistatus_dav::response_dav::propstat_dav::status_': + $this->_ls_ref_cdata = &$this->_ls_ref['status']; + break; + + default: + // handle unknown xml elements... + $this->_ls_ref_cdata = &$this->_ls_ref[$this->_xmltree[$parser]]; + } + } + + /** + * Private method _propfind_cData + * + * Is needed by public method ls. + * + * Will be called by php xml_set_character_data_handler() when xml data has to be handled. + * Stores data found into class var _ls_ref_cdata + * @param resource parser, string cdata + * @access private + */ + private function _propfind_cData($parser, $cdata) { + if (trim($cdata) <> '') { + // cdata must be appended, because sometimes the php xml parser makes multiple calls + // to _propfind_cData before the xml end tag was reached... + $this->_ls_ref_cdata .= $cdata; + } else { + // do nothing + } + } + + /** + * Private method _delete_startElement + * + * Is used by public method delete. + * + * Will be called by php xml_parse. + * @param resource parser, string name, string attrs) + * @access private + */ + private function _delete_startElement($parser, $name, $attrs) { + // lower XML Names... maybe break a RFC, don't know ... + $propname = strtolower($name); + $this->_xmltree[$parser] .= $propname . '_'; + + // translate xml tree to a flat array ... + switch($this->_xmltree[$parser]) { + case 'dav::multistatus_dav::response_': + // new element in mu + $this->_delete_ref =& $this->_delete[$parser][]; + break; + case 'dav::multistatus_dav::response_dav::href_': + $this->_delete_ref_cdata = &$this->_ls_ref['href']; + break; + + default: + // handle unknown xml elements... + $this->_delete_cdata = &$this->_delete_ref[$this->_xmltree[$parser]]; + } + } + + + /** + * Private method _delete_cData + * + * Is used by public method delete. + * + * Will be called by php xml_set_character_data_handler() when xml data has to be handled. + * Stores data found into class var _delete_ref_cdata + * @param resource parser, string cdata + * @access private + */ + private function _delete_cData($parser, $cdata) { + if (trim($cdata) <> '') { + $this->_delete_ref_cdata .= $cdata; + } else { + // do nothing + } + } + + + /** + * Private method _lock_startElement + * + * Is needed by public method lock. + * + * Mmethod will called by php xml_parse when a xml start element tag has been detected. + * The xml tree will translated into a flat php array for easier access. + * @param resource parser, string name, string attrs + * @access private + */ + private function _lock_startElement($parser, $name, $attrs) { + // lower XML Names... maybe break a RFC, don't know ... + $propname = strtolower($name); + $this->_xmltree[$parser] .= $propname . '_'; + + // translate xml tree to a flat array ... + /* + dav::prop_dav::lockdiscovery_dav::activelock_dav::depth_= + dav::prop_dav::lockdiscovery_dav::activelock_dav::owner_dav::href_= + dav::prop_dav::lockdiscovery_dav::activelock_dav::timeout_= + dav::prop_dav::lockdiscovery_dav::activelock_dav::locktoken_dav::href_= + */ + switch($this->_xmltree[$parser]) { + case 'dav::prop_dav::lockdiscovery_dav::activelock_': + // new element + $this->_lock_ref =& $this->_lock[$parser][]; + break; + case 'dav::prop_dav::lockdiscovery_dav::activelock_dav::locktype_dav::write_': + $this->_lock_ref_cdata = &$this->_lock_ref['locktype']; + $this->_lock_cdata = 'write'; + $this->_lock_cdata = &$this->_null; + break; + case 'dav::prop_dav::lockdiscovery_dav::activelock_dav::lockscope_dav::exclusive_': + $this->_lock_ref_cdata = &$this->_lock_ref['lockscope']; + $this->_lock_ref_cdata = 'exclusive'; + $this->_lock_ref_cdata = &$this->_null; + break; + case 'dav::prop_dav::lockdiscovery_dav::activelock_dav::depth_': + $this->_lock_ref_cdata = &$this->_lock_ref['depth']; + break; + case 'dav::prop_dav::lockdiscovery_dav::activelock_dav::owner_dav::href_': + $this->_lock_ref_cdata = &$this->_lock_ref['owner']; + break; + case 'dav::prop_dav::lockdiscovery_dav::activelock_dav::timeout_': + $this->_lock_ref_cdata = &$this->_lock_ref['timeout']; + break; + case 'dav::prop_dav::lockdiscovery_dav::activelock_dav::locktoken_dav::href_': + $this->_lock_ref_cdata = &$this->_lock_ref['locktoken']; + break; + default: + // handle unknown xml elements... + $this->_lock_cdata = &$this->_lock_ref[$this->_xmltree[$parser]]; + + } + } + + /** + * Private method _lock_cData + * + * Is used by public method lock. + * + * Will be called by php xml_set_character_data_handler() when xml data has to be handled. + * Stores data found into class var _lock_ref_cdata + * @param resource parser, string cdata + * @access private + */ + private function _lock_cData($parser, $cdata) { + if (trim($cdata) <> '') { + // $this->_error_log(($this->_xmltree[$parser]) . '='. htmlentities($cdata)); + $this->_lock_ref_cdata .= $cdata; + } else { + // do nothing + } + } + + + /** + * Private method _header_add + * + * extends class var array _req + * @param string string + * @access private + */ + private function _header_add($string) { + $this->_req[] = $string; + } + + /** + * Private method _header_unset + * + * unsets class var array _req + * @access private + */ + + private function _header_unset() { + unset($this->_req); + } + + /** + * Private method _create_basic_request + * + * creates by using private method _header_add an general request header. + * @param string method + * @access private + */ + private function _create_basic_request($method) { + $request = ''; + $this->_header_add(sprintf('%s %s %s', $method, $this->_path, $this->_protocol)); + $this->_header_add(sprintf('Host: %s:%s', $this->_server, $this->_port)); + //$request .= sprintf('Connection: Keep-Alive'); + $this->_header_add(sprintf('User-Agent: %s', $this->_user_agent)); + $this->_header_add('Connection: TE'); + $this->_header_add('TE: Trailers'); + //$this->_header_add(sprintf('Authorization: Basic %s', base64_encode("$this->_user:$this->_pass"))); + } + + /** + * Private method _send_request + * + * Sends a ready formed http/webdav request to webdav server. + * + * @access private + */ + private function _send_request() { + // check if stream is declared to be open + // only logical check we are not sure if socket is really still open ... + if ($this->_connection_closed) { + // reopen it + // be sure to close the open socket. + $this->close(); + $this->_reopen(); + } + + // convert array to string + $buffer = implode("\r\n", $this->_req); + $buffer .= "\r\n\r\n"; + $this->_error_log($buffer); + fputs($this->_fp, $buffer); + } + + /** + * Private method _get_respond + * + * Reads the reponse from the webdav server. + * + * Stores data into class vars _header for the header data and + * _body for the rest of the response. + * This routine is the weakest part of this class, because it very depends how php does handle a socket stream. + * If the stream is blocked for some reason php is blocked as well. + * @access private + */ + private function _get_respond() { + $this->_error_log('_get_respond()'); + // init vars (good coding style ;-) + $buffer = ''; + $header = ''; + // attention: do not make max_chunk_size to big.... + $max_chunk_size = 8192; + // be sure we got a open ressource + if (! $this->_fp) { + $this->_error_log('socket is not open. Can not process response'); + return false; + } + + // following code maybe helps to improve socket behaviour ... more testing needed + // disabled at the moment ... + // socket_set_timeout($this->_fp,1 ); + // $socket_state = socket_get_status($this->_fp); + + // read stream one byte by another until http header ends + $i = 0; + $matches = array(); + do { + $header.=fread($this->_fp,1); + $i++; + } while (!preg_match('/\\r\\n\\r\\n$/',$header, $matches) && $i < $this->_maxheaderlenth); + + $this->_error_log($header); + + if (preg_match('/Connection: close\\r\\n/', $header)) { + // This says that the server will close connection at the end of this stream. + // Therefore we need to reopen the socket, before are sending the next request... + $this->_error_log('Connection: close found'); + $this->_connection_closed = true; + } + // check how to get the data on socket stream + // chunked or content-length (HTTP/1.1) or + // one block until feof is received (HTTP/1.0) + switch(true) { + case (preg_match('/Transfer\\-Encoding:\\s+chunked\\r\\n/',$header)): + $this->_error_log('Getting HTTP/1.1 chunked data...'); + do { + $byte = ''; + $chunk_size=''; + do { + $chunk_size.=$byte; + $byte=fread($this->_fp,1); + // check what happens while reading, because I do not really understand how php reads the socketstream... + // but so far - it seems to work here - tested with php v4.3.1 on apache 1.3.27 and Debian Linux 3.0 ... + if (strlen($byte) == 0) { + $this->_error_log('_get_respond: warning --> read zero bytes'); + } + } while ($byte!="\r" and strlen($byte)>0); // till we match the Carriage Return + fread($this->_fp, 1); // also drop off the Line Feed + $chunk_size=hexdec($chunk_size); // convert to a number in decimal system + if ($chunk_size > 0) { + $buffer .= fread($this->_fp,$chunk_size); + } + fread($this->_fp,2); // ditch the CRLF that trails the chunk + } while ($chunk_size); // till we reach the 0 length chunk (end marker) + break; + + // check for a specified content-length + case preg_match('/Content\\-Length:\\s+([0-9]*)\\r\\n/',$header,$matches): + $this->_error_log('Getting data using Content-Length '. $matches[1]); + // check if we the content data size is small enough to get it as one block + if ($matches[1] <= $max_chunk_size ) { + // only read something if Content-Length is bigger than 0 + if ($matches[1] > 0 ) { + $buffer = fread($this->_fp, $matches[1]); + } else { + $buffer = ''; + } + } else { + // data is to big to handle it as one. Get it chunk per chunk... + do { + $mod = $max_chunk_size % ($matches[1] - strlen($buffer)); + $chunk_size = ($mod == $max_chunk_size ? $max_chunk_size : $matches[1] - strlen($buffer)); + $buffer .= fread($this->_fp, $chunk_size); + $this->_error_log('mod: ' . $mod . ' chunk: ' . $chunk_size . ' total: ' . strlen($buffer)); + } while ($mod == $max_chunk_size); + } + break; + + // check for 204 No Content + // 204 responds have no body. + // Therefore we do not need to read any data from socket stream. + case preg_match('/HTTP\/1\.1\ 204/',$header): + // nothing to do, just proceed + $this->_error_log('204 No Content found. No further data to read..'); + break; + default: + // just get the data until foef appears... + $this->_error_log('reading until feof...' . $header); + socket_set_timeout($this->_fp,0 ); + while (!feof($this->_fp)) { + $buffer .= fread($this->_fp, 4096); + } + // renew the socket timeout...does it do something ???? Is it needed. More debugging needed... + socket_set_timeout($this->_fp, $this->_socket_timeout); + } + + $this->_header = $header; + $this->_body = $buffer; + // $this->_buffer = $header . "\r\n\r\n" . $buffer; + $this->_error_log($this->_header); + $this->_error_log($this->_body); + } + + + /** + * Private method _process_respond + * + * Processes the webdav server respond and detects its components (header, body). + * and returns data array structure. + * @return array ret_struct + * @access private + */ + private function _process_respond() { + $lines = explode("\r\n", $this->_header); + $header_done = false; + // $this->_error_log($this->_buffer); + // First line should be a HTTP status line (see http://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html#sec6) + // Format is: HTTP-Version SP Status-Code SP Reason-Phrase CRLF + list($ret_struct['status']['http-version'], + $ret_struct['status']['status-code'], + $ret_struct['status']['reason-phrase']) = explode(' ', $lines[0],3); + + // print "HTTP Version: '$http_version' Status-Code: '$status_code' Reason Phrase: '$reason_phrase'
"; + // get the response header fields + // See http://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html#sec6 + for($i=1; $i"; + + } + if (!$header_done ) { + // store all found headers in array ... + list($fieldname, $fieldvalue) = explode(':', $lines[$i]); + // check if this header was allready set (apache 2.0 webdav module does this....). + // If so we add the the value to the end the fieldvalue, separated by comma... if (empty($ret_struct['header'])) { $ret_struct['header'] = array(); } - if (empty($ret_struct['header'][$fieldname])) { - $ret_struct['header'][$fieldname] = trim($fieldvalue); - } else { - $ret_struct['header'][$fieldname] .= ',' . trim($fieldvalue); - } - } - } - // print 'string len of response_body:'. strlen($response_body); - // print '[' . htmlentities($response_body) . ']'; - $ret_struct['body'] = $this->_body; - $this->_error_log('_process_respond: ' . var_export($ret_struct,true)); - return $ret_struct; - - } - - /** - * Private method _reopen - * - * Reopens a socket, if 'connection: closed'-header was received from server. - * - * Uses public method open. - * @access private - */ - function _reopen() { - // let's try to reopen a socket - $this->_error_log('reopen a socket connection'); - return $this->open(); - /* - $this->_fp = fsockopen ($this->_server, $this->_port, $this->_errno, $this->_errstr, 5); - set_time_limit(180); - socket_set_blocking($this->_fp, true); - socket_set_timeout($this->_fp,5 ); - if (!$this->_fp) { - $this->_error_log("$this->_errstr ($this->_errno)\n"); - return false; - } else { - $this->_connection_closed = false; - $this->_error_log('reopen ok...' . $this->_fp); - return true; - } - */ - } - - - /** - * Private method _translate_uri - * - * translates an uri to raw url encoded string. - * Removes any html entity in uri - * @param string uri - * @return string translated_uri - * @access private - */ - function _translate_uri($uri) { - // remove all html entities... - $native_path = html_entity_decode($uri); - $parts = explode('/', $native_path); - for ($i = 0; $i < count($parts); $i++) { - // check if part is allready utf8 - if (iconv('UTF-8', 'UTF-8', $parts[$i]) == $parts[$i]) { - $parts[$i] = rawurlencode($parts[$i]); - } else { - $parts[$i] = rawurlencode(utf8_encode($parts[$i])); - } - } - return implode('/', $parts); - } - - /** - * Private method _utf_decode_path - * - * decodes a UTF-8 encoded string - * @return string decodedstring - * @access private - */ - function _utf_decode_path($path) { - $fullpath = $path; - if (iconv('UTF-8', 'UTF-8', $fullpath) == $fullpath) { - $this->_error_log("filename is utf-8. Needs conversion..."); - $fullpath = utf8_decode($fullpath); - } - return $fullpath; - } - - /** - * Private method _error_log - * - * a simple php error_log wrapper. - * @param string err_string - * @access private - */ - function _error_log($err_string) { - if ($this->_debug) { - error_log($err_string); - } - } + if (empty($ret_struct['header'][$fieldname])) { + $ret_struct['header'][$fieldname] = trim($fieldvalue); + } else { + $ret_struct['header'][$fieldname] .= ',' . trim($fieldvalue); + } + } + } + // print 'string len of response_body:'. strlen($response_body); + // print '[' . htmlentities($response_body) . ']'; + $ret_struct['body'] = $this->_body; + $this->_error_log('_process_respond: ' . var_export($ret_struct,true)); + return $ret_struct; + + } + + /** + * Private method _reopen + * + * Reopens a socket, if 'connection: closed'-header was received from server. + * + * Uses public method open. + * @access private + */ + private function _reopen() { + // let's try to reopen a socket + $this->_error_log('reopen a socket connection'); + return $this->open(); + /* + $this->_fp = @fsockopen ($this->_server, $this->_port, $this->_errno, $this->_errstr, 5); + set_time_limit(180); + socket_set_blocking($this->_fp, true); + socket_set_timeout($this->_fp,5 ); + if (!$this->_fp) { + $this->_error_log("$this->_errstr ($this->_errno)\n"); + return false; + } else { + $this->_connection_closed = false; + $this->_error_log('reopen ok...' . $this->_fp); + return true; + } + */ + } + + + /** + * Private method _translate_uri + * + * translates an uri to raw url encoded string. + * Removes any html entity in uri + * @param string uri + * @return string translated_uri + * @access private + */ + private function _translate_uri($uri) { + // remove all html entities... + $native_path = html_entity_decode($uri); + $parts = explode('/', $native_path); + for ($i = 0; $i < count($parts); $i++) { + // check if part is allready utf8 + if (iconv('UTF-8', 'UTF-8', $parts[$i]) == $parts[$i]) { + $parts[$i] = rawurlencode($parts[$i]); + } else { + $parts[$i] = rawurlencode(utf8_encode($parts[$i])); + } + } + return implode('/', $parts); + } + + /** + * Private method _utf_decode_path + * + * decodes a UTF-8 encoded string + * @return string decodedstring + * @access private + */ + private function _utf_decode_path($path) { + $fullpath = $path; + if (iconv('UTF-8', 'UTF-8', $fullpath) == $fullpath) { + $this->_error_log("filename is utf-8. Needs conversion..."); + $fullpath = utf8_decode($fullpath); + } + return $fullpath; + } + + /** + * Private method _error_log + * + * a simple php error_log wrapper. + * @param string err_string + * @access private + */ + private function _error_log($err_string) { + if ($this->_debug) { + error_log($err_string); + } + } } ?> diff --git a/repository/webdav/repository.class.php b/repository/webdav/repository.class.php index cbd9158ea5..3c7448c7e4 100644 --- a/repository/webdav/repository.class.php +++ b/repository/webdav/repository.class.php @@ -12,16 +12,29 @@ require_once($CFG->libdir.'/webdavlib.php'); class repository_webdav extends repository { public function __construct($repositoryid, $context = SITEID, $options = array()) { parent::__construct($repositoryid, $context, $options); + // set up webdav client $this->wd = new webdav_client(); if (empty($this->webdav_server)) { return; } + if (empty($this->webdav_type)) { + $this->webdav_type = ''; + } else { + $this->webdav_type = 'ssl://'; + } $this->wd->set_server($this->webdav_server); if (empty($this->webdav_port)) { - $this->wd->set_port(80); + if (empty($this->webdav_type)) { + $this->wd->set_port(80); + } else { + $this->wd->set_port(443); + } + $port = ''; } else { $this->wd->set_port($this->webdav_port); + $port = ':'.$this->webdav_port; } + $this->webdav_host = $this->webdav_type.$this->webdav_server.$port; if(!empty($this->webdav_user)){ $this->wd->set_user($this->webdav_user); } @@ -32,14 +45,15 @@ class repository_webdav extends repository { $this->wd->set_debug(false); } public function check_login() { - global $SESSION; return true; } public function get_file($url, $title) { global $CFG; $path = $this->prepare_file($title); $buffer = ''; - $this->wd->open(); + if (!$this->wd->open()) { + return false; + } $this->wd->get($url, $buffer); $fp = fopen($path, 'wb'); fwrite($fp, $buffer); @@ -53,29 +67,48 @@ class repository_webdav extends repository { $list = array(); $ret = array(); $ret['dynload'] = true; - $ret['list'] = array(); $ret['nosearch'] = true; $ret['nologin'] = true; $ret['path'] = array(array('name'=>'Root', 'path'=>0)); - $this->wd->open(); + $ret['list'] = array(); + if (!$this->wd->open()) { + return $ret; + } if (empty($path)) { - $dir = $this->wd->ls('/'); $path = '/'; + $dir = $this->wd->ls($path); } else { + if (empty($this->webdav_type)) { + $partern = '#http://'.$this->webdav_host.'/#'; + } else { + $partern = '#http://'.$this->webdav_type.$this->webdav_host.'/#'; + } + $path = '/'.preg_replace($partern, '', $path); $dir = $this->wd->ls($path); } if (!is_array($dir)) { return $ret; } foreach ($dir as $v) { - $ts = $this->wd->iso8601totime($v['creationdate']); - $filedate = userdate($ts); + if (!empty($v['creationdate'])) { + $ts = $this->wd->iso8601totime($v['creationdate']); + $filedate = userdate($ts); + } else { + $filedate = ''; + } if (!empty($v['resourcetype']) && $v['resourcetype'] == 'collection') { + // a folder if ($path != $v['href']) { - $title = urldecode(substr($v['href'], strpos($v['href'], $path)+strlen($path))); + $matches = array(); + preg_match('#(\w+)$#i', $v['href'], $matches); + if (!empty($matches[1])) { + $title = urldecode($matches[1]); + } else { + $title = urldecode($v['href']); + } $ret['list'][] = array( 'title'=>$title, - 'thumbnail'=>$CFG->pixpath.'/f/folder.gif', + 'thumbnail'=>$CFG->pixpath.'/f/folder-32.png', 'children'=>array(), 'date'=>$filedate, 'size'=>0, @@ -83,11 +116,13 @@ class repository_webdav extends repository { ); } }else{ + // a file $title = urldecode(substr($v['href'], strpos($v['href'], $path)+strlen($path))); + $size = !empty($v['getcontentlength'])? $v['getcontentlength']:''; $ret['list'][] = array( 'title'=>$title, 'thumbnail' => $CFG->pixpath .'/f/'. mimeinfo('icon32', $title), - 'size'=>$v['getcontentlength'], + 'size'=>$size, 'date'=>$filedate, 'source'=>$v['href'] ); @@ -96,10 +131,14 @@ class repository_webdav extends repository { return $ret; } public static function get_instance_option_names() { - return array('webdav_server', 'webdav_port', 'webdav_user', 'webdav_password'); + return array('webdav_type', 'webdav_server', 'webdav_port', 'webdav_user', 'webdav_password'); } public function instance_config_form(&$mform) { + $choices = array(0 => get_string('http', 'repository_webdav'), 1 => get_string('https', 'repository_webdav')); + $mform->addElement('select', 'webdav_type', get_string('webdav_type', 'repository_webdav'), $choices); + $mform->addRule('webdav_type', get_string('required'), 'required', null, 'client'); + $mform->addElement('text', 'webdav_server', get_string('webdav_server', 'repository_webdav'), array('size' => '40')); $mform->addRule('webdav_server', get_string('required'), 'required', null, 'client'); $mform->addElement('text', 'webdav_port', get_string('webdav_port', 'repository_webdav'), array('size' => '40')); diff --git a/repository/ws.php b/repository/ws.php index 707009d901..46fc509902 100644 --- a/repository/ws.php +++ b/repository/ws.php @@ -176,11 +176,15 @@ EOD; } break; case 'download': - $path = $repo->get_file($file, $title); - if (empty($itemid)) { - $itemid = (int)substr(hexdec(uniqid()), 0, 9)+rand(1,100); - } try { + $path = $repo->get_file($file, $title); + if ($path === false) { + $err->e = get_string('cannotdownload', 'repository'); + die(json_encode($err)); + } + if (empty($itemid)) { + $itemid = (int)substr(hexdec(uniqid()), 0, 9)+rand(1,100); + } if (preg_match('#(https?://([-\w\.]+)+(:\d+)?(/([\w/_\.]*(\?\S+)?)?)?)#', $path)) { echo json_encode(array('client_id'=>$client_id, 'url'=>$path, 'id'=>$path, 'file'=>$path)); } else { -- 2.39.5