Custom sitemap handler causing site to fail to load (Sitecore) - redirect

I'm trying to add a custom handler to my site to redirect to the appropriate Sitemap for the current language, i.e. mysite.com/sitemap.xml --> sitemap-en.xml, mysite.es/sitemap.xml --> sitemap-es.xml, etc.
This is my setup:
handler trigger patch:
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
<sitecore>
<customHandlers>
<handler trigger="sitemap.xml" handler="sitemap.ashx" />
</customHandlers>
</sitecore>
</configuration>
web.config:
<add path="sitemap.ashx" verb="*" type="MySite.CustomSitecore.Handlers.SitemapHandler,MySite" name="SitemapXml" />
handler:
namespace MySite.CustomSitecore.Handlers
{
public class SitemapHandler : IHttpHandler
{
public bool IsReusable
{
get
{
throw new NotImplementedException();
}
}
public void ProcessRequest(HttpContext context)
{
try
{
var curSite = Sitecore.Context.Site;
var m_Sites = SitemapManagerConfiguration.GetSites();
foreach (DictionaryEntry site in m_Sites)
{
if (site.Key.ToString().Equals(curSite.Name))
{
var filepath = site.Value;
HttpContext.Current.Response.Redirect("/" + filepath, false);
return;
}
}
return;
}
catch (Exception ex)
{
Log.Info("Error in SitemapHandler: " + ex, this);
}
}
}
}
It's not working though, when I try to go to mysite.com/sitemap.xml and debug it steps through the redirect process as expected and looks as if it should be successfully redirecting to /sitemap-en.xml, but the page just spins and displays browser error saying that the page can't be loaded.
I have tried a couple different redirect methods, but nothing has worked. I tried this as well:
HttpContext.Current.Response.StatusCode = 200;
HttpContext.Current.Response.ContentType = "text/xml";
HttpContext.Current.Response.AddHeader("Location", "/" + filepath);
HttpContext.Current.Response.End();
return;

Not sure if this is the issue, But out of the box Sitecore don't allow .xml files. Security hardening.
You can use somethings like this in the config, to add the .xml extension
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
<sitecore>
<pipelines>
<preprocessRequest help="Processors should derive from Sitecore.Pipelines.PreprocessRequest.PreprocessRequestProcessor">
<processor type="Sitecore.Pipelines.PreprocessRequest.FilterUrlExtensions, Sitecore.Kernel">
<param desc="Allowed extensions (comma separated)">aspx, ashx, asmx, xml</param>
</processor>
</preprocessRequest>
</pipelines>
</sitecore>
</configuration>
But I think a better solution, use the default and take security seriously.
Tell the search engine what the url of the sitemap is by using a robots.txt
User-agent: *
Sitemap: /sitemap.ashx

Related

How to exclude a controller of being redirect to https in ASP.NET Core 1.1

