PageRenderTime 57ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 1ms

/tags/stable-1.0.3/Server/Config/PHPConfigHelper.cs

#
C# | 1267 lines | 1040 code | 163 blank | 64 comment | 182 complexity | 9756d764572f6e574f9570314e918e5d MD5 | raw file
Possible License(s): CC-BY-SA-3.0

Large files files are truncated, but you can click here to view the full file

  1. //-----------------------------------------------------------------------
  2. // <copyright>
  3. // Copyright (C) Ruslan Yakushev for the PHP Manager for IIS project.
  4. //
  5. // This file is subject to the terms and conditions of the Microsoft Public License (MS-PL).
  6. // See http://www.microsoft.com/opensource/licenses.mspx#Ms-PL for more details.
  7. // </copyright>
  8. //-----------------------------------------------------------------------
  9. using System;
  10. using System.Collections;
  11. using System.Collections.Generic;
  12. using System.Diagnostics;
  13. using System.Globalization;
  14. using System.IO;
  15. using Microsoft.Web.Administration;
  16. using Microsoft.Web.Management.Server;
  17. using Web.Management.PHP.DefaultDocument;
  18. using Web.Management.PHP.FastCgi;
  19. using Web.Management.PHP.Handlers;
  20. namespace Web.Management.PHP.Config
  21. {
  22. /// <summary>
  23. /// Provides functions to register PHP with IIS and to manage IIS and PHP configuration.
  24. /// </summary>
  25. internal sealed class PHPConfigHelper
  26. {
  27. private ManagementUnit _managementUnit;
  28. private ApplicationElement _currentFastCgiApplication;
  29. private HandlerElement _currentPHPHandler;
  30. private HandlersCollection _handlersCollection;
  31. private FastCgiApplicationCollection _fastCgiApplicationCollection;
  32. private FilesCollection _defaultDocumentCollection;
  33. private string _phpIniFilePath;
  34. private string _phpDirectory;
  35. private PHPRegistrationType _registrationType;
  36. public PHPConfigHelper(ManagementUnit mgmtUnit)
  37. {
  38. _managementUnit = mgmtUnit;
  39. Initialize();
  40. }
  41. public string PHPDirectory
  42. {
  43. get
  44. {
  45. return _phpDirectory;
  46. }
  47. }
  48. public string PHPIniFilePath
  49. {
  50. get
  51. {
  52. return _phpIniFilePath;
  53. }
  54. }
  55. private void ApplyRecommendedFastCgiSettings(ArrayList configIssueIndexes)
  56. {
  57. bool iisChangeHappened = false;
  58. foreach (int configIssueIndex in configIssueIndexes)
  59. {
  60. switch (configIssueIndex)
  61. {
  62. case PHPConfigIssueIndex.DefaultDocument:
  63. {
  64. iisChangeHappened = ChangeDefaultDocument() || iisChangeHappened;
  65. break;
  66. }
  67. case PHPConfigIssueIndex.ResourceType:
  68. {
  69. iisChangeHappened = ChangeResourceType() || iisChangeHappened;
  70. break;
  71. }
  72. case PHPConfigIssueIndex.PHPMaxRequests:
  73. {
  74. iisChangeHappened = ChangePHPMaxRequests() || iisChangeHappened;
  75. break;
  76. }
  77. case PHPConfigIssueIndex.PHPRC:
  78. {
  79. iisChangeHappened = ChangePHPRC() || iisChangeHappened;
  80. break;
  81. }
  82. case PHPConfigIssueIndex.MonitorChangesTo:
  83. {
  84. iisChangeHappened = ChangeMonitorChanges() || iisChangeHappened;
  85. break;
  86. }
  87. }
  88. }
  89. if (iisChangeHappened)
  90. {
  91. _managementUnit.Update();
  92. }
  93. }
  94. private void ApplyRecommendedPHPIniSettings(ArrayList configIssueIndexes)
  95. {
  96. PHPIniFile file = new PHPIniFile(PHPIniFilePath);
  97. file.Parse();
  98. List<PHPIniSetting> settings = new List<PHPIniSetting>();
  99. foreach (int configIssueIndex in configIssueIndexes)
  100. {
  101. switch (configIssueIndex)
  102. {
  103. case PHPConfigIssueIndex.ExtensionDir:
  104. {
  105. settings.Add(GetToApplyExtensionDir());
  106. break;
  107. }
  108. case PHPConfigIssueIndex.LogErrors:
  109. {
  110. settings.Add(GetToApplyLogErrors());
  111. break;
  112. }
  113. case PHPConfigIssueIndex.ErrorLog:
  114. {
  115. settings.Add(GetToApplyErrorLog(file));
  116. break;
  117. }
  118. case PHPConfigIssueIndex.SessionPath:
  119. {
  120. settings.Add(GetToApplySessionPath(file));
  121. break;
  122. }
  123. case PHPConfigIssueIndex.UploadDir:
  124. {
  125. settings.Add(GetToApplyUploadTmpDir(file));
  126. break;
  127. }
  128. case PHPConfigIssueIndex.CgiForceRedirect:
  129. {
  130. settings.Add(GetToApplyCgiForceRedirect());
  131. break;
  132. }
  133. case PHPConfigIssueIndex.CgiPathInfo:
  134. {
  135. settings.Add(GetToApplyCgiPathInfo());
  136. break;
  137. }
  138. case PHPConfigIssueIndex.FastCgiImpersonation:
  139. {
  140. settings.Add(GetToApplyFastCgiImpersonate());
  141. break;
  142. }
  143. }
  144. }
  145. if (settings.Count > 0)
  146. {
  147. file.AddOrUpdateSettings(settings);
  148. file.Save(PHPIniFilePath);
  149. }
  150. }
  151. public void ApplyRecommendedSettings(ArrayList configIssueIndexes)
  152. {
  153. // Check if PHP is not registered
  154. if (!IsPHPRegistered())
  155. {
  156. throw new InvalidOperationException("Cannot apply recommended settings because PHP is not registered properly");
  157. }
  158. ApplyRecommendedFastCgiSettings(configIssueIndexes);
  159. ApplyRecommendedPHPIniSettings(configIssueIndexes);
  160. }
  161. private bool ChangeDefaultDocument()
  162. {
  163. bool changeHappened = false;
  164. FileElement fileElement = _defaultDocumentCollection["index.php"];
  165. if (fileElement == null)
  166. {
  167. fileElement = _defaultDocumentCollection.CreateElement();
  168. fileElement.Value = "index.php";
  169. _defaultDocumentCollection.AddAt(0, fileElement);
  170. changeHappened = true;
  171. }
  172. else if (_defaultDocumentCollection.IndexOf(fileElement) > 0)
  173. {
  174. CopyIneritedDefaultDocs();
  175. MoveIndexPhpOnTop();
  176. changeHappened = true;
  177. }
  178. return changeHappened;
  179. }
  180. private bool ChangeMonitorChanges()
  181. {
  182. bool changeHappened = false;
  183. // If monitorChangesTo is supported then set it
  184. if (_currentFastCgiApplication.MonitorChangesToExists())
  185. {
  186. _currentFastCgiApplication.MonitorChangesTo = PHPIniFilePath;
  187. changeHappened = true;
  188. }
  189. return changeHappened;
  190. }
  191. private bool ChangePHPMaxRequests()
  192. {
  193. // Set PHP_FCGI_MAX_REQUESTS to be equal to instanceMaxRequests
  194. EnvironmentVariableElement envVariableElement = _currentFastCgiApplication.EnvironmentVariables["PHP_FCGI_MAX_REQUESTS"];
  195. if (envVariableElement == null)
  196. {
  197. _currentFastCgiApplication.EnvironmentVariables.Add("PHP_FCGI_MAX_REQUESTS", _currentFastCgiApplication.InstanceMaxRequests.ToString(CultureInfo.InvariantCulture));
  198. }
  199. else
  200. {
  201. envVariableElement.Value = _currentFastCgiApplication.InstanceMaxRequests.ToString(CultureInfo.InvariantCulture);
  202. }
  203. return true;
  204. }
  205. private bool ChangePHPRC()
  206. {
  207. bool changeHappened = false;
  208. // Set PHPRC
  209. EnvironmentVariableElement envVariableElement = _currentFastCgiApplication.EnvironmentVariables["PHPRC"];
  210. if (envVariableElement == null)
  211. {
  212. _currentFastCgiApplication.EnvironmentVariables.Add("PHPRC", PHPDirectory);
  213. changeHappened = true;
  214. }
  215. else
  216. {
  217. // If PHPRC does not point to a valid directory with php.ini ...
  218. string path = Path.Combine(envVariableElement.Value, "php.ini");
  219. if (!File.Exists(path))
  220. {
  221. // ... then set it to current PHP directory
  222. envVariableElement.Value = PHPDirectory;
  223. changeHappened = true;
  224. }
  225. }
  226. return changeHappened;
  227. }
  228. private bool ChangeResourceType()
  229. {
  230. bool changeHappened = false;
  231. if (_currentPHPHandler.ResourceType != ResourceType.Either)
  232. {
  233. _currentPHPHandler.ResourceType = ResourceType.Either;
  234. changeHappened = true;
  235. }
  236. return changeHappened;
  237. }
  238. private void CopyIneritedDefaultDocs()
  239. {
  240. if (_managementUnit.ConfigurationPath.PathType == ConfigurationPathType.Server)
  241. {
  242. return;
  243. }
  244. FileElement[] list = new FileElement[_defaultDocumentCollection.Count];
  245. ((ICollection)_defaultDocumentCollection).CopyTo(list, 0);
  246. _defaultDocumentCollection.Clear();
  247. foreach (FileElement f in list)
  248. {
  249. _defaultDocumentCollection.AddCopy(f);
  250. }
  251. }
  252. private void CopyInheritedHandlers()
  253. {
  254. if (_managementUnit.ConfigurationPath.PathType == ConfigurationPathType.Server)
  255. {
  256. return;
  257. }
  258. HandlerElement[] list = new HandlerElement[_handlersCollection.Count];
  259. ((ICollection)_handlersCollection).CopyTo(list, 0);
  260. _handlersCollection.Clear();
  261. foreach (HandlerElement handler in list)
  262. {
  263. _handlersCollection.AddCopy(handler);
  264. }
  265. }
  266. private static string DoubleQuotesWrap(string value)
  267. {
  268. return '"' + value + '"';
  269. }
  270. private static string EnsureTrailingBackslash(string str)
  271. {
  272. if (!str.EndsWith(@"\", StringComparison.Ordinal))
  273. {
  274. return str + @"\";
  275. }
  276. return str;
  277. }
  278. private static string GenerateHandlerName(HandlersCollection collection, string phpVersion)
  279. {
  280. string prefix = "php-" + phpVersion;
  281. string name = prefix;
  282. for (int i = 1; true; i++)
  283. {
  284. if (collection[name] != null)
  285. {
  286. name = prefix + "_" + i.ToString(CultureInfo.InvariantCulture);
  287. }
  288. else
  289. {
  290. break;
  291. }
  292. }
  293. return name;
  294. }
  295. public ArrayList GetAllPHPVersions()
  296. {
  297. ArrayList result = new ArrayList();
  298. foreach (HandlerElement handler in _handlersCollection)
  299. {
  300. if (String.Equals(handler.Path, "*.php", StringComparison.OrdinalIgnoreCase))
  301. {
  302. if (String.Equals(handler.Modules, "FastCgiModule", StringComparison.OrdinalIgnoreCase) &&
  303. File.Exists(handler.Executable))
  304. {
  305. result.Add(new string[] { handler.Name, handler.Executable, GetPHPExecutableVersion(handler.Executable) });
  306. }
  307. }
  308. }
  309. return result;
  310. }
  311. public PHPConfigInfo GetPHPConfigInfo()
  312. {
  313. PHPConfigInfo configInfo = new PHPConfigInfo();
  314. // If PHP is not registered properly then just return information about
  315. // how it registered.
  316. if (!IsPHPRegistered())
  317. {
  318. configInfo.RegistrationType = _registrationType;
  319. return configInfo;
  320. }
  321. configInfo.RegistrationType = _registrationType;
  322. configInfo.HandlerName = _currentPHPHandler.Name;
  323. configInfo.Executable = _currentPHPHandler.Executable;
  324. configInfo.Version = GetPHPExecutableVersion(_currentPHPHandler.Executable);
  325. if (String.IsNullOrEmpty(PHPIniFilePath))
  326. {
  327. throw new FileNotFoundException("php.ini file does not exist");
  328. }
  329. configInfo.PHPIniFilePath = PHPIniFilePath;
  330. PHPIniFile file = new PHPIniFile(PHPIniFilePath);
  331. file.Parse();
  332. PHPIniSetting setting = file.GetSetting("error_log");
  333. if (setting != null)
  334. {
  335. configInfo.ErrorLog = setting.TrimmedValue;
  336. }
  337. else
  338. {
  339. configInfo.ErrorLog = String.Empty;
  340. }
  341. configInfo.EnabledExtCount = file.GetEnabledExtensionsCount();
  342. configInfo.InstalledExtCount = file.Extensions.Count;
  343. ICollection issues = ValidateConfiguration(file);
  344. configInfo.IsConfigOptimal = (issues.Count == 0);
  345. return configInfo;
  346. }
  347. private string GetPHPDirectory()
  348. {
  349. string phpDirectory = Path.GetDirectoryName(Environment.ExpandEnvironmentVariables(_currentPHPHandler.Executable));
  350. return EnsureTrailingBackslash(phpDirectory);
  351. }
  352. private static string GetPHPExecutableVersion(string phpexePath)
  353. {
  354. FileVersionInfo fileVersionInfo = FileVersionInfo.GetVersionInfo(phpexePath);
  355. return fileVersionInfo.ProductVersion;
  356. }
  357. private string GetPHPIniFilePath()
  358. {
  359. // If PHPRC environment variable is set then use the path specified there.
  360. // Otherwise use the same path as where PHP executable is located.
  361. string directoryPath = String.Empty;
  362. EnvironmentVariableElement phpRcElement = _currentFastCgiApplication.EnvironmentVariables["PHPRC"];
  363. if (phpRcElement != null && !String.IsNullOrEmpty(phpRcElement.Value))
  364. {
  365. directoryPath = phpRcElement.Value;
  366. }
  367. else
  368. {
  369. directoryPath = Path.GetDirectoryName(_currentPHPHandler.Executable);
  370. }
  371. string phpIniPath = Path.Combine(directoryPath, "php.ini");
  372. if (File.Exists(phpIniPath))
  373. {
  374. return phpIniPath;
  375. }
  376. return String.Empty;
  377. }
  378. private static PHPIniSetting GetToApplyCgiForceRedirect()
  379. {
  380. return new PHPIniSetting("cgi.force_redirect", "0", "PHP");
  381. }
  382. private static PHPIniSetting GetToApplyCgiPathInfo()
  383. {
  384. return new PHPIniSetting("cgi.fix_pathinfo", "1", "PHP");
  385. }
  386. private PHPIniSetting GetToApplyErrorLog(PHPIniFile file)
  387. {
  388. string handlerName = _currentPHPHandler.Name;
  389. PHPIniSetting setting = file.GetSetting("error_log");
  390. if (setting == null || !IsAbsoluteFilePath(setting.TrimmedValue, true))
  391. {
  392. string value = Path.Combine(Environment.ExpandEnvironmentVariables(@"%WINDIR%\Temp\"), handlerName + "_errors.log");
  393. setting = new PHPIniSetting("error_log", DoubleQuotesWrap(value), "PHP");
  394. }
  395. return setting;
  396. }
  397. private PHPIniSetting GetToApplyExtensionDir()
  398. {
  399. string value = EnsureTrailingBackslash(Path.Combine(PHPDirectory, "ext"));
  400. return new PHPIniSetting("extension_dir", DoubleQuotesWrap(value), "PHP");
  401. }
  402. private static PHPIniSetting GetToApplyFastCgiImpersonate()
  403. {
  404. return new PHPIniSetting("fastcgi.impersonate", "1", "PHP");
  405. }
  406. private static PHPIniSetting GetToApplyLogErrors()
  407. {
  408. return new PHPIniSetting("log_errors", "On", "PHP");
  409. }
  410. private static PHPIniSetting GetToApplySessionPath(PHPIniFile file)
  411. {
  412. PHPIniSetting setting = file.GetSetting("session.save_path");
  413. if (setting == null || !IsAbsoluteFilePath(setting.TrimmedValue, false))
  414. {
  415. string value = Environment.ExpandEnvironmentVariables(@"%WINDIR%\Temp\");
  416. setting = new PHPIniSetting("session.save_path", DoubleQuotesWrap(value), "Session");
  417. }
  418. return setting;
  419. }
  420. private static PHPIniSetting GetToApplyUploadTmpDir(PHPIniFile file)
  421. {
  422. PHPIniSetting setting = file.GetSetting("upload_tmp_dir");
  423. if (setting == null || !IsAbsoluteFilePath(setting.TrimmedValue, false))
  424. {
  425. string value = Environment.ExpandEnvironmentVariables(@"%WINDIR%\Temp\");
  426. setting = new PHPIniSetting("upload_tmp_dir", DoubleQuotesWrap(value), "PHP");
  427. }
  428. return setting;
  429. }
  430. private void Initialize()
  431. {
  432. // Get the handlers collection
  433. ManagementConfiguration config = _managementUnit.Configuration;
  434. HandlersSection handlersSection = (HandlersSection)config.GetSection("system.webServer/handlers", typeof(HandlersSection));
  435. _handlersCollection = handlersSection.Handlers;
  436. // Get the Default document collection
  437. DefaultDocumentSection defaultDocumentSection = (DefaultDocumentSection)config.GetSection("system.webServer/defaultDocument", typeof(DefaultDocumentSection));
  438. _defaultDocumentCollection = defaultDocumentSection.Files;
  439. // Get the FastCgi application collection
  440. Configuration appHostConfig = _managementUnit.ServerManager.GetApplicationHostConfiguration();
  441. FastCgiSection fastCgiSection = (FastCgiSection)appHostConfig.GetSection("system.webServer/fastCgi", typeof(FastCgiSection));
  442. _fastCgiApplicationCollection = fastCgiSection.Applications;
  443. // Assume by default that PHP is not registered
  444. _registrationType = PHPRegistrationType.None;
  445. // Find the currently active PHP handler and FastCGI application
  446. HandlerElement handler = _handlersCollection.GetActiveHandler("*.php");
  447. if (handler != null)
  448. {
  449. if (String.Equals(handler.Modules, "FastCgiModule", StringComparison.OrdinalIgnoreCase))
  450. {
  451. _registrationType = PHPRegistrationType.FastCgi;
  452. }
  453. else if (String.Equals(handler.Modules, "CgiModule", StringComparison.OrdinalIgnoreCase))
  454. {
  455. _registrationType = PHPRegistrationType.Cgi;
  456. }
  457. else if (String.Equals(handler.Modules, "IsapiModule", StringComparison.OrdinalIgnoreCase))
  458. {
  459. _registrationType = PHPRegistrationType.Isapi;
  460. }
  461. if (_registrationType == PHPRegistrationType.FastCgi)
  462. {
  463. ApplicationElement fastCgiApplication = _fastCgiApplicationCollection.GetApplication(handler.Executable, handler.Arguments);
  464. if (fastCgiApplication != null)
  465. {
  466. _currentPHPHandler = handler;
  467. _currentFastCgiApplication = fastCgiApplication;
  468. _phpIniFilePath = GetPHPIniFilePath();
  469. _phpDirectory = GetPHPDirectory();
  470. }
  471. else
  472. {
  473. _registrationType = PHPRegistrationType.None;
  474. }
  475. }
  476. }
  477. }
  478. private static bool IsAbsoluteFilePath(string path, bool isFile)
  479. {
  480. string directory = Environment.ExpandEnvironmentVariables(path);
  481. if (Path.IsPathRooted(path))
  482. {
  483. if (isFile)
  484. {
  485. directory = Path.GetDirectoryName(directory);
  486. }
  487. return Directory.Exists(directory);
  488. }
  489. return false;
  490. }
  491. private bool IsPHPRegistered()
  492. {
  493. return (_registrationType == PHPRegistrationType.FastCgi);
  494. }
  495. private HandlerElement MakeHandlerActive(string handlerName)
  496. {
  497. // We have to look up the handler elements by name because we may be working
  498. // on the copy of the handlers collection.
  499. HandlerElement handlerElement = _handlersCollection[handlerName];
  500. HandlerElement activeHandlerElement = _handlersCollection[_currentPHPHandler.Name];
  501. Debug.Assert(handlerElement != null && activeHandlerElement != null);
  502. int activeHandlerIndex = _handlersCollection.IndexOf(activeHandlerElement);
  503. _handlersCollection.Remove(handlerElement);
  504. return _handlersCollection.AddCopyAt(activeHandlerIndex, handlerElement);
  505. }
  506. private void MakeRecommendedFastCgiChanges()
  507. {
  508. bool iisChangeHappened = false;
  509. iisChangeHappened = ChangeDefaultDocument() || iisChangeHappened;
  510. iisChangeHappened = ChangeResourceType() || iisChangeHappened;
  511. iisChangeHappened = ChangePHPMaxRequests() || iisChangeHappened;
  512. iisChangeHappened = ChangePHPRC() || iisChangeHappened;
  513. iisChangeHappened = ChangeMonitorChanges() || iisChangeHappened;
  514. if (iisChangeHappened)
  515. {
  516. _managementUnit.Update();
  517. }
  518. }
  519. private void MakeRecommendedPHPIniChanges()
  520. {
  521. PHPIniFile file = new PHPIniFile(PHPIniFilePath);
  522. file.Parse();
  523. // Set the recommended php.ini settings
  524. List<PHPIniSetting> settings = new List<PHPIniSetting>();
  525. settings.Add(GetToApplyExtensionDir());
  526. settings.Add(GetToApplyLogErrors());
  527. settings.Add(GetToApplyErrorLog(file));
  528. settings.Add(GetToApplySessionPath(file));
  529. settings.Add(GetToApplyUploadTmpDir(file));
  530. settings.Add(GetToApplyCgiForceRedirect());
  531. settings.Add(GetToApplyCgiPathInfo());
  532. settings.Add(GetToApplyFastCgiImpersonate());
  533. settings.Add(new PHPIniSetting("fastcgi.logging", "0", "PHP"));
  534. settings.Add(new PHPIniSetting("max_execution_time", "300", "PHP"));
  535. settings.Add(new PHPIniSetting("display_errors", "Off", "PHP"));
  536. // Enable the most common PHP extensions
  537. List<PHPIniExtension> extensions = new List<PHPIniExtension>();
  538. extensions.Add(new PHPIniExtension("php_curl.dll", true));
  539. extensions.Add(new PHPIniExtension("php_gd2.dll", true));
  540. extensions.Add(new PHPIniExtension("php_gettext.dll", true));
  541. extensions.Add(new PHPIniExtension("php_mysql.dll", true));
  542. extensions.Add(new PHPIniExtension("php_mysqli.dll", true));
  543. extensions.Add(new PHPIniExtension("php_mbstring.dll", true));
  544. extensions.Add(new PHPIniExtension("php_openssl.dll", true));
  545. extensions.Add(new PHPIniExtension("php_soap.dll", true));
  546. extensions.Add(new PHPIniExtension("php_xmlrpc.dll", true));
  547. file.UpdateExtensions(extensions);
  548. file.AddOrUpdateSettings(settings);
  549. file.Save(PHPIniFilePath);
  550. }
  551. private FileElement MoveIndexPhpOnTop()
  552. {
  553. FileElement fileElement = _defaultDocumentCollection["index.php"];
  554. Debug.Assert(fileElement != null);
  555. _defaultDocumentCollection.Remove(fileElement);
  556. return _defaultDocumentCollection.AddCopyAt(0, fileElement);
  557. }
  558. private static string PreparePHPIniFile(string phpDir)
  559. {
  560. // Check for existence of php.ini file. If it does not exist then copy php.ini-recommended
  561. // or php.ini-production to it
  562. string phpIniFilePath = Path.Combine(phpDir, "php.ini");
  563. if (!File.Exists(phpIniFilePath))
  564. {
  565. string phpIniRecommendedPath = Path.Combine(phpDir, "php.ini-recommended");
  566. string phpIniProductionPath = Path.Combine(phpDir, "php.ini-production");
  567. if (File.Exists(phpIniRecommendedPath))
  568. {
  569. File.Copy(phpIniRecommendedPath, phpIniFilePath);
  570. }
  571. else if (File.Exists(phpIniProductionPath))
  572. {
  573. File.Copy(phpIniProductionPath, phpIniFilePath);
  574. }
  575. else
  576. {
  577. throw new FileNotFoundException("php.ini and php.ini recommended do not exist in " + phpDir);
  578. }
  579. }
  580. return phpIniFilePath;
  581. }
  582. public void RegisterPHPWithIIS(string path)
  583. {
  584. string phpexePath = Environment.ExpandEnvironmentVariables(path);
  585. if (!String.Equals(Path.GetFileName(phpexePath), "php-cgi.exe", StringComparison.OrdinalIgnoreCase) &&
  586. !String.Equals(Path.GetFileName(phpexePath), "php.exe", StringComparison.OrdinalIgnoreCase))
  587. {
  588. throw new ArgumentException("The provided php executable path is invalid", phpexePath);
  589. }
  590. // Check for existence of php executable in the specified directory
  591. if (!File.Exists(phpexePath))
  592. {
  593. throw new FileNotFoundException("php-cgi.exe and php.exe do not exist");
  594. }
  595. // Check for existence of php extensions directory
  596. string phpDir = EnsureTrailingBackslash(Path.GetDirectoryName(phpexePath));
  597. string extDir = Path.Combine(phpDir, "ext");
  598. if (!Directory.Exists(extDir))
  599. {
  600. throw new DirectoryNotFoundException("ext directory does not exist in " + phpDir);
  601. }
  602. string phpIniFilePath = PreparePHPIniFile(phpDir);
  603. bool iisUpdateHappened = false;
  604. ApplicationElement fastCgiApplication = _fastCgiApplicationCollection.GetApplication(phpexePath, "");
  605. // Create a FastCGI application if it does not exist
  606. bool isNewFastCgi = false;
  607. if (fastCgiApplication == null)
  608. {
  609. fastCgiApplication = _fastCgiApplicationCollection.CreateElement();
  610. fastCgiApplication.FullPath = phpexePath;
  611. // monitorChangesTo may not exist if FastCGI update is not installed
  612. if (fastCgiApplication.MonitorChangesToExists())
  613. {
  614. fastCgiApplication.MonitorChangesTo = phpIniFilePath;
  615. }
  616. fastCgiApplication.InstanceMaxRequests = 10000;
  617. fastCgiApplication.ActivityTimeout = 300;
  618. fastCgiApplication.RequestTimeout = 300;
  619. fastCgiApplication.EnvironmentVariables.Add("PHPRC", phpDir);
  620. fastCgiApplication.EnvironmentVariables.Add("PHP_FCGI_MAX_REQUESTS", "10000");
  621. _fastCgiApplicationCollection.Add(fastCgiApplication);
  622. isNewFastCgi = true;
  623. iisUpdateHappened = true;
  624. }
  625. // Check if handler mapping with this executable already exists
  626. HandlerElement handlerElement = _handlersCollection.GetHandler("*.php", phpexePath);
  627. // Create a handler mapping if it does not exist
  628. bool isNewHandler = false;
  629. if (handlerElement == null)
  630. {
  631. // Create a PHP file handler if it does not exist
  632. handlerElement = _handlersCollection.CreateElement();
  633. handlerElement.Name = GenerateHandlerName(_handlersCollection, GetPHPExecutableVersion(phpexePath));
  634. handlerElement.Modules = "FastCgiModule";
  635. handlerElement.RequireAccess = RequireAccess.Script;
  636. handlerElement.Verb = "GET,HEAD,POST";
  637. handlerElement.Path = "*.php";
  638. handlerElement.ScriptProcessor = phpexePath;
  639. handlerElement.ResourceType = ResourceType.Either;
  640. _handlersCollection.AddAt(0, handlerElement);
  641. isNewHandler = true;
  642. iisUpdateHappened = true;
  643. }
  644. else if (_currentPHPHandler != null && handlerElement != _currentPHPHandler)
  645. {
  646. // Move the existing PHP file handler mapping on top
  647. CopyInheritedHandlers();
  648. MakeHandlerActive(handlerElement.Name);
  649. iisUpdateHappened = true;
  650. }
  651. // Check if index.php is set as a default document and move it to the top of the list
  652. iisUpdateHappened = ChangeDefaultDocument() || iisUpdateHappened;
  653. if (iisUpdateHappened)
  654. {
  655. _managementUnit.Update();
  656. // We need to call Initialize() again to set references to current handler and
  657. // fastcgi application and to avoid the read-only exception from IIS config
  658. Initialize();
  659. }
  660. // Make the recommended changes to php.ini file
  661. MakeRecommendedPHPIniChanges();
  662. // Make recommended changes to existing iis configuration. This is the case
  663. // when either FastCGI application or a handler mapping or both existed for the
  664. // specified php-cgi.exe executable.
  665. if (!isNewFastCgi || !isNewHandler)
  666. {
  667. MakeRecommendedFastCgiChanges();
  668. }
  669. }
  670. public void SelectPHPHandler(string name)
  671. {
  672. // PHP is not registered properly so we don't attempt to do anything.
  673. if (!IsPHPRegistered())
  674. {
  675. return;
  676. }
  677. HandlerElement handler = _handlersCollection[name];
  678. // If the handler is already an active PHP handler then no need to do anything.
  679. if (handler != null && handler != _currentPHPHandler)
  680. {
  681. CopyInheritedHandlers();
  682. MakeHandlerActive(name);
  683. _managementUnit.Update();
  684. }
  685. }
  686. private static PHPConfigIssue ValidateCgiForceRedirect(PHPIniFile file)
  687. {
  688. PHPConfigIssue configIssue = null;
  689. // Check if cgi.force_redirect is set correctly
  690. PHPIniSetting setting = file.GetSetting("cgi.force_redirect");
  691. if (setting == null || String.IsNullOrEmpty(setting.TrimmedValue))
  692. {
  693. configIssue = new PHPConfigIssue("cgi.force_redirect",
  694. String.Empty,
  695. "0",
  696. "ConfigIssueCgiForceRedirectNotSet",
  697. "ConfigIssueCgiForceRedirectRecommend",
  698. PHPConfigIssueIndex.CgiForceRedirect);
  699. }
  700. else if (!String.Equals(setting.TrimmedValue, "0", StringComparison.OrdinalIgnoreCase))
  701. {
  702. configIssue = new PHPConfigIssue("cgi.force_redirect",
  703. setting.TrimmedValue,
  704. "0",
  705. "ConfigIssueCgiForceRedirectNotCorrect",
  706. "ConfigIssueCgiForceRedirectRecommend",
  707. PHPConfigIssueIndex.CgiForceRedirect);
  708. }
  709. return configIssue;
  710. }
  711. private static PHPConfigIssue ValidateCgiPathInfo(PHPIniFile file)
  712. {
  713. PHPConfigIssue configIssue = null;
  714. // Check if cgi.fix_pathinfo is set correctly
  715. PHPIniSetting setting = file.GetSetting("cgi.fix_pathinfo");
  716. if (setting == null || String.IsNullOrEmpty(setting.TrimmedValue))
  717. {
  718. configIssue = new PHPConfigIssue("cgi.fix_pathinfo",
  719. String.Empty,
  720. "1",
  721. "ConfigIssueCgiPathInfoNotSet",
  722. "ConfigIssueCgiPathInfoRecommend",
  723. PHPConfigIssueIndex.CgiPathInfo);
  724. }
  725. else if (!String.Equals(setting.TrimmedValue, "1", StringComparison.OrdinalIgnoreCase))
  726. {
  727. configIssue = new PHPConfigIssue("cgi.fix_pathinfo",
  728. setting.TrimmedValue,
  729. "1",
  730. "ConfigIssueCgiPathInfoNotCorrect",
  731. "ConfigIssueCgiPathInfoRecommend",
  732. PHPConfigIssueIndex.CgiPathInfo);
  733. }
  734. return configIssue;
  735. }
  736. public RemoteObjectCollection<PHPConfigIssue> ValidateConfiguration()
  737. {
  738. // Check if PHP is not registered
  739. if (!IsPHPRegistered())
  740. {
  741. return null;
  742. }
  743. PHPIniFile file = new PHPIniFile(PHPIniFilePath);
  744. file.Parse();
  745. return ValidateConfiguration(file);
  746. }
  747. private RemoteObjectCollection<PHPConfigIssue> ValidateConfiguration(PHPIniFile file)
  748. {
  749. RemoteObjectCollection<PHPConfigIssue> configIssues = new RemoteObjectCollection<PHPConfigIssue>();
  750. // IIS and FastCGI settings
  751. PHPConfigIssue configIssue = ValidateDefaultDocument();
  752. if (configIssue != null)
  753. {
  754. configIssues.Add(configIssue);
  755. }
  756. configIssue = ValidateResourceType();
  757. if (configIssue != null)
  758. {
  759. configIssues.Add(configIssue);
  760. }
  761. configIssue = ValidatePHPMaxRequests();
  762. if (configIssue != null)
  763. {
  764. configIssues.Add(configIssue);
  765. }
  766. configIssue = ValidatePHPRC();
  767. if (configIssue != null)
  768. {
  769. configIssues.Add(configIssue);
  770. }
  771. configIssue = ValidateMonitorChanges();
  772. if (configIssue != null)
  773. {
  774. configIssues.Add(configIssue);
  775. }
  776. // PHP Settings
  777. configIssue = ValidateExtensionDir(file);
  778. if (configIssue != null)
  779. {
  780. configIssues.Add(configIssue);
  781. }
  782. configIssue = ValidateLogErrors(file);
  783. if (configIssue != null)
  784. {
  785. configIssues.Add(configIssue);
  786. }
  787. configIssue = ValidateErrorLog(file);
  788. if (configIssue != null)
  789. {
  790. configIssues.Add(configIssue);
  791. }
  792. configIssue = ValidateSessionPath(file);
  793. if (configIssue != null)
  794. {
  795. configIssues.Add(configIssue);
  796. }
  797. configIssue = ValidateUploadTmpDir(file);
  798. if (configIssue != null)
  799. {
  800. configIssues.Add(configIssue);
  801. }
  802. configIssue = ValidateCgiForceRedirect(file);
  803. if (configIssue != null)
  804. {
  805. configIssues.Add(configIssue);
  806. }
  807. configIssue = ValidateCgiPathInfo(file);
  808. if (configIssue != null)
  809. {
  810. configIssues.Add(configIssue);
  811. }
  812. configIssue = ValidateFastCgiImpersonate(file);
  813. if (configIssue != null)
  814. {
  815. configIssues.Add(configIssue);
  816. }
  817. return configIssues;
  818. }
  819. private PHPConfigIssue ValidateDefaultDocument()
  820. {
  821. PHPConfigIssue configIssue = null;
  822. // Check if index.php is set as a default document
  823. FileElement fileElement = _defaultDocumentCollection["index.php"];
  824. if (fileElement == null)
  825. {
  826. configIssue = new PHPConfigIssue("Default document",
  827. _defaultDocumentCollection[0].Value,
  828. "index.php",
  829. "ConfigIssueDefaultDocumentNotSet",
  830. "ConfigIssueDefaultDocumentRecommend",
  831. PHPConfigIssueIndex.DefaultDocument);
  832. }
  833. else if (_defaultDocumentCollection.IndexOf(fileElement) > 0)
  834. {
  835. configIssue = new PHPConfigIssue("Default document",
  836. _defaultDocumentCollection[0].Value,
  837. "index.php",
  838. "ConfigIssueDefaultDocumentNotFirst",
  839. "ConfigIssueDefaultDocumentRecommend",
  840. PHPConfigIssueIndex.DefaultDocument);
  841. }
  842. return configIssue;
  843. }
  844. private PHPConfigIssue ValidateErrorLog(PHPIniFile file)
  845. {
  846. PHPConfigIssue configIssue = null;
  847. // Check if error_log is set to an absolute path and that path exists
  848. PHPIniSetting setting = file.GetSetting("error_log");
  849. string expectedValue = Path.Combine(Environment.ExpandEnvironmentVariables(@"%WINDIR%\Temp\"), _currentPHPHandler.Name + "_errors.log");
  850. if (setting == null || String.IsNullOrEmpty(setting.TrimmedValue))
  851. {
  852. configIssue = new PHPConfigIssue("error_log",
  853. String.Empty,
  854. expectedValue,
  855. "ConfigIssueErrorLogNotSet",
  856. "ConfigIssueErrorLogRecommend",
  857. PHPConfigIssueIndex.ErrorLog);
  858. }
  859. else if (!IsAbsoluteFilePath(setting.TrimmedValue, true /* this is supposed to be a file */))
  860. {
  861. configIssue = new PHPConfigIssue("error_log",
  862. setting.TrimmedValue,
  863. expectedValue,
  864. "ConfigIssueErrorLogNotCorrect",
  865. "ConfigIssueErrorLogRecommend",
  866. PHPConfigIssueIndex.ErrorLog);
  867. }
  868. return configIssue;
  869. }
  870. private PHPConfigIssue ValidateExtensionDir(PHPIniFile file)
  871. {
  872. PHPConfigIssue configIssue = null;
  873. PHPIniSetting setting = file.GetSetting("extension_dir");
  874. string expectedValue = EnsureTrailingBackslash(Path.Combine(PHPDirectory, "ext"));
  875. if (setting == null || String.IsNullOrEmpty(setting.TrimmedValue))
  876. {
  877. configIssue = new PHPConfigIssue("extension_dir",
  878. String.Empty,
  879. expectedValue,
  880. "ConfigIssueExtensionDirNotSet",
  881. "ConfigIssueExtensionDirRecommend",
  882. PHPConfigIssueIndex.ExtensionDir);
  883. }
  884. else
  885. {
  886. string currentValue = EnsureTrailingBackslash(setting.TrimmedValue);
  887. if (!String.Equals(currentValue, expectedValue, StringComparison.OrdinalIgnoreCase))
  888. {
  889. configIssue = new PHPConfigIssue("extension_dir",
  890. setting.TrimmedValue,
  891. expectedValue,
  892. "ConfigIssueExtensionDirIncorrect",
  893. "ConfigIssueExtensionDirRecommend",
  894. PHPConfigIssueIndex.ExtensionDir);
  895. }
  896. }
  897. return configIssue;
  898. }
  899. private static PHPConfigIssue ValidateFastCgiImpersonate(PHPIniFile file)
  900. {
  901. PHPConfigIssue configIssue = null;
  902. // Check if fastcgi impersonation is turned on
  903. PHPIniSetting setting = file.GetSetting("fastcgi.impersonate");
  904. if (setting == null || String.IsNullOrEmpty(setting.TrimmedValue))
  905. {
  906. configIssue = new PHPConfigIssue("fastcgi.impersonate",
  907. String.Empty,
  908. "1",
  909. "ConfigIssueFastCgiImpersonateNotSet",
  910. "ConfigIssueFastCgiImpersonateRecommend",
  911. PHPConfigIssueIndex.FastCgiImpersonation);
  912. }
  913. else if (!String.Equals(setting.TrimmedValue, "1", StringComparison.OrdinalIgnoreCase))
  914. {
  915. configIssue = new PHPConfigIssue("fastcgi.impersonate",
  916. setting.TrimmedValue,
  917. "1",
  918. "ConfigIssueFastCgiImpersonateNotCorrect",
  919. "ConfigIssueFastCgiImpersonateRecommend",
  920. PHPConfigIssueIndex.FastCgiImpersonation);
  921. }
  922. return configIssue;
  923. }
  924. private static PHPConfigIssue ValidateLogErrors(PHPIniFile file)
  925. {
  926. PHPConfigIssue configIssue = null;
  927. // Check if log_errors is set to On
  928. PHPIniSetting setting = file.GetSetting("log_errors");
  929. if (setting == null || String.IsNullOrEmpty(setting.TrimmedValue))
  930. {
  931. configIssue = new PHPConfigIssue("log_errors",
  932. String.Empty,
  933. "On",
  934. "ConfigIssueLogErrorsNotSet",
  935. "ConfigIssueLogErrorsRecommend",
  936. PHPConfigIssueIndex.LogErrors);
  937. }
  938. else if (!String.Equals(setting.TrimmedValue, "On", StringComparison.OrdinalIgnoreCase))
  939. {
  940. configIssue = new PHPConfigIssue("log_errors",
  941. setting.TrimmedValue,
  942. "On",
  943. "ConfigIssueLogErrorsNotCorrect",
  944. "ConfigIssueLogErrorsRecommend",
  945. PHPConfigIssueIndex.LogErrors);
  946. }
  947. return configIssue;
  948. }
  949. private PHPConfigIssue ValidateMonitorChanges()
  950. {
  951. PHPConfigIssue configIssue = null;
  952. // Check if monitorChangesTo setting is supported and is set correctly
  953. if (_currentFastCgiApplication.MonitorChangesToExists())
  954. {
  955. string path = _currentFastCgiApplication.MonitorChangesTo;
  956. if (String.IsNullOrEmpty(path))
  957. {
  958. configIssue = new PHPConfigIssue("monitorChangesTo",
  959. String.Empty,
  960. PHPIniFilePath,
  961. "ConfigIssueMonitorChangesNotSet",
  962. "ConfigIssueMonitorChangesRecommend",
  963. PHPConfigIssueIndex.MonitorChangesTo);
  964. }
  965. else if (!String.Equals(PHPIniFilePath, path, StringComparison.OrdinalIgnoreCase))
  966. {
  967. configIssue = new PHPConfigIssue("monitorChangesTo",
  968. path,
  969. PHPIniFilePath,
  970. "ConfigIssueMonitorChangesIncorrect",
  971. "ConfigIssueMonitorChangesRecommend",
  972. PHPConfigIssueIndex.MonitorChangesTo);
  973. }
  974. }
  975. return configIssue;
  976. }
  977. private PHPConfigIssue ValidatePHPMaxRequests()
  978. {
  979. PHPConfigIssue configIssue = null;
  980. // Check if PHP_FCGI_MAX_REQUESTS is set and is bigger than instanceMaxRequests
  981. EnvironmentVariableElement envVariableElement = _currentFastCgiApplication.EnvironmentVariables["PHP_FCGI_MAX_REQUESTS"];
  982. if (envVariableElement == null)
  983. {
  984. configIssue = new PHPConfigIssue("PHP_FCGI_MAX_REQUESTS",
  985. String.Empty,
  986. _currentFastCgiApplication.InstanceMaxRequests.ToString(CultureInfo.InvariantCulture),
  987. "ConfigIssuePHPMaxRequestsNotSet",
  988. "ConfigIssuePHPMaxRequestsRecommend",
  989. PHPConfigIssueIndex.PHPMaxRequests);
  990. }
  991. else
  992. {
  993. long maxRequests;
  994. if (!Int64.TryParse(envVariableElement.Value, out maxRequests) ||
  995. (maxRequests < _currentFastCgiApplication.InstanceMaxRequests))
  996. {
  997. configIssue = new PHPConfigIssue("PHP_FCGI_MAX_REQUESTS",
  998. envVariableElement.Value,
  999. _currentFastCgiApplication.InstanceMaxRequests.ToString(CultureInfo.InvariantCulture),
  1000. "ConfigIssuePHPMaxRequestsIncorrect",

Large files files are truncated, but you can click here to view the full file