| 1 | <?php
|
|---|
| 2 |
|
|---|
| 3 | # PHP FCGI to Extend NGINX WebDAV
|
|---|
| 4 | # Written by Jason LaPorte (jason@agoragames.com)
|
|---|
| 5 | #
|
|---|
| 6 | # Copyright (C) 2009 Agora Games, Inc.
|
|---|
| 7 | #
|
|---|
| 8 | # This software is provided 'as-is', without any express or implied warranty.
|
|---|
| 9 | # In no event will the authors be held liable for any damages arising from the
|
|---|
| 10 | # use of this software.
|
|---|
| 11 | #
|
|---|
| 12 | # Permission is granted to anyone to use this software for any purpose,
|
|---|
| 13 | # including commercial applications, and to alter it and redistribute it
|
|---|
| 14 | # freely, subject to the following restrictions:
|
|---|
| 15 | #
|
|---|
| 16 | # 1. The origin of this software must not be misrepresented; you must not
|
|---|
| 17 | # claim that you wrote the original software. If you use this software
|
|---|
| 18 | # in a product, an acknowledgment in the product documentation would be
|
|---|
| 19 | # appreciated but is not required.
|
|---|
| 20 | # 2. Altered source versions must be plainly marked as such, and must not be
|
|---|
| 21 | # misrepresented as being the original software.
|
|---|
| 22 | # 3. This notice may not be removed or altered from any source distribution.
|
|---|
| 23 | #
|
|---|
| 24 | # MIME type determination, miscellaneous error handling, and general code
|
|---|
| 25 | # structure adapted from YUNO's similar Perl CGI script[1]. More information
|
|---|
| 26 | # about this script can be found on the Agora Games website[2].
|
|---|
| 27 | #
|
|---|
| 28 | # [1]: http://plan9.aichi-u.ac.jp/netlib/webappls/webdav.cgi
|
|---|
| 29 | # [2]: http://blog.agoragames.com/2009/03/20/webdav-nginx-play-nice/
|
|---|
| 30 | #
|
|---|
| 31 | # Version History:
|
|---|
| 32 | # * 3/17/2009: V1. (Initial version.)
|
|---|
| 33 | # * 3/19/2009: V2. (Added basic support for the COPY and MOVE methods. Added
|
|---|
| 34 | # ZLib license for public distribution.)
|
|---|
| 35 | # * 3/20/2009: V3. (Added recursive_copy and recursive_unlink functions,
|
|---|
| 36 | # eliminating the need to fork processes.)
|
|---|
| 37 | #
|
|---|
| 38 | # To-Do:
|
|---|
| 39 | # * Support (or, at least, stub out) PROPPATCH, LOCK, and UNLOCK operations.
|
|---|
| 40 | # * Properly support the DEPTH header in COPY requests.
|
|---|
| 41 | # * Properly support the various options for PROPFIND requests. (Ha!)
|
|---|
| 42 |
|
|---|
| 43 | # Recursively delete $src. This is equivalent to UNIX's "rm -rf". Be very
|
|---|
| 44 | # careful how you use it. Returns true on success and false on failure. (On
|
|---|
| 45 | # failure, files that could not be deleted will obviously remain.)
|
|---|
| 46 | function recursive_unlink ($src) {
|
|---|
| 47 | if (file_exists ($src)) {
|
|---|
| 48 | if (is_dir ($src)) {
|
|---|
| 49 | foreach (scandir ($src) as $child)
|
|---|
| 50 | if ($child != '.' && $child != '..')
|
|---|
| 51 | recursive_unlink ("$src/$child");
|
|---|
| 52 |
|
|---|
| 53 | return rmdir ($src);
|
|---|
| 54 | }
|
|---|
| 55 |
|
|---|
| 56 | else
|
|---|
| 57 | return unlink ($src);
|
|---|
| 58 | }
|
|---|
| 59 |
|
|---|
| 60 | return false;
|
|---|
| 61 | }
|
|---|
| 62 |
|
|---|
| 63 | # Recursively copy $src to $dest. If $dest already exists, it will be
|
|---|
| 64 | # overwritten. (This makes this function not quite semantically identical to
|
|---|
| 65 | # UNIX's "cp -r".) Returns true on success and false on failure. On failure,
|
|---|
| 66 | # $dest may be destroyed, regardless of whether it existed before the copy or
|
|---|
| 67 | # not. Thus, if you call this function, treat $dest as forfeit.
|
|---|
| 68 | # FIXME: We should preserve permissions in the copy.
|
|---|
| 69 | function recursive_copy ($src, $dest) {
|
|---|
| 70 | if (file_exists ($src)) {
|
|---|
| 71 | recursive_unlink ($dest);
|
|---|
| 72 |
|
|---|
| 73 | if (is_dir ($src)) {
|
|---|
| 74 | mkdir ($dest);
|
|---|
| 75 |
|
|---|
| 76 | foreach (scandir ($src) as $child)
|
|---|
| 77 | if ($child != '.' && $child != '..')
|
|---|
| 78 | if (!recursive_copy ("$src/$child", "$dest/$child")) {
|
|---|
| 79 | recursive_unlink ($dest);
|
|---|
| 80 | return false;
|
|---|
| 81 | }
|
|---|
| 82 |
|
|---|
| 83 | return true;
|
|---|
| 84 | }
|
|---|
| 85 |
|
|---|
| 86 | else if (is_link ($src))
|
|---|
| 87 | return symlink (readlink ($src), $dest);
|
|---|
| 88 |
|
|---|
| 89 | else
|
|---|
| 90 | return copy ($src, $dest);
|
|---|
| 91 | }
|
|---|
| 92 |
|
|---|
| 93 | return false;
|
|---|
| 94 | }
|
|---|
| 95 |
|
|---|
| 96 | # Validate $key, treating it as $default if not supplied, according to the
|
|---|
| 97 | # possible values in $options.
|
|---|
| 98 | function validate ($key, $default, $options) {
|
|---|
| 99 | if (is_null ($key) || $key === '') $key = $default;
|
|---|
| 100 | return $options[$key];
|
|---|
| 101 | }
|
|---|
| 102 |
|
|---|
| 103 | # Gets the MIME type of a particular file by examining its file extension. This
|
|---|
| 104 | # could be greatly improved by doing something similar to the UNIX "file"
|
|---|
| 105 | # command (e.g. examining headers), but this is a quick and easy hack.
|
|---|
| 106 | function mime_type ($path) {
|
|---|
| 107 | # I love how PHP makes me want to kill myself. Why doesn't it support
|
|---|
| 108 | # nonscalar constants?
|
|---|
| 109 | $mime_types = array (
|
|---|
| 110 | 'aif' => 'audio/x-aiff',
|
|---|
| 111 | 'aiff' => 'audio/x-aiff',
|
|---|
| 112 | 'asc' => 'text/plain',
|
|---|
| 113 | 'atom' => 'text/plain',
|
|---|
| 114 | 'au' => 'audio/basic',
|
|---|
| 115 | 'avi' => 'video/x-msvideo',
|
|---|
| 116 | 'bmp' => 'image/bmp',
|
|---|
| 117 | 'c' => 'text/plain',
|
|---|
| 118 | 'cc' => 'text/plain',
|
|---|
| 119 | 'cgi' => 'text/plain',
|
|---|
| 120 | 'cpp' => 'text/plain',
|
|---|
| 121 | 'css' => 'text/css',
|
|---|
| 122 | 'cxx' => 'text/plain',
|
|---|
| 123 | 'doc' => 'application/msword',
|
|---|
| 124 | 'dv' => 'video/x-dv',
|
|---|
| 125 | 'eps' => 'application/postscript',
|
|---|
| 126 | 'gif' => 'image/gif',
|
|---|
| 127 | 'gz' => 'application/x-gzip',
|
|---|
| 128 | 'h' => 'text/plain',
|
|---|
| 129 | 'hpp' => 'text/plain',
|
|---|
| 130 | 'hqx' => 'application/mac-binhex40',
|
|---|
| 131 | 'htm' => 'text/html',
|
|---|
| 132 | 'html' => 'text/html',
|
|---|
| 133 | 'hxx' => 'text/plain',
|
|---|
| 134 | 'jar' => 'application/java-archive',
|
|---|
| 135 | 'jav' => 'text/plain',
|
|---|
| 136 | 'java' => 'text/plain',
|
|---|
| 137 | 'jpeg' => 'image/jpeg',
|
|---|
| 138 | 'jpg' => 'image/jpeg',
|
|---|
| 139 | 'js' => 'text/plain',
|
|---|
| 140 | 'lzh' => 'application/x-lzh',
|
|---|
| 141 | 'm' => 'text/plain',
|
|---|
| 142 | 'm4a' => 'audio/mp4a-latm',
|
|---|
| 143 | 'mid' => 'audio/midi',
|
|---|
| 144 | 'midi' => 'audio/midi',
|
|---|
| 145 | 'mm' => 'text/plain',
|
|---|
| 146 | 'mov' => 'video/quicktime',
|
|---|
| 147 | 'mp2' => 'audio/mpeg',
|
|---|
| 148 | 'mp3' => 'audio/mpeg',
|
|---|
| 149 | 'mp4' => 'video/mp4',
|
|---|
| 150 | 'mpeg' => 'video/mpeg',
|
|---|
| 151 | 'mpg' => 'video/mpeg',
|
|---|
| 152 | 'ogg' => 'application/ogg',
|
|---|
| 153 | 'pdf' => 'application/pdf',
|
|---|
| 154 | 'php' => 'text/plain',
|
|---|
| 155 | 'pict' => 'image/pict',
|
|---|
| 156 | 'pl' => 'text/plain',
|
|---|
| 157 | 'png' => 'image/png',
|
|---|
| 158 | 'ppt' => 'application/vnd.ms-powerpoint',
|
|---|
| 159 | 'ps' => 'application/postscript',
|
|---|
| 160 | 'py' => 'text/plain',
|
|---|
| 161 | 'rb' => 'text/plain',
|
|---|
| 162 | 'rdf' => 'text/plain',
|
|---|
| 163 | 'rm' => 'audio/x-pn-realaudio',
|
|---|
| 164 | 'rtf' => 'text/rtf',
|
|---|
| 165 | 'sh' => 'text/plain',
|
|---|
| 166 | 'shtml' => 'text/html',
|
|---|
| 167 | 'snd' => 'audio/basic',
|
|---|
| 168 | 'svg' => 'image/svg+xml',
|
|---|
| 169 | 'swf' => 'application/x-shockwave-flash',
|
|---|
| 170 | 'tar' => 'application/x-tar',
|
|---|
| 171 | 'tex' => 'application/x-tex',
|
|---|
| 172 | 'tif' => 'image/tiff',
|
|---|
| 173 | 'tiff' => 'image/tiff',
|
|---|
| 174 | 'txt' => 'text/plain',
|
|---|
| 175 | 'vrml' => 'model/vrml',
|
|---|
| 176 | 'wav' => 'audio/x-wav',
|
|---|
| 177 | 'wbmp' => 'image/vnd.wap.wbmp',
|
|---|
| 178 | 'wrl' => 'model/vrml',
|
|---|
| 179 | 'xbm' => 'image/x-xbitmap',
|
|---|
| 180 | 'xhtml' => 'text/html',
|
|---|
| 181 | 'xls' => 'application/vnd.ms-excel',
|
|---|
| 182 | 'xml' => 'text/xml',
|
|---|
| 183 | 'xpm' => 'image/x-xpixmap',
|
|---|
| 184 | 'xsl' => 'text/xsl',
|
|---|
| 185 | 'zip' => 'application/zip'
|
|---|
| 186 | );
|
|---|
| 187 |
|
|---|
| 188 | $extension = substr (strrchr ($path, '.'), 1);
|
|---|
| 189 | $mime = $mime_types[$extension];
|
|---|
| 190 |
|
|---|
| 191 | return $mime ? $mime : 'application/octet-stream';
|
|---|
| 192 | }
|
|---|
| 193 |
|
|---|
| 194 | # PROPFIND is recursive in nature, so it gets its own function. Since we
|
|---|
| 195 | # assume "allprop" is set, it's not especially complicated: just stat() a file
|
|---|
| 196 | # and format the results as XML. (Granted, the XML formatting is kinda silly,
|
|---|
| 197 | # but you can blame the WebDAV folks for that.)
|
|---|
| 198 | function propfind ($root, $path, $depth) {
|
|---|
| 199 | $href = str_replace (array ('%2F', '+'),
|
|---|
| 200 | array ('/', '%20'),
|
|---|
| 201 | urlencode ($path));
|
|---|
| 202 | $file = $root . $path;
|
|---|
| 203 | $exists = file_exists ($file);
|
|---|
| 204 | $dir = NULL;
|
|---|
| 205 | $stat = NULL;
|
|---|
| 206 |
|
|---|
| 207 | if ($href === '')
|
|---|
| 208 | $href = '/';
|
|---|
| 209 |
|
|---|
| 210 | if ($exists) {
|
|---|
| 211 | $dir = is_dir ($file);
|
|---|
| 212 | $stat = stat ($file);
|
|---|
| 213 | }
|
|---|
| 214 |
|
|---|
| 215 | echo ('<response>');
|
|---|
| 216 | echo ("<href>$href</href>");
|
|---|
| 217 | echo ('<propstat>');
|
|---|
| 218 |
|
|---|
| 219 | # File not found.
|
|---|
| 220 | if (!$exists)
|
|---|
| 221 | echo ('<status>HTTP/1.1 404 File Not Found</status>');
|
|---|
| 222 |
|
|---|
| 223 | # If we can't stat the file, it's probably a permissions issue. (I use a
|
|---|
| 224 | # 403 and not a 401 because the client can never recover from the error--
|
|---|
| 225 | # it's based on the server's permissions, not the client's.)
|
|---|
| 226 | else if (!$stat)
|
|---|
| 227 | echo ('<status>HTTP/1.1 403 Forbidden</status>');
|
|---|
| 228 |
|
|---|
| 229 | else {
|
|---|
| 230 | echo ('<status>HTTP/1.1 200 OK</status>');
|
|---|
| 231 | echo ('<prop>');
|
|---|
| 232 |
|
|---|
| 233 | $name = htmlspecialchars (basename ($file));
|
|---|
| 234 | $created = gmdate ('c', $stat['ctime']);
|
|---|
| 235 | $modified = gmdate ('c', $stat['mtime']);
|
|---|
| 236 |
|
|---|
| 237 | # Display various general properties.
|
|---|
| 238 | echo ("<displayname>$name</displayname>");
|
|---|
| 239 | echo ("<creationdate>$created</creationdate>");
|
|---|
| 240 | echo ("<getlastmodified>$modified</getlastmodified>");
|
|---|
| 241 | echo ('<supportedlock/>');
|
|---|
| 242 |
|
|---|
| 243 | # If it's a directory, say so.
|
|---|
| 244 | if ($dir)
|
|---|
| 245 | echo ('<resourcetype><collection/></resourcetype>');
|
|---|
| 246 |
|
|---|
| 247 | # Otherwise, print out statistics that only make sense on files.
|
|---|
| 248 | else {
|
|---|
| 249 | $size = $stat['size'];
|
|---|
| 250 | $mime = mime_type ($path);
|
|---|
| 251 | $etag = "{$stat['dev']}-{$stat['ino']}-{$stat['mtime']}";
|
|---|
| 252 |
|
|---|
| 253 | echo ('<resourcetype/>');
|
|---|
| 254 | echo ("<getcontentlength>$size</getcontentlength>");
|
|---|
| 255 | echo ("<getcontenttype>$mime</getcontenttype>");
|
|---|
| 256 | echo ("<getetag>$etag</getetag>");
|
|---|
| 257 | }
|
|---|
| 258 |
|
|---|
| 259 | echo ('</prop>');
|
|---|
| 260 | }
|
|---|
| 261 |
|
|---|
| 262 | echo ('</propstat>');
|
|---|
| 263 | echo ('</response>');
|
|---|
| 264 |
|
|---|
| 265 | # If this is a directory and we're set to recurse, then also print out
|
|---|
| 266 | # PROPFIND responses for all of this directory's children.
|
|---|
| 267 | if ($dir && $depth > 0)
|
|---|
| 268 | foreach (scandir ($file) as $child)
|
|---|
| 269 | if ($child != '.' && $child != '..')
|
|---|
| 270 | propfind ($root, "$path/$child", $depth - 1);
|
|---|
| 271 | }
|
|---|
| 272 |
|
|---|
| 273 | # Response handling begins here. (Determine what method is being called, and
|
|---|
| 274 | # respond appropriately.)
|
|---|
| 275 |
|
|---|
| 276 | $request_method = $_SERVER['REQUEST_METHOD'];
|
|---|
| 277 |
|
|---|
| 278 | switch ($request_method) {
|
|---|
| 279 | # PROPFIND supports a truly staggering amount of options and flags to limit
|
|---|
| 280 | # or define the various pieces of data you're interested in retrieving. We
|
|---|
| 281 | # pretend that the client has always specified "allprop" (that is, complete
|
|---|
| 282 | # information about everything), and make it the client's responsibility to
|
|---|
| 283 | # pull out less information if so desired.
|
|---|
| 284 | case 'PROPFIND':
|
|---|
| 285 | # Figure out what file they're looking at.
|
|---|
| 286 | $document_root = $_SERVER['DOCUMENT_ROOT'];
|
|---|
| 287 | $document_uri = urldecode (rtrim ($_SERVER['DOCUMENT_URI'], '/'));
|
|---|
| 288 |
|
|---|
| 289 | # Figure out what depth to recurse to. Valid values are 0, 1, and infinity.
|
|---|
| 290 | $depth = validate ($_SERVER['DEPTH'],
|
|---|
| 291 | 'infinity',
|
|---|
| 292 | array ('0' => 0, '1' => 1, 'infinity' => 'infinity'));
|
|---|
| 293 |
|
|---|
| 294 | # Choke if they specify an invalid depth.
|
|---|
| 295 | if (is_null ($depth))
|
|---|
| 296 | header ('HTTP/1.1 400 Bad Request');
|
|---|
| 297 |
|
|---|
| 298 | # "allprop" with an infinite depth is a scary proposition. Supporting it is
|
|---|
| 299 | # both optional and stupid, so I don't.
|
|---|
| 300 | else if ($depth === 'infinity') {
|
|---|
| 301 | header ('HTTP/1.1 403 Forbidden');
|
|---|
| 302 | header ('Content-Type: text/xml');
|
|---|
| 303 |
|
|---|
| 304 | echo ('<error xmlns="DAV:">');
|
|---|
| 305 | echo ('<propfind-finite-depth/>');
|
|---|
| 306 | echo ('</error>');
|
|---|
| 307 | }
|
|---|
| 308 |
|
|---|
| 309 | # Otherwise, give them the requested information.
|
|---|
| 310 | else {
|
|---|
| 311 | header ('HTTP/1.1 207 Multi-Status');
|
|---|
| 312 | header ('Content-Type: text/xml');
|
|---|
| 313 |
|
|---|
| 314 | echo ('<multistatus xmlns="DAV:">');
|
|---|
| 315 | propfind ($document_root, $document_uri, $depth);
|
|---|
| 316 | echo ('</multistatus>');
|
|---|
| 317 | }
|
|---|
| 318 |
|
|---|
| 319 | break;
|
|---|
| 320 |
|
|---|
| 321 | # We handle COPY and MOVE together, since they're almost identical. COPY
|
|---|
| 322 | # should support the Depth header, which modifies the semantics of the copy.
|
|---|
| 323 | # We ignore it and assume an infinite depth. Additionally, we do not support
|
|---|
| 324 | # copies between servers--all copies must be local. Finally, we don't
|
|---|
| 325 | # properly check for disk space errors, but a generic 500 should be good
|
|---|
| 326 | # enough.
|
|---|
| 327 | case 'COPY':
|
|---|
| 328 | case 'MOVE':
|
|---|
| 329 | # Figure out what files they're looking at.
|
|---|
| 330 | $host = $_SERVER['HOST'];
|
|---|
| 331 | $document_root = $_SERVER['DOCUMENT_ROOT'];
|
|---|
| 332 | $document_uri = urldecode (rtrim ($_SERVER['DOCUMENT_URI'], '/'));
|
|---|
| 333 |
|
|---|
| 334 | $destination = NULL;
|
|---|
| 335 | $destination_host = NULL;
|
|---|
| 336 | $url = parse_url ($_SERVER['DESTINATION']);
|
|---|
| 337 |
|
|---|
| 338 | if ($url) {
|
|---|
| 339 | $destination = urldecode (rtrim ($url['path'], '/'));
|
|---|
| 340 | $destination_host = $url['host'];
|
|---|
| 341 | }
|
|---|
| 342 |
|
|---|
| 343 | $source_exists = file_exists ($document_root . $document_uri);
|
|---|
| 344 | $destination_exists = file_exists ($document_root . $destination);
|
|---|
| 345 |
|
|---|
| 346 | # Do we overwrite the destination file?
|
|---|
| 347 | $overwrite = validate ($ENV_['OVERWRITE'],
|
|---|
| 348 | 'T',
|
|---|
| 349 | array ('T' => true, 't' => true,
|
|---|
| 350 | 'F' => false, 'f' => false));
|
|---|
| 351 |
|
|---|
| 352 | # Choke if they specify an invalid destination or depth.
|
|---|
| 353 | if (is_null ($destination))
|
|---|
| 354 | header ('HTTP/1.1 400 Bad Request');
|
|---|
| 355 |
|
|---|
| 356 | # Disallow copying/moving to a remote host.
|
|---|
| 357 | else if ($host != $destination_host)
|
|---|
| 358 | header ('HTTP/1.1 502 Bad Gateway');
|
|---|
| 359 |
|
|---|
| 360 | # Fail if the source doesn't exist.
|
|---|
| 361 | else if (!$source_exists)
|
|---|
| 362 | header ('HTTP/1.1 404 File Not Found');
|
|---|
| 363 |
|
|---|
| 364 | # Disallow copying/moving a file to itself.
|
|---|
| 365 | else if ($document_uri == $destination)
|
|---|
| 366 | header ('HTTP/1.1 403 Forbidden');
|
|---|
| 367 |
|
|---|
| 368 | # Fail if the destination file exists and they said they didn't want to
|
|---|
| 369 | # overwrite it.
|
|---|
| 370 | else if ($overwrite == false && $destination_exists)
|
|---|
| 371 | header ('HTTP/1.1 412 Precondition Failed');
|
|---|
| 372 |
|
|---|
| 373 | # If we're doing a copy, copy the files.
|
|---|
| 374 | # FIXME: We resort to shell since PHP doesn't support a recursive copy and
|
|---|
| 375 | # I didn't want to bother implementing it (though it would be a good idea
|
|---|
| 376 | # to do so at some point).
|
|---|
| 377 | else if ($request_method == 'COPY' &&
|
|---|
| 378 | !recursive_copy ($document_root . $document_uri,
|
|---|
| 379 | $document_root . $destination))
|
|---|
| 380 | header ('500 Internal Server Error');
|
|---|
| 381 |
|
|---|
| 382 | # If we're doing a move, move the files.
|
|---|
| 383 | else if ($request_method == 'MOVE' &&
|
|---|
| 384 | !rename ($document_root . $document_uri,
|
|---|
| 385 | $document_root . $destination))
|
|---|
| 386 | header ('500 Internal Server Error');
|
|---|
| 387 |
|
|---|
| 388 | else
|
|---|
| 389 | header ($destination_exists ?
|
|---|
| 390 | 'HTTP/1.1 204 No Content' :
|
|---|
| 391 | 'HTTP/1.1 201 Created');
|
|---|
| 392 |
|
|---|
| 393 | break;
|
|---|
| 394 |
|
|---|
| 395 | # In case they ask, pretend that we actually know what we're talking about.
|
|---|
| 396 | # (The only methods conspicuously absent are PROPPATCH, LOCK, and UNLOCK. We
|
|---|
| 397 | # tell the clients that we don't support them.)
|
|---|
| 398 | case 'OPTIONS':
|
|---|
| 399 | header ('HTTP/1.1 200 OK');
|
|---|
| 400 | header ('Allow: OPTIONS, GET, HEAD, POST, PUT, DELETE, MKCOL, COPY, MOVE, PROPFIND');
|
|---|
| 401 | header ('DAV: 1, 2');
|
|---|
| 402 | break;
|
|---|
| 403 |
|
|---|
| 404 | # The following methods are valid but unimplemented. In theory, NGINX is
|
|---|
| 405 | # supposed to handle each of them anyway.
|
|---|
| 406 | case 'GET':
|
|---|
| 407 | case 'HEAD':
|
|---|
| 408 | case 'POST':
|
|---|
| 409 | case 'PUT':
|
|---|
| 410 | case 'DELETE':
|
|---|
| 411 | case 'MKCOL':
|
|---|
| 412 | header ('HTTP/1.1 501 Not Implemented');
|
|---|
| 413 | break;
|
|---|
| 414 |
|
|---|
| 415 | # Any other methods are unknown.
|
|---|
| 416 | default:
|
|---|
| 417 | header ('HTTP/1.1 400 Bad Request');
|
|---|
| 418 | break;
|
|---|
| 419 | }
|
|---|
| 420 |
|
|---|
| 421 | ?>
|
|---|