View Issue Details

IDProjectCategoryView StatusLast Update
0025606mantisbtemailpublic2019-04-05 17:14
ReportersachAssigned Todregad 
PrioritynormalSeverityminorReproducibilityalways
Status closedResolutionunable to reproduce 
PlatformWindowsOSWindows Server 2012 R2OS Version
Product Version2.19.0 
Target VersionFixed in Version 
Summary0025606: EMail Verification Link Broken
Description

When a new user signs up, in the verification email that receives the link is broken. If the link in the EMail is clicked, I get the following error:

APPLICATION ERROR #200 A required parameter to this page (confirm_hash) was not found.

Following is a sample Email that I get upon new user registration.


Thank you for registering. You have an account with username
"testuser". In order to complete your registration, visit
the following URL (make sure it is entered as the single line) and set your own
access password:<br />
<br />
{my-domain}/tracker/verify.php?id=5&confirm_hash=gpZfvtEF5nvujF2MunOMBHW_FnRpk-INykgIJb0r_cTZKSENcu2SV1m9NA5DrA7FuE9Rq4gNXGkspMbn-Xvy<br />
<br />
If you did not request any registration, ignore this message and nothing will
happen.<br />
<br />
Do not reply to this message


I figured out that the problem is there's an additional "amp;" in the link, and when I remove it and pastes in the browser and hit enter, it works.

According to the following bug it's supposed to be fixed, but it's not.
https://mantisbt.org/bugs/view.php?id=10369

Steps To Reproduce

Sign up for a new user.
Click the verification email you get.

TagsNo tags attached.

Activities

sach

sach

2019-03-15 20:04

reporter   ~0061677

Well mantis corrected the link, so here's an image of what it looks like:



err.PNG (21,754 bytes)
err.PNG (21,754 bytes)
sach

sach

2019-03-15 20:07

reporter   ~0061678

Here's my string_api.php



string_api.php (33,310 bytes)
<?php
# MantisBT - A PHP based bugtracking system

# MantisBT is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# MantisBT is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with MantisBT.  If not, see <http://www.gnu.org/licenses/>.

/**
 * String Processing API
 *
 * @package CoreAPI
 * @subpackage StringProcessingAPI
 * @copyright Copyright 2000 - 2002  Kenzaburo Ito - kenito@300baud.org
 * @copyright Copyright 2002  MantisBT Team - mantisbt-dev@lists.sourceforge.net
 * @link http://www.mantisbt.org
 *
 * @uses access_api.php
 * @uses authentication_api.php
 * @uses bug_api.php
 * @uses bugnote_api.php
 * @uses config_api.php
 * @uses constant_inc.php
 * @uses email_api.php
 * @uses event_api.php
 * @uses helper_api.php
 * @uses lang_api.php
 * @uses user_api.php
 * @uses utility_api.php
 */

require_api( 'access_api.php' );
require_api( 'authentication_api.php' );
require_api( 'bug_api.php' );
require_api( 'bugnote_api.php' );
require_api( 'config_api.php' );
require_api( 'constant_inc.php' );
require_api( 'email_api.php' );
require_api( 'event_api.php' );
require_api( 'helper_api.php' );
require_api( 'lang_api.php' );
require_api( 'user_api.php' );
require_api( 'utility_api.php' );

$g_cache_html_valid_tags = '';
$g_cache_html_valid_tags_single_line = '';

/**
 * Preserve spaces at beginning of lines.
 * Lines must be separated by \n rather than <br />
 * @param string $p_string String to be processed.
 * @return string
 */
function string_preserve_spaces_at_bol( $p_string ) {
	$t_lines = explode( "\n", $p_string );
	$t_line_count = count( $t_lines );
	for( $i = 0;$i < $t_line_count;$i++ ) {
		$t_count = 0;
		$t_prefix = '';

		$t_char = mb_substr( $t_lines[$i], $t_count, 1 );
		$t_spaces = 0;
		while( ( $t_char == ' ' ) || ( $t_char == "\t" ) ) {
			if( $t_char == ' ' ) {
				$t_spaces++;
			} else {
				$t_spaces += 4;
			}

			# 1 tab = 4 spaces, can be configurable.

			$t_count++;
			$t_char = mb_substr( $t_lines[$i], $t_count, 1 );
		}

		for( $j = 0;$j < $t_spaces;$j++ ) {
			$t_prefix .= '&#160;';
		}

		$t_lines[$i] = $t_prefix . mb_substr( $t_lines[$i], $t_count );
	}
	return implode( "\n", $t_lines );
}

/**
 * Prepare a string to be printed without being broken into multiple lines
 * @param string $p_string String to be processed.
 * @return string
 */
function string_no_break( $p_string ) {
	if( strpos( $p_string, ' ' ) !== false ) {
		return '<span class="nowrap">' . $p_string . '</span>';
	} else {
		return $p_string;
	}
}

/**
 * Similar to nl2br, but fixes up a problem where new lines are doubled between
 * html pre tags.
 * additionally, wrap the text an $p_wrap character intervals if the config is set
 * @param string  $p_string String to be processed.
 * @param integer $p_wrap   Number of characters to wrap text at.
 * @return string
 */
