1<?php 2 3/** 4 * Ensures that files are not executable unless they are either binary or 5 * contain a shebang. 6 */ 7final class ArcanistChmodLinter extends ArcanistLinter { 8 9 const LINT_INVALID_EXECUTABLE = 1; 10 11 public function getInfoName() { 12 return 'Chmod'; 13 } 14 15 public function getInfoDescription() { 16 return pht( 17 'Checks the permissions on files and ensures that they are not made to '. 18 'be executable unnecessarily. In particular, a file should not be '. 19 'executable unless it is either binary or contain a shebang.'); 20 } 21 22 public function getLinterName() { 23 return 'CHMOD'; 24 } 25 26 public function getLinterConfigurationName() { 27 return 'chmod'; 28 } 29 30 public function getLintNameMap() { 31 return array( 32 self::LINT_INVALID_EXECUTABLE => pht('Invalid Executable'), 33 ); 34 } 35 36 protected function getDefaultMessageSeverity($code) { 37 return ArcanistLintSeverity::SEVERITY_WARNING; 38 } 39 40 protected function shouldLintBinaryFiles() { 41 return true; 42 } 43 44 public function lintPath($path) { 45 $engine = $this->getEngine(); 46 47 if (is_executable($engine->getFilePathOnDisk($path))) { 48 if ($engine->isBinaryFile($path)) { 49 $mime = Filesystem::getMimeType($engine->getFilePathOnDisk($path)); 50 51 switch ($mime) { 52 // Archives 53 case 'application/jar': 54 case 'application/java-archive': 55 case 'application/x-bzip2': 56 case 'application/x-gzip': 57 case 'application/x-rar-compressed': 58 case 'application/x-tar': 59 case 'application/zip': 60 61 // Audio 62 case 'audio/midi': 63 case 'audio/mpeg': 64 case 'audio/mp4': 65 case 'audio/x-wav': 66 67 // Fonts 68 case 'application/vnd.ms-fontobject': 69 case 'application/x-font-ttf': 70 case 'application/x-woff': 71 72 // Images 73 case 'application/x-shockwave-flash': 74 case 'image/gif': 75 case 'image/jpeg': 76 case 'image/png': 77 case 'image/tiff': 78 case 'image/x-icon': 79 case 'image/x-ms-bmp': 80 81 // Miscellaneous 82 case 'application/msword': 83 case 'application/pdf': 84 case 'application/postscript': 85 case 'application/rtf': 86 case 'application/vnd.ms-excel': 87 case 'application/vnd.ms-powerpoint': 88 89 // Video 90 case 'video/mpeg': 91 case 'video/quicktime': 92 case 'video/x-flv': 93 case 'video/x-msvideo': 94 case 'video/x-ms-wmv': 95 96 $this->raiseLintAtPath( 97 self::LINT_INVALID_EXECUTABLE, 98 pht("'%s' files should not be executable.", $mime)); 99 return; 100 101 default: 102 // Path is a binary file, which makes it a valid executable. 103 return; 104 } 105 } else if ($this->getShebang($path)) { 106 // Path contains a shebang, which makes it a valid executable. 107 return; 108 } else { 109 $this->raiseLintAtPath( 110 self::LINT_INVALID_EXECUTABLE, 111 pht( 112 'Executable files should either be binary or contain a shebang.')); 113 } 114 } 115 } 116 117 /** 118 * Returns the path's shebang. 119 * 120 * @param string 121 * @return string|null 122 */ 123 private function getShebang($path) { 124 $line = head(phutil_split_lines($this->getEngine()->loadData($path), true)); 125 126 $matches = array(); 127 if (preg_match('/^#!(.*)$/', $line, $matches)) { 128 return $matches[1]; 129 } else { 130 return null; 131 } 132 } 133 134} 135