/libs/bolt80/stupidhttp/lib/StupidHttp/VirtualFileSystem.php

https://bitbucket.org/ndj/piecrust · PHP · 233 lines · 179 code · 19 blank · 35 comment · 27 complexity · b0e432e1d5be7a7c8cfa8edd5d743538 MD5 · raw file

  1. <?php
  2. /**
  3. * A class responsible for handling static files served by the server.
  4. */
  5. class StupidHttp_VirtualFileSystem
  6. {
  7. protected $documentRoot;
  8. protected $mounts;
  9. protected $mimeTypes;
  10. /**
  11. * Gets the root directory for the static files.
  12. */
  13. public function getDocumentRoot()
  14. {
  15. return $this->documentRoot;
  16. }
  17. /**
  18. * Gets the mime types to be used by the server.
  19. */
  20. public function getMimeTypes()
  21. {
  22. $this->ensureMimeTypes();
  23. return $this->mimeTypes;
  24. }
  25. /**
  26. * Sets the mime types to be used by the server.
  27. */
  28. public function setMimeTypes($mimeTypes)
  29. {
  30. $this->mimeTypes = $mimeTypes;
  31. }
  32. /**
  33. * Sets a specific mime type for a given file extension.
  34. */
  35. public function setMimeType($extension, $mimeType)
  36. {
  37. $this->ensureMimeTypes();
  38. $this->mimeTypes[$extension] = $mimeType;
  39. }
  40. /**
  41. * Builds a new instance of StupidHttp_VirtualFileSystem.
  42. */
  43. public function __construct($documentRoot)
  44. {
  45. if ($documentRoot != null)
  46. {
  47. if (!is_dir($documentRoot))
  48. {
  49. throw new StupidHttp_WebException("The given document root is not valid: " . $documentRoot);
  50. }
  51. $this->documentRoot = rtrim($documentRoot, '/\\');
  52. }
  53. else
  54. {
  55. $this->documentRoot = null;
  56. }
  57. $this->mounts = array();
  58. }
  59. /**
  60. * Adds a virtual mount point to the file system.
  61. */
  62. public function addMountPoint($directory, $alias)
  63. {
  64. $this->mounts[$alias] = rtrim($directory, '/\\');
  65. }
  66. /**
  67. * Returns a web response that corresponds to serving a given static file.
  68. */
  69. public function serveDocument(StupidHttp_WebRequest $request, $documentPath)
  70. {
  71. // First, check for timestamp if possible.
  72. $serverTimestamp = filemtime($documentPath);
  73. $ifModifiedSince = $request->getHeader('If-Modified-Since');
  74. if ($ifModifiedSince != null)
  75. {
  76. $clientTimestamp = strtotime($ifModifiedSince);
  77. if ($clientTimestamp > $serverTimestamp)
  78. {
  79. return new StupidHttp_WebResponse(304);
  80. }
  81. }
  82. // ...otherwise, check for similar checksum.
  83. $documentSize = filesize($documentPath);
  84. if ($documentSize == 0)
  85. {
  86. return new StupidHttp_WebResponse(200);
  87. }
  88. $documentHandle = fopen($documentPath, "rb");
  89. $contents = fread($documentHandle, $documentSize);
  90. fclose($documentHandle);
  91. if ($contents === false)
  92. {
  93. throw new StupidHttp_WebException('Error reading file: ' . $documentPath, 500);
  94. }
  95. $contentsHash = md5($contents);
  96. $ifNoneMatch = $request->getHeader('If-None-Match');
  97. if ($ifNoneMatch != null)
  98. {
  99. if ($ifNoneMatch == $contentsHash)
  100. {
  101. return new StupidHttp_WebResponse(304);
  102. }
  103. }
  104. // ...ok, let's send the file.
  105. $this->ensureMimeTypes();
  106. $extension = pathinfo($documentPath, PATHINFO_EXTENSION);
  107. $headers = array(
  108. 'Content-Length' => $documentSize,
  109. 'Content-MD5' => base64_encode($contentsHash),
  110. 'Content-Type' => (
  111. isset($this->mimeTypes[$extension]) ?
  112. $this->mimeTypes[$extension] :
  113. 'text/plain'
  114. ),
  115. 'ETag' => $contentsHash,
  116. 'Last-Modified' => date("D, d M Y H:i:s T", filemtime($documentPath))
  117. );
  118. return new StupidHttp_WebResponse(200, $headers, $contents);
  119. }
  120. /**
  121. * Returns a web response that corresponds to serving the contents of a
  122. * given directory.
  123. */
  124. public function serveDirectory(StupidHttp_WebRequest $request, $documentPath)
  125. {
  126. $headers = array();
  127. $contents = '<ul>' . PHP_EOL;
  128. foreach (new DirectoryIterator($documentPath) as $entry)
  129. {
  130. $contents .= '<li>' . $entry->getFilename() . '</li>' . PHP_EOL;
  131. }
  132. $contents .= '</ul>' . PHP_EOL;
  133. $replacements = array(
  134. '%path%' => $documentPath,
  135. '%contents%' => $contents
  136. );
  137. $body = file_get_contents(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'directory-listing.html');
  138. $body = str_replace(array_keys($replacements), array_values($replacements), $body);
  139. return new StupidHttp_WebResponse(200, $headers, $body);
  140. }
  141. public function getDocumentPath($uri)
  142. {
  143. if ($this->documentRoot == null)
  144. return null;
  145. $root = $this->documentRoot;
  146. $uri = rawurldecode($uri);
  147. $secondSlash = strpos($uri, '/', 1);
  148. if ($secondSlash !== false)
  149. {
  150. $firstDir = substr($uri, 1, $secondSlash - 1);
  151. if (isset($this->mounts[$firstDir]))
  152. {
  153. $root = $this->mounts[$firstDir];
  154. $uri = substr($uri, $secondSlash);
  155. }
  156. }
  157. if ($root === false)
  158. return false;
  159. return $root . str_replace('/', DIRECTORY_SEPARATOR, $uri);
  160. }
  161. /**
  162. * Finds the index document for a given directory (e.g. `index.html`).
  163. */
  164. public function getIndexDocument($path)
  165. {
  166. static $indexDocuments = array(
  167. 'index.htm',
  168. 'index.html'
  169. );
  170. $path = rtrim($path, '/\\') . DIRECTORY_SEPARATOR;
  171. foreach ($indexDocuments as $doc)
  172. {
  173. if (is_file($path . $doc))
  174. {
  175. return $path . $doc;
  176. }
  177. }
  178. return null;
  179. }
  180. protected function ensureMimeTypes()
  181. {
  182. if ($this->mimeTypes !== null)
  183. return;
  184. // Lazy-load the MIME types.
  185. $mimeTypesPath = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mime.types';
  186. $handle = @fopen($mimeTypesPath, "r");
  187. if ($handle)
  188. {
  189. $hasError = false;
  190. $this->mimeTypes = array();
  191. while (($buffer = fgets($handle, 4096)) !== false)
  192. {
  193. $tokens = preg_split('/\s+/', $buffer, -1, PREG_SPLIT_NO_EMPTY);
  194. if (count($tokens) > 1)
  195. {
  196. for ($i = 1; $i < count($tokens); $i++)
  197. {
  198. $this->mimeTypes[$tokens[$i]] = $tokens[0];
  199. }
  200. }
  201. }
  202. if (!feof($handle))
  203. $hasError = true;
  204. fclose($handle);
  205. if ($hasError)
  206. throw new StupidHttp_WebException("An error occured while reading the mime.types file: " . $mimeTypesPath);
  207. }
  208. else
  209. {
  210. throw new StupidHttp_WebException("Can't find the 'mime.types' file: " . $mimeTypesPath);
  211. }
  212. }
  213. }