function string_nl2br( $p_string, $p_wrap = 100 ) {
	$t_pieces = preg_split( '/(<pre[^>]*>.*?<\/pre>)/is', $p_string, -1, PREG_SPLIT_DELIM_CAPTURE );
	if( isset( $t_pieces[1] ) ) {
		$t_output = '';
		foreach( $t_pieces as $t_piece ) {
			if( preg_match( '/(<pre[^>]*>.*?<\/pre>)/is', $t_piece ) ) {
				$t_piece = preg_replace( '/<br[^>]*?>/', '', $t_piece );

				# @@@ thraxisp - this may want to be replaced by html_entity_decode (or equivalent)
				#     if other encoded characters are a problem
				$t_piece = preg_replace( '/&#160;/', ' ', $t_piece );
				if( ON == config_get( 'wrap_in_preformatted_text' ) ) {
					# Use PCRE_UTF8 modifier to ensure a correct char count
					$t_output .= preg_replace( '/([^\n]{' . $p_wrap . ',}?[\s]+)(?!<\/pre>)/u', "$1", $t_piece );
				} else {
					$t_output .= $t_piece;
				}
			} else {
				$t_output .= nl2br( $t_piece );
			}
		}
		return $t_output;
	} else {
		return nl2br( $p_string );
	}
}

/**
 * Prepare a multiple line string for display to HTML
 * @param string $p_string String to be processed.
 * @return string
 */
function string_display( $p_string ) {
	return event_signal( 'EVENT_DISPLAY_TEXT', $p_string, true );
}

/**
 * Prepare a single line string for display to HTML
 * @param string $p_string String to be processed.
 * @return string
 */
function string_display_line( $p_string ) {
	return event_signal( 'EVENT_DISPLAY_TEXT', $p_string, false );
}

/**
 * Prepare a string for display to HTML and add href anchors for URLs, emails
 * and bug references
 * @param string $p_string String to be processed.
 * @return string
 */
function string_display_links( $p_string ) {
	return event_signal( 'EVENT_DISPLAY_FORMATTED', $p_string, true );
}

/**
 * Prepare a single line string for display to HTML and add href anchors for
 * URLs, emails and bug references
 * @param string $p_string String to be processed.
 * @return string
 */
function string_display_line_links( $p_string ) {
	return event_signal( 'EVENT_DISPLAY_FORMATTED', $p_string, false );
}

/**
 * Prepare a string for display in rss
 * @param string $p_string String to be processed.
 * @return string
 */
function string_rss_links( $p_string ) {
	# rss can not start with &#160; which spaces will be replaced into by string_display().
	$t_string = trim( $p_string );

	$t_string = event_signal( 'EVENT_DISPLAY_RSS', $t_string );

	# another escaping to escape the special characters created by the generated links
	return string_html_specialchars( $t_string );
}

/**
 * Prepare a string for plain text display in email
 * @param string $p_string String to be processed.
 * @return string
 */
function string_email( $p_string ) {
	return string_strip_hrefs( $p_string );
}

/**
 * Prepare a string for plain text display in email and add URLs for bug
 * links
 * @param string $p_string String to be processed.
 * @return string
 */
function string_email_links( $p_string ) {
	return event_signal( 'EVENT_DISPLAY_EMAIL', $p_string );
}

/**
 * Process a string for display in a textarea box
 * @param string $p_string String to be processed.
 * @return string
 */
function string_textarea( $p_string ) {
	return string_html_specialchars( $p_string );
}

/**
 * Process a string for display in a text box
 * @param string $p_string String to be processed.
 * @return string
 */
function string_attribute( $p_string ) {
	return string_html_specialchars( $p_string );
}

/**
 * Process a string for inclusion in a URL as a GET parameter
 * @param string $p_string String to be processed.
 * @return string
 */
function string_url( $p_string ) {
	return rawurlencode( $p_string );
}

/**
 * validate the url as part of this site before continuing
 * @param string  $p_url             URL to be processed.
 * @param boolean $p_return_absolute Whether to return the absolute URL to this Mantis instance.
 * @return string
 */
function string_sanitize_url( $p_url, $p_return_absolute = false ) {
	$t_url = strip_tags( urldecode( $p_url ) );

	$t_path = rtrim( config_get_global( 'path' ), '/' );
	$t_short_path = rtrim( config_get_global( 'short_path' ), '/' );

	$t_pattern = '(?:/*(?P<script>[^\?#]*))(?:\?(?P<query>[^#]*))?(?:#(?P<anchor>[^#]*))?';

	# Break the given URL into pieces for path, script, query, and anchor
	$t_type = 0;
	if( preg_match( '@^(?P<path>' . preg_quote( $t_path, '@' ) . ')' . $t_pattern . '$@', $t_url, $t_matches ) ) {
		$t_type = 1;
	} else if( !empty( $t_short_path )
			&& preg_match( '@^(?P<path>' . preg_quote( $t_short_path, '@' ) . ')' . $t_pattern . '$@', $t_url, $t_matches )
	) {
		$t_type = 2;
	} else if( preg_match( '@^(?P<path>)' . $t_pattern . '$@', $t_url, $t_matches ) ) {
		$t_type = 3;
	}

	# Check for URL's pointing to other domains
	if( 0 == $t_type || empty( $t_matches['script'] ) ||
		3 == $t_type && preg_match( '@(?:[^:]*)?:/*@', $t_url ) > 0 ) {

		return ( $p_return_absolute ? $t_path . '/' : '' ) . 'index.php';
	}

	# Start extracting regex matches
	# Encode backslashes to prevent unwanted escaping of a leading '/' allowing
	# redirection to external sites
	$t_script = strtr( $t_matches['script'], array( '\\' => '%5C' ) );
	$t_script_path = $t_matches['path'];

	# Clean/encode query params
	$t_query = '';
	if( isset( $t_matches['query'] ) ) {
		$t_pairs = array();
		parse_str( html_entity_decode( $t_matches['query'] ), $t_pairs );

		$t_clean_pairs = array();
		foreach( $t_pairs as $t_key => $t_value ) {
			if( is_array( $t_value ) ) {
				foreach( $t_value as $t_value_each ) {
					$t_clean_pairs[] .= rawurlencode( $t_key ) . '[]=' . rawurlencode( $t_value_each );
				}
			} else {
				$t_clean_pairs[] = rawurlencode( $t_key ) . '=' . rawurlencode( $t_value );
			}
		}

		if( !empty( $t_clean_pairs ) ) {
			$t_query = '?' . join( '&', $t_clean_pairs );
		}
	}

	# encode link anchor
	$t_anchor = '';
	if( isset( $t_matches['anchor'] ) ) {
		$t_anchor = '#' . rawurlencode( $t_matches['anchor'] );
	}

	# Return an appropriate re-combined URL string
	if( $p_return_absolute ) {
		return $t_path . '/' . $t_script . $t_query . $t_anchor;
	} else {
		return ( !empty( $t_script_path ) ? $t_script_path . '/' : '' ) . $t_script . $t_query . $t_anchor;
	}
}

