on nginx webdav, PUT returns 501

I’ve been charged with converting a webserver from apache to nginx, and I’m currently stuck on getting webdav to work. WebDAV is served by the PEAR webdav server (http://pear.php.net/package/HTTP_WebDAV_Server). I’m new to nginx, but I’ve done my best to follow examples per the nginx webdav and webdav_ext modules (or find anyone having exactly this problem) to no avail. List/read operations appear to all work fine, but PUT operations return 501 not implemented and make no change to the file. Nothing in the error.log I suspect there’s a pitfall I’m missing.

nginx version/options:

vagrant@precise64:~/mudlib$ /opt/nginx-1.4.4/sbin/nginx -V
nginx version: nginx/1.4.4
built by gcc 4.6.3 (Ubuntu/Linaro 4.6.3-1ubuntu5)
TLS SNI support enabled
configure arguments: --prefix=/opt/nginx-1.4.4 --conf-path=/etc/nginx/nginx.conf --sbin-path=/opt/nginx-1.4.4/sbin/nginx --add-module=/var/chef/cache/5a85970ba61a99f55a26d2536a11d512b39bbd622f5737d25a9a8c10db81efa9 --with-http_ssl_module --with-http_gzip_static_module --with-http_dav_module --add-module=/var/chef/cache/d428a0236c933779cb40ac8c91afb19d5c25a376dc3caab825bfd543e1ee530d

The two chef-managed extensions are http_auth_pam (http://web.iti.upv.es/~sto/nginx/ngx_http_auth_pam_module-1.2.tar.gz) and nginx_dav_ext (http://github.com/arut/nginx-dav-ext-module/archive/v0.0.3.tar.gz).

Nginx site config:

server {
  root /home/vagrant/mudlib/www/;

  listen 80;
  server_name tsunami;
  access_log  /var/log/nginx/tsunami.access.log;

  index index.php;

  location / {
    try_files $uri $uri/ /index.php;
  }

  location ~ ^(/[^./]+)$ {
    proxy_pass http://localhost:8002;
  }

  location /wizards/ {
    auth_pam "Restricted Zone";
    auth_pam_service_name "nginx";
  }

  location ~ ^(.+\.php)(.*)$ {
  fastcgi_split_path_info ^(.+\.php)(.*)$;
  fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
  fastcgi_param PATH_INFO $fastcgi_path_info;
  dav_methods  PUT DELETE MKCOL COPY MOVE;
  dav_ext_methods PROPFIND OPTIONS;
  dav_access user:rw group:rw  all:rw;
  fastcgi_pass unix:/var/run/php-fpm-www.sock;
  include fastcgi_params;
  }
}

nginx site access log:

10.0.2.2 - misery [30/Aug/2014:15:51:29 +0000] "PROPFIND /webdav.php/text/WIZNEWS HTTP/1.1" 207 685 "-" "Microsoft-WebDAV-MiniRedir/6.1.7601"
10.0.2.2 - misery [30/Aug/2014:15:51:29 +0000] "LOCK /webdav.php/text/WIZNEWS HTTP/1.1" 200 458 "-" "Microsoft-WebDAV-MiniRedir/6.1.7601"
10.0.2.2 - misery [30/Aug/2014:15:51:29 +0000] "GET /webdav.php/text/WIZNEWS HTTP/1.1" 200 474 "-" "Microsoft-WebDAV-MiniRedir/6.1.7601"
10.0.2.2 - misery [30/Aug/2014:15:51:29 +0000] "PROPFIND /webdav.php/text HTTP/1.1" 207 661 "-" "Microsoft-WebDAV-MiniRedir/6.1.7601"
10.0.2.2 - misery [30/Aug/2014:15:51:29 +0000] "HEAD /webdav.php/text/WIZNEWS HTTP/1.1" 200 0 "-" "Microsoft-WebDAV-MiniRedir/6.1.7601"
10.0.2.2 - misery [30/Aug/2014:15:51:29 +0000] "PUT /webdav.php/text/WIZNEWS HTTP/1.1" 501 61 "-" "Microsoft-WebDAV-MiniRedir/6.1.7601"
10.0.2.2 - misery [30/Aug/2014:15:51:30 +0000] "UNLOCK /webdav.php/text/WIZNEWS HTTP/1.1" 204 0 "-" "Microsoft-WebDAV-MiniRedir/6.1.7601"
10.0.2.2 - misery [30/Aug/2014:15:51:30 +0000] "PROPFIND /webdav.php/text/WIZNEWS HTTP/1.1" 207 685 "-" "Microsoft-WebDAV-MiniRedir/6.1.7601"
10.0.2.2 - misery [30/Aug/2014:15:51:30 +0000] "LOCK /webdav.php/text/WIZNEWS HTTP/1.1" 200 458 "-" "Microsoft-WebDAV-MiniRedir/6.1.7601"
10.0.2.2 - misery [30/Aug/2014:15:51:30 +0000] "GET /webdav.php/text/WIZNEWS HTTP/1.1" 200 474 "-" "Microsoft-WebDAV-MiniRedir/6.1.7601"
10.0.2.2 - misery [30/Aug/2014:15:51:30 +0000] "HEAD /webdav.php/text/WIZNEWS HTTP/1.1" 200 0 "-" "Microsoft-WebDAV-MiniRedir/6.1.7601"
10.0.2.2 - misery [30/Aug/2014:15:51:30 +0000] "PUT /webdav.php/text/WIZNEWS HTTP/1.1" 501 61 "-" "Microsoft-WebDAV-MiniRedir/6.1.7601"
10.0.2.2 - misery [30/Aug/2014:15:51:31 +0000] "UNLOCK /webdav.php/text/WIZNEWS HTTP/1.1" 204 0 "-" "Microsoft-WebDAV-MiniRedir/6.1.7601"

Answer

The solution in our case was to pull a current copy of the pear/webdav server code and re-apply the security policy edits we’d made to it. The 501 code wasn’t nginx’s fault, and whatever underlying issue caused the PUT failures to appear in nginx but not apache has been resolved in more recent versions of the webdav server code.

In case it helps anyone else who encounters the issue in the future find this question, I’ll document where the error cropped up. In http_PUT() of pear/webdav’s Server.php, there are a number of checks for unsupported Content-* headers:

       /* RFC 2616 2.6 says: "The recipient of the entity MUST NOT
         ignore any Content-* (e.g. Content-Range) headers that it
         does not understand or implement and MUST return a 501
         (Not Implemented) response in such cases."
        */
        foreach ($this->_SERVER as $key => $val) {
            if (strncmp($key, "HTTP_CONTENT", 11)) continue;
            switch ($key) {
            case 'HTTP_CONTENT_ENCODING': // RFC 2616 14.11
                // TODO support this if ext/zlib filters are available
                $this->http_status("501 not implemented");
                echo "The service does not support '$val' content encoding";
                return;

            case 'HTTP_CONTENT_LANGUAGE': // RFC 2616 14.12
                // we assume it is not critical if this one is ignored
                // in the actual PUT implementation ...
                $options["content_language"] = $val;
                break;

            case 'HTTP_CONTENT_LOCATION': // RFC 2616 14.14
                /* The meaning of the Content-Location header in PUT
                 or POST requests is undefined; servers are free
                 to ignore it in those cases. */
                break;

            case 'HTTP_CONTENT_RANGE':    // RFC 2616 14.16
                // single byte range requests are supported
                // the header format is also specified in RFC 2616 14.16
                // TODO we have to ensure that implementations support this or send 501 instead
                if (!preg_match('@bytes\s+(\d+)-(\d+)/((\d+)|\*)@', $val, $matches)) {
                    $this->http_status("400 bad request");
                    echo "The service does only support single byte ranges";
                    return;
                }

                $range = array("start"=>$matches[1], "end"=>$matches[2]);
                if (is_numeric($matches[3])) {
                    $range["total_length"] = $matches[3];
                }
                $option["ranges"][] = $range;

                // TODO make sure the implementation supports partial PUT
                // this has to be done in advance to avoid data being overwritten
                // on implementations that do not support this ...
                break;

            case 'HTTP_CONTENT_MD5':      // RFC 2616 14.15
                // TODO: maybe we can just pretend here?
                $this->http_status("501 not implemented");
                echo "The service does not support content MD5 checksum verification";
                return;

            default:
                // any other unknown Content-* headers
                $this->http_status("501 not implemented");
                echo "The service does not support '$key'";
                return;
            }
        }

PUT was failing under the default case, when encountering an HTTP_CONTENT_LENGTH header it didn’t seem to expect.

Attribution
Source : Link , Question Author : abathur , Answer Author : abathur

Leave a Comment