Following this nice article https://long2know.com/2016/07/asp-net-core-enforcing-https/ i try to enforce HTTPS but with the exception of one single web-api controller that have to response to an embedded system not capable of SSL. The problem exist in the following Configure section of startup.cs.
var options = new RewriteOptions()
.AddRewrite("^api/&", "/api/", skipRemainingRules: true)
.AddRedirectToHttps(302, sslPort);
app.UseRewriter(options);
With the AddRewrite line (that doesn't replace anything) i try to trigger skipRemainingRules in order to avert the redirection. This is working at development in IIS express (trough localhost) but not at the production enviroment behind IIS. Apperently the SkipRemainingRules does not prevent the AddRedirectToHttps of enter into force.
Many thanks for any clue the may solve this.
I'm aware that my response is quite late, but maybe it will help someone.
var options = new RewriteOptions()
.AddRewrite(#"^api/(.*)", "api/$1", true)
.AddRedirectToHttps(302, sslPort);
app.UseRewriter(options);
With this solution one can call /api/... on http, every other request will be redirected to https.
I've testes this solution with .Net Core 2.1.
I eventually fix it with a custom redirect:
public class CustomRedirect : Microsoft.AspNetCore.Rewrite.IRule
{
public void ApplyRule(RewriteContext context)
{
var request = context.HttpContext.Request;
var host = request.Host;
// Exclude localhost
if ( string.Equals(host.Host, "localhost", StringComparison.OrdinalIgnoreCase))
{
context.Result = RuleResult.ContinueRules;
return;
}
// Exclude api
if (request.Path.Value.Contains("/api/"))
{
context.Result = RuleResult.ContinueRules;
return;
}
// force other traffic to https
if (string.Equals(request.Scheme, "http", StringComparison.OrdinalIgnoreCase))
{
string path = "https://" + host.Value + request.PathBase + request.Path + request.QueryString;
context.HttpContext.Response.Redirect(path,true);
context.Result = RuleResult.EndResponse;
}
}
}
And register in the Configure section of startup.cs:
app.UseRewriter(new RewriteOptions().Add(new CustomRedirect()));
I think there was an issue working with IIS even in Azure web apps regarding http/https redirect.
So one way to acheive this is to enforce it via web.config like this:
<system.webServer>
<rewrite>
<rules>
<rule name="HTTP/S to HTTPS Redirect" enabled="true" stopProcessing="true">
<match url="^((?!api).)*$" />
<conditions logicalGrouping="MatchAny">
<add input="{SERVER_PORT_SECURE}" pattern="^0$" />
</conditions>
<action type="Redirect" url="https://{HTTP_HOST}/{R:0}" redirectType="Permanent" />
</rule>
</rules>
</rewrite>
</system.webServer>
By this convention you are enforcing https for every url except those with api keyword inside.

How to write Monolog logs into a file AND remote database

I have a Symfony2 -project and it writes the logs into different files beautifully, but I would like it to write the logs into a remote database(mongodb) as well. I would like to keep the actual log files in the servers as a backup in case something goes wrong with the database connection.
Question 1:
Is it even possible to save the same logs into two different places at the same time?
Question 2:
How do I save the logs into the mongodb? I don't necessarily need specific mongodb-instructions, but some guidelines on how to write into a remote db with monologger. The mongodb-specific instructions are also welcome if available. ;)
Question 3(OPTIONAL):
Can I get a full error stack into the logs somehow? Where could one find a full list of what data the Monolog can actually write and how to write?
There was a very good Blogpost sometime back for logging to a mysql database with monolog and doctrine. I can't find it anymore so i will just add the neccessary Files here and you can adjust it.
The whole logic is done in the DatabaseHandler so you can just change from
mysql inserts to a handling for your mongodb.
This code is not mine if anyone knows the original post please comment.
BacktraceLoggerListener.php
namespace UtilsBundle\EventListener;
use Symfony\Component\HttpKernel\Log\LoggerInterface;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
class BacktraceLoggerListener{
private $_logger;
public function __construct(LoggerInterface $logger = null)
{
$this->_logger = $logger;
}
public function onKernelException(GetResponseForExceptionEvent $event)
{
$this->_logger->addError($event->getException());
}
}
DatabaseHandler.php
namespace UtilsBundle\Logger;
use Monolog\Handler\AbstractProcessingHandler;
use Monolog\Logger;
/**
* Stores to database
*
*/
class DatabaseHandler extends AbstractProcessingHandler{
protected $_container;
/**
* #param string $stream
* #param integer $level The minimum logging level at which this handler will be triggered
* #param Boolean $bubble Whether the messages that are handled can bubble up the stack or not
*/
public function __construct($level = Logger::DEBUG, $bubble = true)
{
parent::__construct($level, $bubble);
}
/**
*
* #param type $container
*/
public function setContainer($container)
{
$this->_container = $container;
}
/**
* {#inheritdoc}
*/
protected function write(array $record)
{
// Ensure the doctrine channel is ignored (unless its greater than a warning error), otherwise you will create an infinite loop, as doctrine like to log.. a lot..
if( 'doctrine' == $record['channel'] ) {
if( (int)$record['level'] >= Logger::WARNING ) {
error_log($record['message']);
}
return;
}
// Only log errors greater than a warning
// TODO - you could ideally add this into configuration variable
if( (int)$record['level'] >= Logger::NOTICE ) {
try
{
// Logs are inserted as separate SQL statements, separate to the current transactions that may exist within the entity manager.
$em = $this->_container->get('doctrine')->getManager();
$conn = $em->getConnection();
$created = date('Y-m-d H:i:s');
$serverData = ""; //$record['extra']['server_data'];
$referer = "";
if (isset($_SERVER['HTTP_REFERER'])){
$referer= $_SERVER['HTTP_REFERER'];
}
$stmt = $em->getConnection()->prepare('INSERT INTO system_log(log, level, server_data, modified, created)
VALUES(' . $conn->quote($record['message']) . ', \'' . $record['level'] . '\', ' . $conn->quote($referer) . ', \'' . $created . '\', \'' . $created . '\');');
$stmt->execute();
} catch( \Exception $e ) {
// Fallback to just writing to php error logs if something really bad happens
error_log($record['message']);
error_log($e->getMessage());
}
}
}
}
We used xml here but this can be done in
services.yml too
services.xml
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="utils.database.logger" class="UtilsBundle\Logger\DatabaseHandler">
<call method="setContainer">
<argument type="service" id="service_container" />
</call>
</service>
<service id="utils.backtrace.logger.listener" class="UtilsBundle\EventListener\BacktraceLoggerListener">
<argument type="service" id="logger" />
<tag name="monolog.logger" channel="backtrace" />
<tag name="kernel.event_listener" event="kernel.exception" method="onKernelException" />
</service>
</services>
And lastly add the handler to your monolog config in
config_**.yml so here for production for example
config_prod.yml
monolog:
handlers:
main:
type: rotating_file
action_level: error
max_files: 10
handler: nested
nested:
type: stream
path: "%kernel.logs_dir%/%kernel.environment%.log"
level: debug
console:
type: console
database:
type: service
level: notice
id: utils.database.logger
channels: ["!translation"]
Hope that helps
Hope I can some things up for you:
Question 1: Yes its possible. E.G. you can do smt. like:
$this->logger->pushHandler(new StreamHandler('/path/to/logs/123_info.log', Logger::INFO));
$this->logger->pushHandler(new StreamHandler('/path/to/logs/456_warning.log', Logger::INFO));
So if $this->logger->addInfo("testinfo"); this is getting logged in both streams.
Question 2: There is a MongoDBHandler as according to the StreamHandler. You should be able do configure it and pass it along to the pushHandler method or if you want to have it in your services look at MongoDBConfiguration.
Question 3:
This should help: Configure Monolog
Hope that helps.

Nuget pack csproj using nuspec

I want to create a nuget package that contains only what is specified in my nuspec file, but still get the version from my csproj. In order to use the token I have to pack like:
nuget pack MyProj.csproj
But when I do it like this it adds some dependencies and creates an unwanted folder in my nuget package. My nuspec file is:
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
<metadata>
<id>Test</id>
<version>$version$</version>
<title>Test</title>
<authors>Test</authors>
<owners>Test</owners>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>Test</description>
<summary>Test</summary>
<releaseNotes>Test</releaseNotes>
<copyright>Test</copyright>
</metadata>
<files>
<file src="bin\Debug\*.dll" target="lib\net45" />
<file src="bin\Debug\MyProj.Wpf.exe" target="lib\net45" />
<file src="bin\Debug\MyProj.Wpf.exe.config" target="lib\net45" />
</files>
</package>
When I run the pack command the file it adds extra is the MyProj.Wpf.exe in the target="lib\net452"
Can I force it not to add the dependencies and this extra file? Or to do only what is specified in nuspec?
It's been a while since I posted this question. Since then I used a solution that worked for me, so I'm going to post it here for anyone that needs it.
I created a .csproj that modifies the .nuspec file and sets it's version according to the assembly file of the .csproj. To reduce the manual work I created a .bat file that called this .exe with arguments and finished creating the installer. I used Squirrel.Windows for the installer creation.
I created a NuspectVersionSetter.csproj to set the nuspec version. There are many ways to implement this, here goes a simple one:
static void Main(string[] args)
{
try
{
if (args.Length < 2)
{
throw new Exception("Args are not correct");
}
var assemblyFilePath = args[0];
var nuspecFilePath = args[1];
IsFileValid(assemblyFilePath, ".cs");
IsFileValid(nuspecFilePath, ".nuspec");
var version = GetAssemblyVersion(assemblyFilePath);
if (string.IsNullOrEmpty(version))
{
throw new Exception("Unable to get version");
}
WriteNuspec(nuspecFilePath, version);
}
catch (Exception e)
{
Console.WriteLine(e.Message);
Console.ReadLine();
}
}
static void WriteNuspec(string path, string version)
{
var lines = File.ReadAllLines(path);
string versionLine = null;
for(var i = 0; i < lines.Length; ++i)
{
var line = lines[i];
if (line.Contains("<version>") && line.Contains("</version>"))
{
var init = line.IndexOf("<version>") + "<version>".Length;
var end = line.IndexOf("</version>");
line = line.Remove(init, end - init);
lines[i] = line.Insert(init, version);
break;
}
}
File.WriteAllLines(path, lines);
}
static void IsFileValid(string file, string extension)
{
if (!File.Exists(file))
{
throw new Exception("Invalid file path: " + file);
}
if (!file.EndsWith(extension))
{
throw new Exception("Invalid file extension: " + file);
}
}
static string GetAssemblyVersion(string path)
{
var lines = File.ReadAllLines(path);
foreach(var line in lines)
{
if(line.Contains("AssemblyVersion") && !line.Contains(".*"))
{
var parts = line.Split('\"');
if (parts.Length != 3)
{
break;
}
return parts[1];
}
}
foreach (var line in lines)
{
if (line.Contains("AssemblyFileVersion") && !line.Contains(".*"))
{
var parts = line.Split('\"');
if (parts.Length != 3)
{
break;
}
return parts[1];
}
}
foreach (var line in lines)
{
if (line.Contains("AssemblyInformationalVersion") && !line.Contains(".*"))
{
var parts = line.Split('\"');
if (parts.Length != 3)
{
break;
}
return parts[1];
}
}
throw new Exception("Unable to get version");
}
As explained before the .bat file also wrapped the Squirrel.Windows installer creation.
Small observations about the .bat:
NuspecVersionSetter was the .exe created from the .csproj above
This example assumes your NuspecVersionSetter.exe is inside a folder in your .csproj, therefore some paths may need to be adjusted
The Squirrel.Windows version is old in this example, you many need to change it
The .bat file I used was the following:
NuspecVersionSetter ../Properties/AssemblyInfo.cs mynuspec.nuspec
nuget pack
#echo off
setlocal EnableDelayedExpansion
for /f "tokens=*" %%G in ('dir *.nupkg /b /a-d /od') do (
SET newest=%%G
)
"../../packages/squirrel.windows.1.2.1/tools/Squirrel" --releasify !newest! -g installing.gif
If there are any questions about this answer, ask them in the comments.

Joomla 3 replace text plugin using preg replace not working

I am following a tutorial about joomla 3 extension development. I am using Joomla 3.2.4
I have a plugin name clicktocall, which to make all phone number text displayed as a link.
Phone number format is XXXX-XXXX or XXXX XXXX, X is digit. and I want display any phone number as ">
The method is using pattern as replace any text match the pattern by link tag
I installed, enabled the plugin
I do it after a tutorial in an ebook, in the book everything are so smoothly, but in my site, after I view an article which have phone number text, there are nothing happen. The plugin not working.
My code:
clicktocall.php
defined('_JEXEC') or die;
jimport('joomla.plugin.plugin');
class plgContentClicktocall extends JPlugin {
function plgContentClicktocall(&$subject, $params) {
parent::__construct($subject, $params);
}
public function onContentPrepare($context, &$row, &$params, $page = 0) {
//don't run this when the content is indexing
if ($context == 'com_finder.indexer') {
return true;
}
if (is_object($row)) {
echo $row->text;
return $this->clickToCall($row->text, $params);
}
return $this->clickToCall($row, $params);
}
protected function clickToCall(&$text, &$params) {
// matches 4 numbers followed by an optional hyphen or space,
// then followed by 4 numbers.
// phone number is in the form XXXX-XXXX or XXXX XXXX
$pattern = '/(\W[0-9]{4})-? ?(\W[0-9]{4})/';
$replacement = '$1$2';
$text = preg_replace($pattern, $replacement, $text);
return true;
}
}
clicktocall.xml
<?xml version="1.0" encoding="UTF-8"?>
<extension
version="3.0"
type="plugin"
group="content"
method="upgrade">
<name>Content - Click To Call</name>
<author>Tim Plummer</author>
<creationDate>April 2013</creationDate>
<copyright>Copyright (C) 2013 Packt Publishing. All rights
reserved.</copyright>
<license> http://www.gnu.org/licenses/gpl-3.0.html</license>
<authorEmail>example#packtpub.com</authorEmail>
<authorUrl>http://packtpub.com</authorUrl>
<version>1.0.0</version>
<description>This plugin will replace phone numbers with click
to call links. Requires Joomla! 3.0 or greater.
Don't forget to publish this plugin!
</description>
<files>
<filename plugin="clicktocall">clicktocall.php</filename>
<filename>index.html</filename>
</files>
</extension>
index.html : blank tags only
Sorry for the XML, I try for 10 minutes to make it pre-formatted but seem to be useless, but I confirm it's OK, included all files in my plugin
I believe the issue is you are returning the value from your click2Call() method inside your onContentPrepare() method. Try reformatting like so:
public function onContentPrepare($context, &$row, &$params, $page = 0) {
//don't run this when the content is indexing
if ($context == 'com_finder.indexer') {
return true;
}
if (is_object($row)) {
echo $row->text;
$this->clickToCall($row->text, $params);
} else {
$this->clickToCall($row, $params);
}
return true;
}
Since the row variable is referenced, any changes you make to the row data you're making to the actual data. Therefor, no need to return any data outside of the the return true at the end of the method.

How to get without "default.aspx" url?

I implemented a following code in Global.asax file of my web application.
void Application_BeginRequest()
{
string rule = ConfigurationManager.AppSettings.Get("WwwRule");
HttpContext context = HttpContext.Current;
if (context.Request.HttpMethod != "GET" || context.Request.IsLocal)
{
return;
}
if (context.Request.PhysicalPath.EndsWith(".aspx", StringComparison.OrdinalIgnoreCase))
{
string url = context.Request.Url.ToString();
if (!url.Contains("://www.") && rule == "add")
{
string url = context.Request.Url.ToString().Replace("://", "://www.");
context.Response.Redirect(url);
}
}
}
When I am running above code it works as follows
example.com redirects to www.example.com/default.aspx
www.example.com redirects to www.example.com
http://www.example.com/ redirects to http://www.example.com/
last two conditions works very well. But the first condition did'nt works well because its adding "default.aspx" in the URL which I am not intrested in.
Can anyone please tell me how to make it as below
example.com should redirects to http://www.example.com
Thanks
Most likely the Request.Url is appending default.aspx because that's the page actually being served at the time (IIS makes this transparent to you because it's one of the default pages).
When you make your new URL that you're going to redirect, add another .Replace("/default.aspx", "") to the end of it. So...
string url = context.Request.Url.ToString().Replace("://", "://www.").Replace("/default.aspx", "");
Actually, the /default.aspx is added before the request reaches the BeginRequest event. If you want to remove it, you have to actually remove it:
void Application_BeginRequest() {
string rule = ConfigurationManager.AppSettings.Get("WwwRule");
HttpContext context = HttpContext.Current;
if (context.Request.HttpMethod != "GET" || context.Request.IsLocal) {
return;
}
if (context.Request.PhysicalPath.EndsWith(".aspx", StringComparison.OrdinalIgnoreCase)) {
string url = context.Request.Url.ToString();
if (!url.Contains("://www.") && rule == "add") {
url = url.Replace("://", "://www.");
if (url.EndsWith("/default.aspx", StringComparison.OrdinalIgnoreCase) {
url = url.Substring(0, url.Length - 13);
}
context.Response.Redirect(url);
}
}
}
change your webconfig with below code:it solve my same problem.
<?xml version="1.0"?>
<configuration>
<system.webServer>
<rewrite>
<rules>
<rule name="default.aspx Redirect" stopProcessing="true">
<match url="^(.*\/)*default\.aspx$" />
<conditions logicalGrouping="MatchAll">
<add input="{REQUEST_METHOD}" negate="true" pattern="^POST$" />
</conditions>
<action type="Redirect" url="{R:1}" redirectType="Permanent"/>
</rule>
</rules>
</rewrite>
</system.webServer>
</configuration>