/**
 * Process $p_string, looking for bug ID references and creating bug view
 * links for them.
 *
 * Returns the processed string.
 *
 * If $p_include_anchor is true, include the href tag, otherwise just insert
 * the URL
 *
 * The bug tag ('#' by default) must be at the beginning of the string or
 * preceded by a character that is not a letter, a number or an underscore
 *
 * if $p_include_anchor = false, $p_fqdn is ignored and assumed to true.
 * @param string  $p_string         String to be processed.
 * @param boolean $p_include_anchor Whether to include the href tag or just the URL.
 * @param boolean $p_detail_info    Whether to include more detailed information (e.g. title attribute / project) in the returned string.
 * @param boolean $p_fqdn           Whether to return an absolute or relative link.
 * @return string
 */
function string_process_bug_link( $p_string, $p_include_anchor = true, $p_detail_info = true, $p_fqdn = false ) {
	static $s_bug_link_callback = array();

	$t_tag = config_get( 'bug_link_tag' );

	# bail if the link tag is blank
	if( '' == $t_tag || $p_string == '' ) {
		return $p_string;
	}

	if( !isset( $s_bug_link_callback[$p_include_anchor][$p_detail_info][$p_fqdn] ) ) {
		if( $p_include_anchor ) {
			$s_bug_link_callback[$p_include_anchor][$p_detail_info][$p_fqdn] =
				function( $p_array ) use( $p_detail_info, $p_fqdn ) {
					$c_bug_id = (int)$p_array[2];
					if( bug_exists( $c_bug_id ) ) {
						$t_project_id = bug_get_field( $c_bug_id, 'project_id' );
						$t_view_bug_threshold = config_get( 'view_bug_threshold', null, null, $t_project_id );
						if( access_has_bug_level( $t_view_bug_threshold, $c_bug_id ) ) {
							return $p_array[1] .
								string_get_bug_view_link(
									$c_bug_id,
									(boolean)$p_detail_info,
									(boolean)$p_fqdn
								);
						}
					}
					return $p_array[0];
				}; # end of bug link callback closure
		} else {
			$s_bug_link_callback[$p_include_anchor][$p_detail_info][$p_fqdn] =
				function( $p_array ) {
					$c_bug_id = (int)$p_array[2];
					if( bug_exists( $c_bug_id ) ) {
						# Create link regardless of user's access to the bug
						return $p_array[1] .
							string_get_bug_view_url_with_fqdn( $c_bug_id );
					}
					return $p_array[0];
				}; # end of bug link callback closure
		}
	}

	$p_string = preg_replace_callback(
		'/(^|[^\w&])' . preg_quote( $t_tag, '/' ) . '(\d+)\b/',
		$s_bug_link_callback[$p_include_anchor][$p_detail_info][$p_fqdn],
		$p_string
	);
	return $p_string;
}

/**
 * Process $p_string, looking for bugnote ID references and creating bug view
 * links for them.
 *
 * Returns the processed string.
 *
 * If $p_include_anchor is true, include the href tag, otherwise just insert
 * the URL
 *
 * The bugnote tag ('~' by default) must be at the beginning of the string or
 * preceded by a character that is not a letter, a number or an underscore
 *
 * if $p_include_anchor = false, $p_fqdn is ignored and assumed to true.
 * @param string  $p_string         String to be processed.
 * @param boolean $p_include_anchor Whether to include the href tag or just the URL.
 * @param boolean $p_detail_info    Whether to include more detailed information (e.g. title attribute / project) in the returned string.
 * @param boolean $p_fqdn           Whether to return an absolute or relative link.
 * @return string
 */
function string_process_bugnote_link( $p_string, $p_include_anchor = true, $p_detail_info = true, $p_fqdn = false ) {
	static $s_bugnote_link_callback = array();

	$t_tag = config_get( 'bugnote_link_tag' );

	# bail if the link tag is blank
	if( '' == $t_tag || $p_string == '' ) {
		return $p_string;
	}

	if( !isset( $s_bugnote_link_callback[$p_include_anchor][$p_detail_info][$p_fqdn] ) ) {
		if( $p_include_anchor ) {
			$s_bugnote_link_callback[$p_include_anchor][$p_detail_info][$p_fqdn] =
				function( $p_array ) use( $p_detail_info, $p_fqdn ) {
					global $g_project_override;
					$c_bugnote_id = (int)$p_array[2];
					if( bugnote_exists( $c_bugnote_id ) ) {
						$t_bug_id = bugnote_get_field( $c_bugnote_id, 'bug_id' );
						if( bug_exists( $t_bug_id ) ) {
							$g_project_override = bug_get_field( $t_bug_id, 'project_id' );
							if(   access_compare_level(
										user_get_access_level( auth_get_current_user_id(),
										bug_get_field( $t_bug_id, 'project_id' ) ),
										config_get( 'private_bugnote_threshold' )
								   )
								|| bugnote_get_field( $c_bugnote_id, 'reporter_id' ) == auth_get_current_user_id()
								|| bugnote_get_field( $c_bugnote_id, 'view_state' ) == VS_PUBLIC
							) {
								$g_project_override = null;
								return $p_array[1] .
									string_get_bugnote_view_link(
										$t_bug_id,
										$c_bugnote_id,
										(boolean)$p_detail_info,
										(boolean)$p_fqdn
									);
							}
							$g_project_override = null;
						}
					}
					return $p_array[0];
				}; # end of bugnote link callback closure
		} else {
			$s_bugnote_link_callback[$p_include_anchor][$p_detail_info][$p_fqdn] =
				function( $p_array ) {
					$c_bugnote_id = (int)$p_array[2];
					if( bugnote_exists( $c_bugnote_id ) ) {
						$t_bug_id = bugnote_get_field( $c_bugnote_id, 'bug_id' );
						if( $t_bug_id && bug_exists( $t_bug_id ) ) {
							return $p_array[1] .
								string_get_bugnote_view_url_with_fqdn( $t_bug_id, $c_bugnote_id );
						}
					}
					return $p_array[0];
				}; # end of bugnote link callback closure
		}
	}
	$p_string = preg_replace_callback(
		'/(^|[^\w])' . preg_quote( $t_tag, '/' ) . '(\d+)\b/',
		$s_bugnote_link_callback[$p_include_anchor][$p_detail_info][$p_fqdn],
		$p_string
	);
	return $p_string;
}

/**
 * Search email addresses and URLs for a few common protocols in the given
 * string, and replace occurrences with href anchors.
 * @param string $p_string String to be processed.
 * @return string
 */
function string_insert_hrefs( $p_string ) {
	static $s_url_regex = null;
	static $s_email_regex = null;

	if( !config_get( 'html_make_links' ) ) {
		return $p_string;
	}

	# Initialize static variables
	if( is_null( $s_url_regex ) ) {
		# URL protocol. The regex accepts a small subset from the list of valid
		# IANA permanent and provisional schemes defined in
		# http://www.iana.org/assignments/uri-schemes/uri-schemes.xhtml
		$t_url_protocol = '(?:https?|s?ftp|file|irc[6s]?|ssh|telnet|nntp|git|svn(?:\+ssh)?|cvs):\/\/';

		# %2A notation in url's
		$t_url_hex = '%[[:digit:]A-Fa-f]{2}';

		# valid set of characters that may occur in url scheme. Note: - should be first (A-F != -AF).
		$t_url_valid_chars       = '-_.,!~*\';\/?%^\\\\:@&={\|}+$#[:alnum:]\pL';
		$t_url_chars             = "(?:${t_url_hex}|[${t_url_valid_chars}\(\)\[\]])";
		$t_url_chars2            = "(?:${t_url_hex}|[${t_url_valid_chars}])";
		$t_url_chars_in_brackets = "(?:${t_url_hex}|[${t_url_valid_chars}\(\)])";
		$t_url_chars_in_parens   = "(?:${t_url_hex}|[${t_url_valid_chars}\[\]])";

		$t_url_part1 = $t_url_chars;
		$t_url_part2 = "(?:\(${t_url_chars_in_parens}*\)|\[${t_url_chars_in_brackets}*\]|${t_url_chars2})";

		$s_url_regex = "/(${t_url_protocol}(${t_url_part1}*?${t_url_part2}+))/su";

		# e-mail regex
		$s_email_regex = substr_replace( email_regex_simple(), '(?:mailto:)?', 1, 0 );
	}

	# Find any URL in a string and replace it with a clickable link
	$p_string = preg_replace_callback(
		$s_url_regex,
		function ( $p_match ) {
			$t_url_href = 'href="' . rtrim( $p_match[1], '.' ) . '"';
			if( config_get( 'html_make_links' ) == LINKS_NEW_WINDOW ) {
				$t_url_target = ' target="_blank"';
			} else {
				$t_url_target = '';
			}
			return "<a ${t_url_href}${t_url_target}>${p_match[1]}</a>";
		},
		$p_string
	);

	# Find any email addresses in the string and replace them with a clickable
	# mailto: link, making sure that we skip processing of any existing anchor
	# tags, to avoid parts of URLs such as https://user@example.com/ or
	# http://user:password@example.com/ to be not treated as an email.
	$p_string = string_process_exclude_anchors(
		$p_string,
		function( $p_string ) use ( $s_email_regex ) {
			return preg_replace( $s_email_regex, '<a href="mailto:\0">\0</a>', $p_string );
		}
	);

	return $p_string;
}

/**
 * Processes a string, ignoring anchor tags.
 * Applies the specified callback function to the text between anchor tags;
 * the anchors themselves will be left as-is.
 * @param string   $p_string   String to process
 * @param callable $p_callback Function to apply
 * @return string
 */
function string_process_exclude_anchors( $p_string, $p_callback ) {
	static $s_anchor_regex = '/(<a[^>]*>.*?<\/a>)/is';

	$t_pieces = preg_split( $s_anchor_regex, $p_string, null, PREG_SPLIT_DELIM_CAPTURE );

	$t_string = '';
	foreach( $t_pieces as $t_piece ) {
		if( preg_match( $s_anchor_regex, $t_piece ) ) {
			$t_string .= $t_piece;
		} else {
			$t_string .= $p_callback( $t_piece );
		}
	}

	return $t_string;
}

/**
 * Detect href anchors in the string and replace them with URLs and email addresses
 * @param string $p_string String to be processed.
 * @return string
 */
function string_strip_hrefs( $p_string ) {
	# First grab mailto: hrefs.  We don't care whether the URL is actually
	# correct - just that it's inside an href attribute.
	$p_string = preg_replace( '/<a\s[^\>]*href="mailto:([^\"]+)"[^\>]*>[^\<]*<\/a>/si', '\1', $p_string );

	# Then grab any other href
	$p_string = preg_replace( '/<a\s[^\>]*href="([^\"]+)"[^\>]*>[^\<]*<\/a>/si', '\1', $p_string );
	return $p_string;
}

/**
 * This function looks for text with htmlentities
 * like &lt;b&gt; and converts it into the corresponding
 * html < b > tag based on the configuration information
 * @param string  $p_string    String to be processed.
 * @param boolean $p_multiline Whether the string being processed is a multi-line string.
 * @return string
 */
function string_restore_valid_html_tags( $p_string, $p_multiline = true ) {
	global $g_cache_html_valid_tags_single_line, $g_cache_html_valid_tags;

	if( is_blank( ( $p_multiline ? $g_cache_html_valid_tags : $g_cache_html_valid_tags_single_line ) ) ) {
		$t_html_valid_tags = config_get_global( $p_multiline ? 'html_valid_tags' : 'html_valid_tags_single_line' );

		if( OFF === $t_html_valid_tags || is_blank( $t_html_valid_tags ) ) {
			return $p_string;
		}

		$t_tags = explode( ',', $t_html_valid_tags );
		foreach( $t_tags as $t_key => $t_value ) {
			if( !is_blank( $t_value ) ) {
				$t_tags[$t_key] = trim( $t_value );
			}
		}
		$t_tags = implode( '|', $t_tags );
		if( $p_multiline ) {
			$g_cache_html_valid_tags = $t_tags;
		} else {
			$g_cache_html_valid_tags_single_line = $t_tags;
		}
	} else {
		$t_tags = ( $p_multiline ? $g_cache_html_valid_tags : $g_cache_html_valid_tags_single_line );
	}

	$p_string = preg_replace( '/&lt;(' . $t_tags . ')\s*&gt;/ui', '<\\1>', $p_string );
	$p_string = preg_replace( '/&lt;\/(' . $t_tags . ')\s*&gt;/ui', '</\\1>', $p_string );
	return preg_replace( '/&lt;(' . $t_tags . ')\s*\/&gt;/ui', '<\\1 />', $p_string );
}

/**
 * return the name of a bug page
 * $p_action should be something like 'view', 'update', or 'report'
 * @param string  $p_action  A valid action being performed - currently one of view, update or report.
 * @return string
 */
function string_get_bug_page( $p_action ) {
	switch( $p_action ) {
		case 'view':
			return 'bug_view_page.php';
		case 'update':
			return 'bug_update_page.php';
		case 'report':
			return 'bug_report_page.php';
	}

	trigger_error( ERROR_GENERIC, ERROR );
}

/**
 * return an href anchor that links to a bug VIEW page for the given bug
 * @param integer $p_bug_id	     A bug identifier.
 * @param boolean $p_detail_info Whether to include more detailed information (e.g. title attribute / project) in the returned string.
 * @param boolean $p_fqdn        Whether to return an absolute or relative link.
 * @return string
 */
function string_get_bug_view_link( $p_bug_id, $p_detail_info = true, $p_fqdn = false ) {
	if( bug_exists( $p_bug_id ) ) {
		$t_link = '<a href="';
		if( $p_fqdn ) {
			$t_link .= config_get_global( 'path' );
		} else {
			$t_link .= config_get_global( 'short_path' );
		}
		$t_link .= string_get_bug_view_url( $p_bug_id ) . '"';
		if( $p_detail_info ) {
			$t_summary = string_attribute( bug_get_field( $p_bug_id, 'summary' ) );
			$t_project_id = bug_get_field( $p_bug_id, 'project_id' );
			$t_status = string_attribute( get_enum_element( 'status', bug_get_field( $p_bug_id, 'status' ), $t_project_id ) );
			$t_link .= ' title="[' . $t_status . '] ' . $t_summary . '"';

			$t_resolved = bug_get_field( $p_bug_id, 'status' ) >= config_get( 'bug_resolved_status_threshold', null, null, $t_project_id );
			if( $t_resolved ) {
				$t_link .= ' class="resolved"';
			}
		}
		$t_link .= '>' . bug_format_id( $p_bug_id ) . '</a>';
	} else {
		$t_link = bug_format_id( $p_bug_id );
	}

	return $t_link;
}

/**
 * return an href anchor that links to a bug VIEW page for the given bug
 * @param integer $p_bug_id      A bug identifier.
 * @param integer $p_bugnote_id  A bugnote identifier.
 * @param boolean $p_detail_info Whether to include more detailed information (e.g. title attribute / project) in the returned string.
 * @param boolean $p_fqdn        Whether to return an absolute or relative link.
 * @return string
 */
function string_get_bugnote_view_link( $p_bug_id, $p_bugnote_id, $p_detail_info = true, $p_fqdn = false ) {
	$t_bug_id = (int)$p_bug_id;

	if( bug_exists( $t_bug_id ) && bugnote_exists( $p_bugnote_id ) ) {
		$t_link = '<a href="';
		if( $p_fqdn ) {
			$t_link .= config_get_global( 'path' );
		} else {
			$t_link .= config_get_global( 'short_path' );
		}

		$t_link .= string_get_bugnote_view_url( $p_bug_id, $p_bugnote_id ) . '"';
		if( $p_detail_info ) {
			$t_reporter = string_attribute( user_get_name( bugnote_get_field( $p_bugnote_id, 'reporter_id' ) ) );
			$t_update_date = string_attribute( date( config_get( 'normal_date_format' ), ( bugnote_get_field( $p_bugnote_id, 'last_modified' ) ) ) );
			$t_link .= ' title="' . bug_format_id( $t_bug_id ) . ': [' . $t_update_date . '] ' . $t_reporter . '"';
		}

		$t_link .= '>' . bug_format_id( $t_bug_id ) . ':' . bugnote_format_id( $p_bugnote_id ) . '</a>';
	} else {
		$t_link = bugnote_format_id( $t_bug_id ) . ':' . bugnote_format_id( $p_bugnote_id );
	}

	return $t_link;
}

/**
 * return the name and GET parameters of a bug VIEW page for the given bug
 * @param integer $p_bug_id A bug identifier.
 * @return string
 */
function string_get_bug_view_url( $p_bug_id ) {
	return 'view.php?id=' . $p_bug_id;
}

/**
 * return the name and GET parameters of a bug VIEW page for the given bug
 * @param integer $p_bug_id     A bug identifier.
 * @param integer $p_bugnote_id A bugnote identifier.
 * @return string
 */
function string_get_bugnote_view_url( $p_bug_id, $p_bugnote_id ) {
	return 'view.php?id=' . $p_bug_id . '#c' . $p_bugnote_id;
}

/**
 * return the name and GET parameters of a bug VIEW page for the given bug
 * account for the user preference and site override
 * The returned url includes the fully qualified domain, hence it is suitable to be included
 * in emails.
 * @param integer $p_bug_id     A bug identifier.
 * @param integer $p_bugnote_id A bug note identifier.
 * @return string
 */
function string_get_bugnote_view_url_with_fqdn( $p_bug_id, $p_bugnote_id ) {
	return config_get_global( 'path' ) . string_get_bug_view_url( $p_bug_id ) . '#c' . $p_bugnote_id;
}

/**
 * return the name and GET parameters of a bug VIEW page for the given bug
 * account for the user preference and site override
 * The returned url includes the fully qualified domain, hence it is suitable to be included in emails.
 * @param integer $p_bug_id  A bug identifier.
 * @return string
 */
function string_get_bug_view_url_with_fqdn( $p_bug_id ) {
	return config_get_global( 'path' ) . string_get_bug_view_url( $p_bug_id );
}

/**
 * return an href anchor that links to a bug UPDATE page for the given bug
 * @param integer $p_bug_id  A bug identifier.
 * @return string
 */
function string_get_bug_update_link( $p_bug_id ) {
	$t_summary = string_attribute( bug_get_field( $p_bug_id, 'summary' ) );
	return '<a href="' . helper_mantis_url( string_get_bug_update_url( $p_bug_id ) ) . '" title="' . $t_summary . '">' . bug_format_id( $p_bug_id ) . '</a>';
}

/**
 * return the name and GET parameters of a bug UPDATE page
 * @param integer $p_bug_id  A bug identifier.
 * @return string
 */
function string_get_bug_update_url( $p_bug_id ) {
	return string_get_bug_update_page() . '?bug_id=' . $p_bug_id;
}

/**
 * return the name of a bug UPDATE page
 * @return string
 */
function string_get_bug_update_page() {
	return string_get_bug_page( 'update' );
}

/**
 * return an href anchor that links to a bug REPORT page
 * @return string
 */
function string_get_bug_report_link() {
	return '<a href="' . helper_mantis_url( string_get_bug_report_url() ) . '">' . lang_get( 'report_bug_link' ) . '</a>';
}

/**
 * return the name of a bug REPORT page
 * @return string
 */
function string_get_bug_report_url() {
	return string_get_bug_page( 'report' );
}

/**
 * return the complete URL link to the verify page including the confirmation hash
 * @param integer $p_user_id      A valid user identifier.
 * @param string  $p_confirm_hash The confirmation hash value to include in the link.
 * @return string
 */
function string_get_confirm_hash_url( $p_user_id, $p_confirm_hash ) {
	return config_get_global( 'path' ) . 'verify.php?id=' . string_url( $p_user_id ) . '&confirm_hash=' . string_url( $p_confirm_hash );
}

	
/**
 * Format date for display
 * @param integer $p_date A date value to process.
 * @return string
 */
function string_format_complete_date( $p_date ) {
	return date( config_get( 'complete_date_format' ), $p_date );
}

/**
 * Shorten a string for display on a dropdown to prevent the page rendering too wide
 * ref issues #4630, #5072, #5131
 * @param string  $p_string The string to process.
 * @param integer $p_max    The maximum length of the string to use.
 *                          If not set, defaults to max_dropdown_length configuration variable.
 * @return string
 */
function string_shorten( $p_string, $p_max = null ) {
	if( $p_max === null ) {
		$t_max = config_get( 'max_dropdown_length' );
	} else {
		$t_max = (int)$p_max;
	}

	if( ( $t_max > 0 ) && ( mb_strlen( $p_string ) > $t_max ) ) {
		$t_pattern = '/([\s|.|,|\-|_|\/|\?]+)/';
		$t_bits = preg_split( $t_pattern, $p_string, -1, PREG_SPLIT_DELIM_CAPTURE );

		$t_string = '';
		$t_last = $t_bits[count( $t_bits ) - 1];
		$t_last_len = strlen( $t_last );

		if( count( $t_bits ) == 1 ) {
			$t_string .= mb_substr( $t_last, 0, $t_max - 3 );
			$t_string .= '...';
		} else {
			foreach( $t_bits as $t_bit ) {
				if( ( mb_strlen( $t_string ) + mb_strlen( $t_bit ) + $t_last_len + 3 <= $t_max ) || ( strpos( $t_bit, '.,-/?' ) > 0 ) ) {
					$t_string .= $t_bit;
				} else {
					break;
				}
			}
			$t_string .= '...' . $t_last;
		}
		return $t_string;
	} else {
		return $p_string;
	}
}

/**
 * Normalize a string by removing leading, trailing and excessive internal spaces
 * note a space is used as the pattern instead of '\s' to make it work with UTF-8 strings
 * @param string $p_string The string to process.
 * @return string
 */
function string_normalize( $p_string ) {
	return preg_replace( '/ +/', ' ', trim( $p_string ) );
}

/**
 * remap a field name to a string name (for sort filter)
 * @param string $p_string The string to process.
 * @return string
 */
function string_get_field_name( $p_string ) {
	$t_map = array(
		'attachment_count' => 'attachments',
		'category_id' => 'category',
		'handler_id' => 'assigned_to',
		'id' => 'email_bug',
		'last_updated' => 'updated',
		'project_id' => 'email_project',
		'reporter_id' => 'reporter',
		'view_state' => 'view_status',
	);

	$t_string = $p_string;
	if( isset( $t_map[$p_string] ) ) {
		$t_string = $t_map[$p_string];
	}
	return lang_get_defaulted( $t_string );
}

/**
 * Calls htmlentities on the specified string, passing along
 * the current character set.
 * @param string $p_string The string to process.
 * @return string
 */
function string_html_entities( $p_string ) {
	return htmlentities( $p_string, ENT_COMPAT, 'utf-8' );
}

/**
 * Calls htmlspecialchars on the specified string, handling utf8
 * @param string $p_string The string to process.
 * @return string
 */
function string_html_specialchars( $p_string ) {
	# achumakov: @ added to avoid warning output in unsupported codepages
	# e.g. 8859-2, windows-1257, Korean, which are treated as 8859-1.
	# This is VERY important for Eastern European, Baltic and Korean languages
	return preg_replace( '/&amp;(#[0-9]+|[a-z]+);/i', '&$1;', @htmlspecialchars( $p_string, ENT_COMPAT, 'utf-8' ) );
}

/**
 * Prepares a string to be used as part of header().
 * @param string $p_string The string to process.
 * @return string
 */
function string_prepare_header( $p_string ) {
	$t_string= explode( "\n", $p_string, 2 );
	$t_string= explode( "\r", $t_string[0], 2 );
	return $t_string[0];
}

/**
 * Replacement for str_pad. $padStr may contain multi-byte characters.
 *
 * @author Oliver Saunders <oliver (a) osinternetservices.com>
 * @param string $input
 * @param int $length
 * @param string $padStr
 * @param int $type ( same constants as str_pad )
 * @return string
 * @see http://www.php.net/str_pad
 * @see utf8_substr
 */
function utf8_str_pad( $input, $length, $padStr = ' ', $type = STR_PAD_RIGHT ) {

    $inputLen = mb_strlen($input);
    if ($length <= $inputLen) {
        return $input;
    }

    $padStrLen = mb_strlen($padStr);
    $padLen = $length - $inputLen;

    if ($type == STR_PAD_RIGHT) {
        $repeatTimes = ceil($padLen / $padStrLen);
        return mb_substr($input . str_repeat($padStr, $repeatTimes), 0, $length);
    }

    if ($type == STR_PAD_LEFT) {
        $repeatTimes = ceil($padLen / $padStrLen);
        return mb_substr(str_repeat($padStr, $repeatTimes), 0, floor($padLen)) . $input;
    }

    if ($type == STR_PAD_BOTH) {

        $padLen/= 2;
        $padAmountLeft = floor($padLen);
        $padAmountRight = ceil($padLen);
        $repeatTimesLeft = ceil($padAmountLeft / $padStrLen);
        $repeatTimesRight = ceil($padAmountRight / $padStrLen);

        $paddingLeft = mb_substr(str_repeat($padStr, $repeatTimesLeft), 0, $padAmountLeft);
        $paddingRight = mb_substr(str_repeat($padStr, $repeatTimesRight), 0, $padAmountLeft);
        return $paddingLeft . $input . $paddingRight;
    }

    trigger_error('utf8_str_pad: Unknown padding type (' . $type . ')',E_USER_ERROR);
}

/**
 * Return the number of UTF-8 characters in a string
 * @param string $p_string
 * @return integer number of UTF-8 characters in string
 * @deprecated mb_strlen() should be used in preference to this function
 */
function utf8_strlen( $p_string ) {
    error_parameters( __FUNCTION__ . '()', 'mb_strlen()' );
    trigger_error( ERROR_DEPRECATED_SUPERSEDED, DEPRECATED );
    return mb_strlen( $p_string );
}

/**
 * Get part of string
 * @param string $p_string
 * @param integer $p_offset
 * @param integer $p_length
 * @return mixed string or FALSE if failure
 * @deprecated mb_substr() should be used in preference to this function
 */
function utf8_substr( $p_string, $p_offset, $p_length = NULL ) {
    error_parameters( __FUNCTION__ . '()', 'mb_substr()' );
    trigger_error( ERROR_DEPRECATED_SUPERSEDED, DEPRECATED );
    return mb_substr( $p_string, $p_offset, $p_length );
}

/**
 * Make a string lowercase
 * @param string $p_string
 * @return string with all alphabetic characters converted to lowercase
 * @deprecated mb_strtolower() should be used in preference to this function
 */
function utf8_strtolower( $p_string ) {
    error_parameters( __FUNCTION__ . '()', 'mb_strtolower()' );
    trigger_error( ERROR_DEPRECATED_SUPERSEDED, DEPRECATED );
    return mb_strtolower( $p_string );
}
string_api.php (33,310 bytes)
sach

sach

2019-03-18 16:14

reporter   ~0061693

Last edited: 2019-03-19 12:20

View 2 revisions

I did some digging around in the code and found that the string_html_specialchars function looks like this:

function string_html_specialchars( $p_string ) {
    # achumakov: @ added to avoid warning output in unsupported codepages
    # e.g. 8859-2, windows-1257, Korean, which are treated as 8859-1.
    # This is VERY important for Eastern European, Baltic and Korean languages
    return preg_replace( '/&(#[0-9]+|[a-z]+);/i', '&$1;', @htmlspecialchars( $p_string, ENT_COMPAT, 'utf-8' ) );
}

Removed the 'amp;' from it, and now works fine.
Is this supposed to be correct or is this a bug?

My edited version (which works) look like this:

function string_html_specialchars( $p_string ) {
    # achumakov: @ added to avoid warning output in unsupported codepages
    # e.g. 8859-2, windows-1257, Korean, which are treated as 8859-1.
    # This is VERY important for Eastern European, Baltic and Korean languages
    return preg_replace( '/&(#[0-9]+|[a-z]+);/i', '&$1;', @htmlspecialchars( $p_string, ENT_COMPAT, 'utf-8' ) );
}
sach

sach

2019-03-18 16:17

reporter   ~0061694

I did some digging around in the code and found that the string_html_specialchars function looks like this:

See 'before' attachement.

Removed the 'amp;' from it, and now works fine.
Is this supposed to be correct or is this a bug?

My edited version (which works) look like this:

See 'after' attachment.



after.PNG (10,017 bytes)
after.PNG (10,017 bytes)
before.PNG (10,318 bytes)
before.PNG (10,318 bytes)
dregad

dregad

2019-03-19 08:25

developer   ~0061709

@sach The sample e-mail you posted contains HTML tags -- I'm not sure where those are coming from, as MantisBT generates plain-text e-mails, as shown in the sample below just generated from my dev box. As you can see, no URL-encoded ampersand there.



sach

sach

2019-03-19 11:52

reporter   ~0061711

#dregad, could this be because of GMail? I've specified a GMail account as the mantis EMail.

The receiving link shows as I've shown in both GMail and Outlook clients.

sach

sach

2019-03-19 11:53

reporter   ~0061712

Also, as mentioned above, getting rid of the amp; in string_html_specialchars() solves the problem for me. Do you know how that function is supposed to look like?

dregad

dregad

2019-03-19 12:51

developer   ~0061714

could this be because of GMail

I don't know what's causing it, but it's definitely caused by your environment or your e-mail client.

getting rid of the amp; in string_html_specialchars() solves the problem for me

That is most certainly not the correct solution. You are basically undoing what htmlspecialchar() does, i.e. transforming the & amp; back into an & which could introduce security issues (e.g. XSS) wherever MantisBT displays strings, particularly user-provided content.

Standard MantisBT does not call string_htmlspecialchars() in the process of generating the signup e-mail... Have a look at string_get_confirm_hash_url() function, as you can see there, the &before confirm_hash parameter is not being encoded.

Did you customize code or are you using any plugins ?

sach

sach

2019-03-19 15:26

reporter   ~0061716

Thanks for the information.

Re. customization.
I have done some customization, but this problem existed as soon as I installed Mantis.

I am using following plugins, but again, even without them the problem persists.



Capture.PNG (65,718 bytes)
Capture.PNG (65,718 bytes)
dregad

dregad

2019-03-20 04:23

developer   ~0061719

As mentioned previously, I am not able to reproduce this problem. I suggest you try to find out when and by what component the HTML markup is being added,

To help in your debugging effort, you can try to disable sending of e-mails (e.g. $g_email_send_using_cronjob = ON and disable cron), remove the force-sending of signup e-mail (comment-out line 573 in email_signup() function) then look in mantis_email_table for the raw message body after registering a new account.

I'm going to resolve this for now, feel free to reopen with evidence that the problem is caused by MantisBT.

Issue History

Date Modified Username Field Change
2019-03-15 20:01 sach New Issue
2019-03-15 20:04 sach File Added: err.PNG
2019-03-15 20:04 sach Note Added: 0061677
2019-03-15 20:07 sach File Added: string_api.php
2019-03-15 20:07 sach Note Added: 0061678
2019-03-18 16:14 sach Note Added: 0061693
2019-03-18 16:17 sach File Added: after.PNG
2019-03-18 16:17 sach File Added: before.PNG
2019-03-18 16:17 sach Note Added: 0061694
2019-03-19 08:25 dregad File Added: screenshot-20190319-1323.png
2019-03-19 08:25 dregad Note Added: 0061709
2019-03-19 08:25 dregad Status new => feedback
2019-03-19 11:52 sach Note Added: 0061711
2019-03-19 11:52 sach Status feedback => new
2019-03-19 11:53 sach Note Added: 0061712
2019-03-19 12:20 dregad Note Edited: 0061693 View Revisions
2019-03-19 12:51 dregad Note Added: 0061714
2019-03-19 13:01 dregad Description Updated View Revisions
2019-03-19 15:26 sach File Added: Capture.PNG
2019-03-19 15:26 sach Note Added: 0061716
2019-03-20 04:23 dregad Assigned To => dregad
2019-03-20 04:23 dregad Status new => resolved
2019-03-20 04:23 dregad Resolution open => unable to reproduce
2019-03-20 04:23 dregad Note Added: 0061719
2019-04-05 17:14 atrol Status resolved => closed