1
0
mirror of https://github.com/erusev/parsedown.git synced 2023-08-10 21:13:06 +03:00

Compare commits

..

164 Commits
1.5.1 ... 1.7.0

Author SHA1 Message Date
6678d59be4 Merge pull request #495 from aidantwoods/anti-xss
Prevent various XSS attacks [rebase and update of #276]
2018-02-28 13:41:37 +02:00
c999a4b61b improve readme 2018-01-29 20:55:30 +02:00
e938ab4ffe improve readme 2018-01-29 20:54:40 +02:00
e69374af0d improve readme 2018-01-29 20:52:27 +02:00
1196ed9512 Merge pull request #548 from m1guelpf-forks/patch-1
Update license year
2018-01-01 18:48:54 +02:00
1244122b84 Update LICENSE.txt 2018-01-01 14:09:31 +01:00
d98d60aaf3 Update license year 2017-12-31 22:10:48 +01:00
296ebf0e60 Merge pull request #429 from pablotheissen/patch-1
Support html tags containing dashes
2017-11-19 11:15:43 +02:00
a60ba300b1 Merge pull request #540 from jbafford/patch-1
Fix typo in README
2017-11-15 10:31:22 +02:00
089789dfff Fix typo in README 2017-11-14 17:13:31 -05:00
fbe3fe878f Merge pull request #539 from gabriel-caruso/phpunit
Use PHPUnit\Framework\TestCase instead of PHPUnit_Framework_TestCase
2017-11-14 22:44:03 +02:00
09827f542c Rewrite Travis CI 2017-11-14 15:19:24 -02:00
70ef6f5521 Make Travis CI use installed PHPUnit version, not global one 2017-11-14 13:21:11 -02:00
691e36b1f2 Use PHPUnit\Framework\TestCase instead of PHPUnit_Framework_TestCase 2017-11-11 00:56:03 -02:00
af6affdc2c improve readme 2017-11-06 16:54:00 +02:00
9cf41f27ab improve readme 2017-10-22 16:01:34 +03:00
16aadff2ed improve readme 2017-10-22 16:00:43 +03:00
07c937583d improve readme 2017-10-22 15:57:58 +03:00
728952b90a Merge pull request #499 from aidantwoods/fix/hhvm
Fix hhvm build failure
2017-05-14 17:47:48 +03:00
c82af01bd6 add sudo false 2017-05-14 14:39:09 +01:00
67c3efbea0 according to https://tools.ietf.org/html/rfc3986#section-3 the colon is a required part of the syntax, other methods of achieving the colon character (as to browser interpretation) should be taken care of by htmlencoding that is done on all attribute content 2017-05-10 16:57:18 +01:00
593ffd45a3 Merge pull request #406 from adrilo/patch-1
Create .gitattributes
2017-05-10 12:28:53 +03:00
bbb7687f31 safeMode will either apply all sanitisation techniques to an element or none (note that encoding HTML entities is done regardless because it speaks to character context, and that the only attributes/elements we should permit are the ones we actually mean to create) 2017-05-09 19:31:36 +01:00
b1e5aebaf6 add single safeMode option that encompasses protection from link destination xss and plain markup based xss into a single on/off switch 2017-05-09 19:22:58 +01:00
c63b690a79 remove duplicates 2017-05-09 14:50:15 +01:00
226f636360 remove $safe flag 2017-05-07 13:45:59 +01:00
2e4afde68d faster check substr at beginning of string 2017-05-06 16:32:51 +01:00
dc30cb441c add more protocols to the whitelist 2017-05-05 21:32:27 +01:00
f76b10aaab update readme 2017-05-04 10:28:55 +03:00
054ba3c487 urlencode urls that are potentially unsafe:
this should break urls that attempt to include a protocol, or port (these are absolute URLs and should have a whitelisted protocol for use)
but URLs that are relative, or relative from the site root should be preserved (though characters non essential for the URL structure may be urlencoded)

this approach has significant advantages over attempting to locate something like `javascript:alert(1)` or `javascript:alert(1)` (which are both valid) because browsers have been known to ignore ridiculous characters when encountered (meaning something like `jav\ta\0\0script:alert(1)` would be xss :( ). Instead of trying to chase down a way to interpret a URL to decide whether there is a protocol, this approach ensures that two essential characters needed to achieve a colon are encoded `:` (obviously) and `;` (from `:`). If these characters appear in a relative URL then they are equivalent to their URL encoded form and so this change will be non breaking for that case.
2017-05-03 17:01:27 +01:00
4bae1c9834 whitelist regex for good attribute (no
no chars that could form a delimiter allowed
2017-05-03 00:39:01 +01:00
aee3963e6b jpeg, not jpg 2017-05-02 19:55:03 +01:00
4dc98b635d whitelist changes:
* add gif and jpg as allowed data images
* ensure that user controlled content fall only in the "data section" of the data URI (and does not intersect content-type definition in any way (best to be safe than sorry ;-)))
  "data section" as defined in: https://tools.ietf.org/html/rfc2397#section-3
2017-05-02 19:48:25 +01:00
e4bb12329e array_keys is probably faster 2017-05-02 01:32:24 +01:00
6d0156d707 dump attributes that contain characters that are impossible for validity, or very unlikely 2017-05-02 00:48:48 +01:00
29ad172261 Merge pull request #496 from aidantwoods/fix/ditch-hhvm-nightly
replace hhvm nightly with nightly
2017-05-01 19:35:36 +03:00
131ba75851 filter onevent attributes 2017-05-01 15:44:04 +01:00
924b26e16c replace hhvm nightly with nightly 2017-05-01 03:57:07 +01:00
af04ac92e2 add xss tests 2017-05-01 03:33:49 +01:00
6bb66db00f anti-xss
protect all attributes and content from xss via element method
filter special attributes (a href, img src)
expand url whitelist slightly to permit data images and mailto links
2017-05-01 03:25:07 +01:00
b3d45c4bb9 Add html escaping to all attributes capable of holding user input. 2017-05-01 02:00:38 +01:00
1d4296f34d Customizable whitelist of schemas for safeLinks 2017-05-01 01:58:34 +01:00
bf5105cb1a Improve safeLinks with whitelist. 2017-05-01 01:58:34 +01:00
1140613fc7 Prevent various XSS attacks 2017-05-01 01:58:34 +01:00
4367f89a74 attempt to fix failing builds on 5.3 2017-03-29 19:30:24 +03:00
1bf24f7334 add kbd to text-level elements 2017-03-29 19:04:15 +03:00
0a09d5ad45 update tests to reflect changes in phpunit 6.0 2017-03-23 20:21:18 +02:00
3fc442b078 Merge pull request #484 from hkdobrev/patch-1
Add Symfony demo to "Who uses it?"
2017-03-10 09:41:24 +02:00
bd0e31a7dd Add Symfony demo to "Who uses it?"
409a65b373/composer.json (L24)
2017-03-10 01:04:53 +02:00
dfaf03639a Merge pull request #480 from pjona/patch-1
Removed double semicolon
2017-03-08 23:21:03 +02:00
7081afe8cb Removed double semicolon 2017-03-02 12:43:51 +01:00
4b6493999a Merge pull request #465 from aidantwoods/patch-8
Trim surrounding whitespace from URL in inlineLink
2017-01-23 09:45:19 +02:00
0172d779d7 Trim surrounding whitespace from URL in inlineLink
Fixes https://github.com/erusev/parsedown-extra/issues/103
2017-01-21 11:06:41 +00:00
cc5b38ca39 Merge pull request #459 from gene-sis/fix_inlineLink_regex
fix_inlineLink_regex
2017-01-07 16:51:03 +02:00
48351504de adjust two regex pattern within inlineLink() to reduce backtracking
add test with base64 image
2017-01-07 00:45:38 +01:00
20ff8bbb57 Merge pull request #447 from greut/phpunit-from-extra
Fix include from ParsedownTest
2016-11-02 17:56:58 +02:00
bc21988fe5 Fix include from ParsedownTest
I wasn't able to run all the tests from ParsedownExtra because of it.
2016-11-02 09:27:35 +01:00
e3c3e28554 Merge pull request #446 from jamesevickery/master
Grammar update
2016-10-25 17:39:55 +03:00
f053740132 Merge pull request #1 from erusev/master
Merge pull request #445 from jamesevickery/master
2016-10-25 15:24:11 +01:00
7a92a31739 Grammar update 2016-10-25 15:22:17 +01:00
6eca8796fb Merge pull request #445 from jamesevickery/master
Tiny grammar correction
2016-10-25 17:21:57 +03:00
8876c0984e Tiny grammar correction 2016-10-25 15:10:22 +01:00
cbc4b3f612 Merge pull request #436 from grogy/php71
Added PHP version 7.1 for tests
2016-10-09 13:04:16 +03:00
0080ef218e Added PHP version 7.1 for tests 2016-10-09 01:58:47 +02:00
f4e0234af0 Merge pull request #431 from aidantwoods/patch-2
Allow parsedown to specify list start attribute
2016-10-05 22:55:34 +03:00
5c22531e4d Allow parsedown to specify list start attribute
Syntax preferences
2016-10-05 18:27:54 +01:00
3978e33fd0 Allow parsedown to specify list start attribute
Remove github added tabs on blank lines
2016-10-05 18:17:12 +01:00
a37797ef34 Allow parsedown to specify list start attribute
Syntax preferences to match surrounding code
2016-10-05 18:15:47 +01:00
e3cd271f16 Allow parsedown to specify list start attribute
Performance: Swap preg_replace for stristr to obtain list start
2016-10-05 15:44:34 +01:00
f0b7b61c16 Allow parsedown to specify list start attribute
Should fix compatibility for PHP 5.3
2016-10-05 11:36:27 +01:00
ed41fcf3d6 Allow parsedown to specify list start attribute
oops
2016-10-05 10:06:40 +01:00
1fa8fae301 Allow parsedown to specify list start attribute
Readability improvements
2016-10-05 10:03:21 +01:00
932bafe0f0 update test for new feature 2016-10-04 17:43:37 +01:00
ac857809ab update test for new feature 2016-10-04 17:41:57 +01:00
846274996a Update ordered_list.html 2016-10-04 17:28:43 +01:00
c145a75848 update test for new feature 2016-10-04 15:44:50 +01:00
f17aa0438a Update Parsedown.php 2016-09-27 02:15:35 +01:00
38f4027d5e Update Parsedown.php
Okay, so maybe I should have looked 20 lines or so above where I made the edit in the element function – looks like it already supports adding attributes ;p
Have amended the change to blocklist to use the already existing functionality, and have reverted the change that I made to the element function.
2016-09-27 02:15:09 +01:00
2cee8d8a2d Update Parsedown.php
Looks like I might need to return the pattern which was used previously
Reverting last change as build still failed

This build will still fail, but I'm hoping it will only fair where the list start value has been inserted
2016-09-27 01:23:22 +01:00
cceefafd55 test
Attempting to determine which function change is causing test jobs to fail (in unexpected ways)
2016-09-27 01:16:00 +01:00
1c58e9d8d5 oops
oops
2016-09-27 00:57:57 +01:00
2772b034c6 Update Parsedown.php
(I think this should work)
Allow parsedown to specify list start attribute (see: https://github.com/erusev/parsedown/issues/100#issuecomment-249729602)
2016-09-27 00:53:51 +01:00
1a44cbd62c Update Parsedown.php
Made parsedown compatible with html-tags containing dashes.
see https://github.com/erusev/parsedown/issues/407#issuecomment-248833563
2016-09-22 12:21:39 +02:00
a2ed1592bd Merge pull request #424 from hkdobrev/patch-1
Update URL to dependants
2016-09-05 16:55:29 +03:00
3d7a473aa9 Update URL to dependants
As Packagist has now implemented the feature of listing packages
depending on another package, VersionEye is no longer needed for that.

As VersionEye scrapes the Packagist API to do the same, the original
source of information should be preferred.
2016-09-05 16:38:38 +03:00
f671ae7364 improve readme 2016-07-27 11:05:24 +03:00
b5951e08c6 Create .gitattributes
When using this library, all the files related to tests can be ignored. Tests are only useful when working on the library itself.
2016-06-24 14:18:01 +02:00
490a8f35a4 remove incompatible comment 2016-03-09 19:02:39 +02:00
94688f21cc improve readme 2016-01-07 19:36:51 +02:00
693f2c4842 Merge pull request #377 from PhrozenByte/patch-1
Update composer.json requirements
2016-01-07 19:14:25 +02:00
9545a295cf README.md: Add PHP 5.3+ requirement 2016-01-07 16:48:43 +01:00
3d649081e5 Update composer.json requirements
Fixes #374
2016-01-06 19:44:53 +01:00
32de2cedcc Merge pull request #373 from getgrav/master
Simple changes to make Parsedown more pluggable
2015-12-19 16:39:29 +02:00
e7443a2bd8 Fixed really sorry spelling errors 2015-12-18 20:45:14 -07:00
10a7ff776c Left as-is 2015-12-17 10:48:21 -07:00
5ad15b87fa Break out method_exists checks into extendable methods to allow for better pluggability 2015-12-17 10:46:44 -07:00
b166cab9a2 Make lines protected to allow for extendability 2015-12-17 10:46:04 -07:00
0f974bf34f improve readme 2015-11-26 15:08:28 +02:00
3d7cdeec5f remove duplicate item in: who uses it 2015-11-14 09:54:17 +02:00
97953b193e add Grav CMS to: who uses it 2015-11-14 09:52:07 +02:00
c046a6b646 Merge pull request #361 from grogy/php7-readme
remove PHP 7.0 from allow failures in tests + improve readme
2015-10-31 20:08:48 +02:00
f1fefc257c improve readme 2015-10-31 16:13:42 +02:00
6f23ec8203 improve readme 2015-10-31 15:52:07 +02:00
23d4544986 improve readme 2015-10-31 15:51:14 +02:00
5a4ff5d189 improve readme 2015-10-31 15:49:56 +02:00
50ac4a06e8 remove PHP 7.0 from allow failures in tests + improve readme 2015-10-31 01:55:49 +01:00
003af26499 improve readme 2015-10-31 00:45:21 +02:00
aa63058a88 improve readme 2015-10-31 00:38:24 +02:00
15d56cdd27 improve readme 2015-10-31 00:37:38 +02:00
93d77b0b47 improve readme 2015-10-31 00:35:04 +02:00
c8072a1987 add link to caret.io in readme 2015-10-31 00:33:11 +02:00
b008290917 Merge pull request #359 from tbreuss/master
Add Herbie CMS to "Who uses it?"
2015-10-25 16:42:45 +02:00
9928c933d8 Add Herbie CMS to "Who uses it?" 2015-10-25 13:33:13 +01:00
ddb3bd2107 Merge pull request #357 from donatj/master
Parsedown library shouldn't be executable
2015-10-21 01:43:13 +03:00
e603c2378d Parsedown library shouldn't be executable 2015-10-20 15:16:36 -05:00
3ebbd730b5 1.6.0 2015-10-04 19:44:32 +03:00
1f02626ed6 add link to caret.io 2015-10-01 18:47:31 +03:00
fa005fdb95 Merge pull request #336 from hkdobrev/late-static-binding
Use late static binding for Parsedown::instance()
2015-08-13 15:16:23 +03:00
5f40cab3e7 Use late static binding for Parsedown::instance()
Fixes erusev/parsedown-extra#67.

This introduces PHP 5.3+ late static binding to the Singleton pattern in Parsedown.
It will return an instance of Parsedown which inherits the class which
called the `instance()` method rather than always returning instance of just `Parsedown`.

Tests are testing this feature with a test class which inherits from Parsedown.
Notice that calling `instance()` with the default arguments after an instance of
`Parsedown` was already created, it will return it even though it is from just
an instance of `Parsedown`. So this is fixing the problem just partially.
2015-08-13 13:29:33 +03:00
0e89e3714b 1.5.4 2015-08-03 12:24:05 +03:00
6b24125f06 clean up 2015-07-31 17:01:14 +03:00
a589bcac79 resolve #342 2015-07-31 01:33:21 +03:00
a9dfc97ddc opening code fence doesn't need 2 regex groups 2015-07-16 16:57:13 +03:00
28774a4359 improve readme 2015-07-15 11:59:59 +03:00
b8b5711ee5 improve readme 2015-07-15 11:59:40 +03:00
9579e5f5e5 improve readme 2015-07-15 11:57:45 +03:00
7f7f6418a3 improve readme 2015-07-15 09:21:38 +03:00
ee81967749 improve readme 2015-07-03 17:11:23 +03:00
96e0810188 improve readme 2015-07-03 17:11:08 +03:00
99bd1bd678 improve readme 2015-07-03 17:06:52 +03:00
e7a6a06166 improve readme 2015-07-03 16:46:25 +03:00
eca5bb8262 improve readme 2015-07-03 16:45:22 +03:00
1312908056 improve readme 2015-07-03 16:42:47 +03:00
76b7d7babd improve readme 2015-07-03 16:39:51 +03:00
ba802c1c8d replace the term "incomplete" 2015-07-02 01:01:14 +03:00
438874e9a8 improve line 2015-06-25 01:05:05 +03:00
8e26f45dee improve readme 2015-06-15 17:38:15 +03:00
e2bb3eaaf8 clean up 2015-06-15 12:28:35 +03:00
0de61e7b3a improve readme 2015-06-15 12:09:57 +03:00
5b72dceb26 improve readme 2015-06-15 00:10:09 +03:00
95699c9ba6 improve readme 2015-06-14 22:55:21 +03:00
790066e9a7 improve readme 2015-05-26 13:37:45 +03:00
b9e5228e92 version 1.5.3 2015-04-29 23:22:24 +03:00
31c8856f53 Merge pull request #312 from garoevans/patch-1
Fix check against $matches
2015-04-24 08:24:06 +03:00
d5823ad622 Merge pull request #295 from hkdobrev/nightly
Test on nightly releases of PHP (7.0) and HHVM
2015-04-24 01:44:07 +03:00
6736ba9a04 Fix check against $matches
Fixes inline reference links with int 0 as reference

The link [link][0] where [0] is set at the bottom of the md file current breaks and it's truthy value is false.
2015-04-20 16:24:11 +01:00
468d1e3da8 improve readme 2015-03-18 20:44:57 +02:00
7aa1d97bba improve readme 2015-03-18 20:43:30 +02:00
f768f9c63f improve readme 2015-03-18 20:42:44 +02:00
aa83968534 improve readme 2015-03-18 20:41:23 +02:00
85eadccc05 Merge pull request #297 from hkdobrev/patch-1
Remove "tested in PHP 5.2"
2015-03-18 20:39:40 +02:00
c94fa12d67 Remove "tested on PHP 5.2"
It is no longer tested on PHP 5.2 in Travis CI.

See https://github.com/erusev/parsedown/pull/286
2015-03-18 17:15:00 +02:00
11e02d45fa improve readme 2015-03-18 16:20:28 +02:00
ecd53f9add improve readme 2015-03-18 16:16:51 +02:00
844b2f49ea Merge pull request #249 from HelpingHand1/patch-1
Updated readme to display travis-ci.org build test
2015-03-18 16:08:13 +02:00
b2ad712644 Test on nightly releases of PHP (7.0) and HHVM
See https://github.com/erusev/parsedown/issues/294

See 12bf1c6d05 (commitcomment-10090016)

Nightly releases are allowed to fail since they are not stable and
could fail in unpredictable ways.

New versions of PHP could be added as they are released.
I think they should not be in the allowed to fail part of the matrix then.
2015-03-09 10:14:57 +02:00
65116c3cb0 Merge pull request #286 from henriquemoody/travis
Remove PHP version 5.2 from Travis builds
2015-02-09 01:04:08 +02:00
147003107a Remove PHP version 5.2 from Travis builds 2015-02-08 21:00:50 -02:00
618b26056c Merge pull request #284 from jstanden/master
Fixes #283
2015-02-04 00:39:05 +02:00
b828fe7c8d Fixes #283 2015-02-03 13:58:47 -08:00
6c9df528aa Merge pull request #280 from kelunik/master
Fixes PHP 7 compatibility
2015-02-02 02:33:38 +02:00
cb8cc57742 Fixes #279 2015-02-02 00:58:24 +01:00
4b7d7cdef2 updated readme.md per comment by cebe 2014-12-13 13:01:40 -05:00
97e667ab30 Updated readme to display travis-ci.org build test 2014-12-10 18:13:48 -05:00
24 changed files with 370 additions and 89 deletions

5
.gitattributes vendored Normal file
View File

@ -0,0 +1,5 @@
# Ignore all tests for archive
/test export-ignore
/.gitattributes export-ignore
/.travis.yml export-ignore
/phpunit.xml.dist export-ignore

View File

@ -1,10 +1,27 @@
language: php
php:
- 5.6
- 5.5
- 5.4
- 5.3
- 5.2
- hhvm
dist: trusty
sudo: false
matrix:
include:
- php: 5.3
dist: precise
- php: 5.4
- php: 5.5
- php: 5.6
- php: 7.0
- php: 7.1
- php: nightly
- php: hhvm
- php: hhvm-nightly
fast_finish: true
allow_failures:
- php: nightly
- php: hhvm-nightly
before_script:
- composer install --prefer-dist --no-interaction --no-progress
script:
- vendor/bin/phpunit

View File

@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2013 Emanuil Rusev, erusev.com
Copyright (c) 2013-2018 Emanuil Rusev, erusev.com
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
@ -17,4 +17,4 @@ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

233
Parsedown.php Executable file → Normal file
View File

@ -17,7 +17,7 @@ class Parsedown
{
# ~
const version = '1.5.1';
const version = '1.6.0';
# ~
@ -75,6 +75,32 @@ class Parsedown
protected $urlsLinked = true;
function setSafeMode($safeMode)
{
$this->safeMode = (bool) $safeMode;
return $this;
}
protected $safeMode;
protected $safeLinksWhitelist = array(
'http://',
'https://',
'ftp://',
'ftps://',
'mailto:',
'data:image/png;base64,',
'data:image/gif;base64,',
'data:image/jpeg;base64,',
'irc:',
'ircs:',
'git:',
'ssh:',
'news:',
'steam:',
);
#
# Lines
#
@ -107,12 +133,6 @@ class Parsedown
# ~
protected $DefinitionTypes = array(
'[' => array('Reference'),
);
# ~
protected $unmarkedBlockTypes = array(
'Code',
);
@ -121,7 +141,7 @@ class Parsedown
# Blocks
#
private function lines(array $lines)
protected function lines(array $lines)
{
$CurrentBlock = null;
@ -169,7 +189,7 @@ class Parsedown
# ~
if (isset($CurrentBlock['incomplete']))
if (isset($CurrentBlock['continuable']))
{
$Block = $this->{'block'.$CurrentBlock['type'].'Continue'}($Line, $CurrentBlock);
@ -181,12 +201,10 @@ class Parsedown
}
else
{
if (method_exists($this, 'block'.$CurrentBlock['type'].'Complete'))
if ($this->isBlockCompletable($CurrentBlock['type']))
{
$CurrentBlock = $this->{'block'.$CurrentBlock['type'].'Complete'}($CurrentBlock);
}
unset($CurrentBlock['incomplete']);
}
}
@ -224,9 +242,9 @@ class Parsedown
$Block['identified'] = true;
}
if (method_exists($this, 'block'.$blockType.'Continue'))
if ($this->isBlockContinuable($blockType))
{
$Block['incomplete'] = true;
$Block['continuable'] = true;
}
$CurrentBlock = $Block;
@ -253,7 +271,7 @@ class Parsedown
# ~
if (isset($CurrentBlock['incomplete']) and method_exists($this, 'block'.$CurrentBlock['type'].'Complete'))
if (isset($CurrentBlock['continuable']) and $this->isBlockCompletable($CurrentBlock['type']))
{
$CurrentBlock = $this->{'block'.$CurrentBlock['type'].'Complete'}($CurrentBlock);
}
@ -286,6 +304,16 @@ class Parsedown
return $markup;
}
protected function isBlockContinuable($Type)
{
return method_exists($this, 'block'.$Type.'Continue');
}
protected function isBlockCompletable($Type)
{
return method_exists($this, 'block'.$Type.'Complete');
}
#
# Code
@ -340,8 +368,6 @@ class Parsedown
{
$text = $Block['element']['text']['text'];
$text = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8');
$Block['element']['text']['text'] = $text;
return $Block;
@ -352,7 +378,7 @@ class Parsedown
protected function blockComment($Line)
{
if ($this->markupEscaped)
if ($this->markupEscaped or $this->safeMode)
{
return;
}
@ -394,16 +420,16 @@ class Parsedown
protected function blockFencedCode($Line)
{
if (preg_match('/^(['.$Line['text'][0].']{3,})[ ]*([\w-]+)?[ ]*$/', $Line['text'], $matches))
if (preg_match('/^['.$Line['text'][0].']{3,}[ ]*([\w-]+)?[ ]*$/', $Line['text'], $matches))
{
$Element = array(
'name' => 'code',
'text' => '',
);
if (isset($matches[2]))
if (isset($matches[1]))
{
$class = 'language-'.$matches[2];
$class = 'language-'.$matches[1];
$Element['attributes'] = array(
'class' => $class,
@ -446,7 +472,7 @@ class Parsedown
return $Block;
}
$Block['element']['text']['text'] .= "\n".$Line['body'];;
$Block['element']['text']['text'] .= "\n".$Line['body'];
return $Block;
}
@ -455,8 +481,6 @@ class Parsedown
{
$text = $Block['element']['text']['text'];
$text = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8');
$Block['element']['text']['text'] = $text;
return $Block;
@ -513,6 +537,16 @@ class Parsedown
),
);
if($name === 'ol')
{
$listStart = stristr($matches[0], '.', true);
if($listStart !== '1')
{
$Block['element']['attributes'] = array('start' => $listStart);
}
}
$Block['li'] = array(
'name' => 'li',
'handler' => 'li',
@ -666,14 +700,16 @@ class Parsedown
protected function blockMarkup($Line)
{
if ($this->markupEscaped)
if ($this->markupEscaped or $this->safeMode)
{
return;
}
if (preg_match('/^<(\w*)(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*(\/)?>/', $Line['text'], $matches))
if (preg_match('/^<(\w[\w-]*)(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*(\/)?>/', $Line['text'], $matches))
{
if (in_array($matches[1], $this->textLevelElements))
$element = strtolower($matches[1]);
if (in_array($element, $this->textLevelElements))
{
return;
}
@ -987,15 +1023,13 @@ class Parsedown
{
$markup = '';
$unexaminedText = $text;
# $excerpt is based on the first occurrence of a marker
$markerPosition = 0;
while ($excerpt = strpbrk($unexaminedText, $this->inlineMarkerList))
while ($excerpt = strpbrk($text, $this->inlineMarkerList))
{
$marker = $excerpt[0];
$markerPosition += strpos($unexaminedText, $marker);
$markerPosition = strpos($text, $marker);
$Excerpt = array('text' => $excerpt, 'context' => $text);
@ -1008,34 +1042,42 @@ class Parsedown
continue;
}
if (isset($Inline['position']) and $Inline['position'] > $markerPosition) # position is ahead of marker
# makes sure that the inline belongs to "our" marker
if (isset($Inline['position']) and $Inline['position'] > $markerPosition)
{
continue;
}
# sets a default inline position
if ( ! isset($Inline['position']))
{
$Inline['position'] = $markerPosition;
}
# the text that comes before the inline
$unmarkedText = substr($text, 0, $Inline['position']);
# compile the unmarked text
$markup .= $this->unmarkedText($unmarkedText);
# compile the inline
$markup .= isset($Inline['markup']) ? $Inline['markup'] : $this->element($Inline['element']);
# remove the examined text
$text = substr($text, $Inline['position'] + $Inline['extent']);
$unexaminedText = $text;
$markerPosition = 0;
continue 2;
}
$unexaminedText = substr($excerpt, 1);
# the marker does not belong to an inline
$markerPosition ++;
$unmarkedText = substr($text, 0, $markerPosition + 1);
$markup .= $this->unmarkedText($unmarkedText);
$text = substr($text, $markerPosition + 1);
}
$markup .= $this->unmarkedText($text);
@ -1054,7 +1096,6 @@ class Parsedown
if (preg_match('/^('.$marker.'+)[ ]*(.+?)[ ]*(?<!'.$marker.')\1(?!'.$marker.')/s', $Excerpt['text'], $matches))
{
$text = $matches[2];
$text = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8');
$text = preg_replace("/[ ]*\n/", ' ', $text);
return array(
@ -1184,7 +1225,7 @@ class Parsedown
$remainder = $Excerpt['text'];
if (preg_match('/\[((?:[^][]|(?R))*)\]/', $remainder, $matches))
if (preg_match('/\[((?:[^][]++|(?R))*+)\]/', $remainder, $matches))
{
$Element['text'] = $matches[1];
@ -1197,7 +1238,7 @@ class Parsedown
return;
}
if (preg_match('/^[(]((?:[^ ()]|[(][^ )]+[)])+)(?:[ ]+("[^"]+"|\'[^\']+\'))?[)]/', $remainder, $matches))
if (preg_match('/^[(]\s*+((?:[^ ()]++|[(][^ )]+[)])++)(?:[ ]+("[^"]*"|\'[^\']*\'))?\s*[)]/', $remainder, $matches))
{
$Element['attributes']['href'] = $matches[1];
@ -1212,7 +1253,7 @@ class Parsedown
{
if (preg_match('/^\s*\[(.*?)\]/', $remainder, $matches))
{
$definition = $matches[1] ? $matches[1] : $Element['text'];
$definition = strlen($matches[1]) ? $matches[1] : $Element['text'];
$definition = strtolower($definition);
$extent += strlen($matches[0]);
@ -1233,8 +1274,6 @@ class Parsedown
$Element['attributes']['title'] = $Definition['title'];
}
$Element['attributes']['href'] = str_replace(array('&', '<'), array('&amp;', '&lt;'), $Element['attributes']['href']);
return array(
'extent' => $extent,
'element' => $Element,
@ -1243,12 +1282,12 @@ class Parsedown
protected function inlineMarkup($Excerpt)
{
if ($this->markupEscaped or strpos($Excerpt['text'], '>') === false)
if ($this->markupEscaped or $this->safeMode or strpos($Excerpt['text'], '>') === false)
{
return;
}
if ($Excerpt['text'][1] === '/' and preg_match('/^<\/\w*[ ]*>/s', $Excerpt['text'], $matches))
if ($Excerpt['text'][1] === '/' and preg_match('/^<\/\w[\w-]*[ ]*>/s', $Excerpt['text'], $matches))
{
return array(
'markup' => $matches[0],
@ -1264,7 +1303,7 @@ class Parsedown
);
}
if ($Excerpt['text'][1] !== ' ' and preg_match('/^<\w*(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*\/?>/s', $Excerpt['text'], $matches))
if ($Excerpt['text'][1] !== ' ' and preg_match('/^<\w[\w-]*(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*\/?>/s', $Excerpt['text'], $matches))
{
return array(
'markup' => $matches[0],
@ -1323,14 +1362,16 @@ class Parsedown
if (preg_match('/\bhttps?:[\/]{2}[^\s<]+\b\/*/ui', $Excerpt['context'], $matches, PREG_OFFSET_CAPTURE))
{
$url = $matches[0][0];
$Inline = array(
'extent' => strlen($matches[0][0]),
'position' => $matches[0][1],
'element' => array(
'name' => 'a',
'text' => $matches[0][0],
'text' => $url,
'attributes' => array(
'href' => $matches[0][0],
'href' => $url,
),
),
);
@ -1343,7 +1384,7 @@ class Parsedown
{
if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<(\w+:\/{2}[^ >]+)>/i', $Excerpt['text'], $matches))
{
$url = str_replace(array('&', '<'), array('&amp;', '&lt;'), $matches[1]);
$url = $matches[1];
return array(
'extent' => strlen($matches[0]),
@ -1381,6 +1422,11 @@ class Parsedown
protected function element(array $Element)
{
if ($this->safeMode)
{
$Element = $this->sanitiseElement($Element);
}
$markup = '<'.$Element['name'];
if (isset($Element['attributes']))
@ -1392,7 +1438,7 @@ class Parsedown
continue;
}
$markup .= ' '.$name.'="'.$value.'"';
$markup .= ' '.$name.'="'.self::escape($value).'"';
}
}
@ -1402,11 +1448,11 @@ class Parsedown
if (isset($Element['handler']))
{
$markup .= $this->$Element['handler']($Element['text']);
$markup .= $this->{$Element['handler']}($Element['text']);
}
else
{
$markup .= $Element['text'];
$markup .= self::escape($Element['text'], true);
}
$markup .= '</'.$Element['name'].'>';
@ -1465,10 +1511,77 @@ class Parsedown
return $markup;
}
protected function sanitiseElement(array $Element)
{
static $goodAttribute = '/^[a-zA-Z0-9][a-zA-Z0-9-_]*+$/';
static $safeUrlNameToAtt = array(
'a' => 'href',
'img' => 'src',
);
if (isset($safeUrlNameToAtt[$Element['name']]))
{
$Element = $this->filterUnsafeUrlInAttribute($Element, $safeUrlNameToAtt[$Element['name']]);
}
if ( ! empty($Element['attributes']))
{
foreach ($Element['attributes'] as $att => $val)
{
# filter out badly parsed attribute
if ( ! preg_match($goodAttribute, $att))
{
unset($Element['attributes'][$att]);
}
# dump onevent attribute
elseif (self::striAtStart($att, 'on'))
{
unset($Element['attributes'][$att]);
}
}
}
return $Element;
}
protected function filterUnsafeUrlInAttribute(array $Element, $attribute)
{
foreach ($this->safeLinksWhitelist as $scheme)
{
if (self::striAtStart($Element['attributes'][$attribute], $scheme))
{
return $Element;
}
}
$Element['attributes'][$attribute] = str_replace(':', '%3A', $Element['attributes'][$attribute]);
return $Element;
}
#
# Static Methods
#
protected static function escape($text, $allowQuotes = false)
{
return htmlspecialchars($text, $allowQuotes ? ENT_NOQUOTES : ENT_QUOTES, 'UTF-8');
}
protected static function striAtStart($string, $needle)
{
$len = strlen($needle);
if ($len > strlen($string))
{
return false;
}
else
{
return strtolower(substr($string, 0, $len)) === strtolower($needle);
}
}
static function instance($name = 'default')
{
if (isset(self::$instances[$name]))
@ -1476,7 +1589,7 @@ class Parsedown
return self::$instances[$name];
}
$instance = new self();
$instance = new static();
self::$instances[$name] = $instance;
@ -1519,10 +1632,10 @@ class Parsedown
'b', 'em', 'big', 'cite', 'small', 'spacer', 'listing',
'i', 'rp', 'del', 'code', 'strike', 'marquee',
'q', 'rt', 'ins', 'font', 'strong',
's', 'tt', 'sub', 'mark',
'u', 'xm', 'sup', 'nobr',
'var', 'ruby',
'wbr', 'span',
'time',
's', 'tt', 'kbd', 'mark',
'u', 'xm', 'sub', 'nobr',
'sup', 'ruby',
'var', 'span',
'wbr', 'time',
);
}

View File

@ -1,16 +1,25 @@
> I also make [Caret](https://caret.io?ref=parsedown) - a Markdown editor for Mac and PC.
## Parsedown
[![Build Status](https://img.shields.io/travis/erusev/parsedown/master.svg?style=flat-square)](https://travis-ci.org/erusev/parsedown)
<!--[![Total Downloads](http://img.shields.io/packagist/dt/erusev/parsedown.svg?style=flat-square)](https://packagist.org/packages/erusev/parsedown)-->
Better Markdown Parser in PHP
[[ demo ]](http://parsedown.org/demo)
[Demo](http://parsedown.org/demo) |
[Benchmarks](http://parsedown.org/speed) |
[Tests](http://parsedown.org/tests/) |
[Documentation](https://github.com/erusev/parsedown/wiki/)
### Features
* [Fast](http://parsedown.org/speed)
* [Consistent](http://parsedown.org/consistency)
* One File
* No Dependencies
* Super Fast
* Extensible
* [GitHub flavored](https://help.github.com/articles/github-flavored-markdown)
* [Tested](http://parsedown.org/tests/) in PHP 5.2, 5.3, 5.4, 5.5, 5.6 and [hhvm](http://www.hhvm.com/)
* [Extensible](https://github.com/erusev/parsedown/wiki/Writing-Extensions)
* Tested in 5.3 to 7.1 and in HHVM
* [Markdown Extra extension](https://github.com/erusev/parsedown-extra)
### Installation
@ -25,13 +34,17 @@ $Parsedown = new Parsedown();
echo $Parsedown->text('Hello _Parsedown_!'); # prints: <p>Hello <em>Parsedown</em>!</p>
```
More examples in [the wiki](https://github.com/erusev/parsedown/wiki/Usage) and in [this video tutorial](http://youtu.be/wYZBY8DEikI).
More examples in [the wiki](https://github.com/erusev/parsedown/wiki/) and in [this video tutorial](http://youtu.be/wYZBY8DEikI).
### Security
Parsedown does not sanitize the HTML that it generates. When you deal with untrusted content (ex: user comments) you should also use a HTML sanitizer like [HTML Purifier](http://htmlpurifier.org/).
### Questions
**How does Parsedown work?**
It tries to read Markdown like a human. First, it looks at the lines. Its interested in how the lines start. This helps it recognise blocks. It knows, for example, that if a line start with a `-` then it perhaps belong to a list. Once it recognises the blocks, it continues to the content. As it reads, it watches out for special characters. This helps it recognise inline elements (or inlines).
It tries to read Markdown like a human. First, it looks at the lines. Its interested in how the lines start. This helps it recognise blocks. It knows, for example, that if a line starts with a `-` then perhaps it belongs to a list. Once it recognises the blocks, it continues to the content. As it reads, it watches out for special characters. This helps it recognise inline elements (or inlines).
We call this approach "line based". We believe that Parsedown is the first Markdown parser to use it. Since the release of Parsedown, other developers have used the same approach to develop other Markdown parsers in PHP and in other languages.
@ -41,8 +54,8 @@ It passes most of the CommonMark tests. Most of the tests that don't pass deal w
**Who uses it?**
[phpDocumentor](http://www.phpdoc.org/), [October CMS](http://octobercms.com/), [Bolt CMS](http://bolt.cm/), [Kirby CMS](http://getkirby.com/), [Grav CMS](http://getgrav.org/), [Statamic CMS](http://www.statamic.com/), [RaspberryPi.org](http://www.raspberrypi.org/) and [more](https://www.versioneye.com/php/erusev:parsedown/references).
[phpDocumentor](http://www.phpdoc.org/), [October CMS](http://octobercms.com/), [Bolt CMS](http://bolt.cm/), [Kirby CMS](http://getkirby.com/), [Grav CMS](http://getgrav.org/), [Statamic CMS](http://www.statamic.com/), [Herbie CMS](http://www.getherbie.org/), [RaspberryPi.org](http://www.raspberrypi.org/), [Symfony demo](https://github.com/symfony/symfony-demo) and [more](https://packagist.org/packages/erusev/parsedown/dependents).
**How can I help?**
Use it, star it, share it and if you feel generous, [donate some money](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=528P3NZQMP8N2).
Use it, star it, share it and if you feel generous, [donate](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=528P3NZQMP8N2).

View File

@ -12,7 +12,13 @@
"homepage": "http://erusev.com"
}
],
"require": {
"php": ">=5.3.0"
},
"require-dev": {
"phpunit/phpunit": "^4.8.35"
},
"autoload": {
"psr-0": {"Parsedown": ""}
}
}
}

View File

@ -5,4 +5,4 @@
<file>test/ParsedownTest.php</file>
</testsuite>
</testsuites>
</phpunit>
</phpunit>

View File

@ -8,7 +8,10 @@
* @link http://commonmark.org/ CommonMark
* @link http://git.io/8WtRvQ JavaScript test runner
*/
class CommonMarkTest extends PHPUnit_Framework_TestCase
use PHPUnit\Framework\TestCase;
class CommonMarkTest extends TestCase
{
const SPEC_URL = 'https://raw.githubusercontent.com/jgm/stmd/master/spec.txt';

View File

@ -1,6 +1,8 @@
<?php
class ParsedownTest extends PHPUnit_Framework_TestCase
use PHPUnit\Framework\TestCase;
class ParsedownTest extends TestCase
{
final function __construct($name = null, array $data = array(), $dataName = '')
{
@ -46,6 +48,8 @@ class ParsedownTest extends PHPUnit_Framework_TestCase
$expectedMarkup = str_replace("\r\n", "\n", $expectedMarkup);
$expectedMarkup = str_replace("\r", "\n", $expectedMarkup);
$this->Parsedown->setSafeMode(substr($test, 0, 3) === 'xss');
$actualMarkup = $this->Parsedown->text($markdown);
$this->assertEquals($expectedMarkup, $actualMarkup);
@ -136,4 +140,24 @@ EXPECTED_HTML;
$parsedownWithNoMarkup->setMarkupEscaped(true);
$this->assertEquals($expectedHtml, $parsedownWithNoMarkup->text($markdownWithHtml));
}
public function testLateStaticBinding()
{
include __DIR__ . '/TestParsedown.php';
$parsedown = Parsedown::instance();
$this->assertInstanceOf('Parsedown', $parsedown);
// After instance is already called on Parsedown
// subsequent calls with the same arguments return the same instance
$sameParsedown = TestParsedown::instance();
$this->assertInstanceOf('Parsedown', $sameParsedown);
$this->assertSame($parsedown, $sameParsedown);
$testParsedown = TestParsedown::instance('test late static binding');
$this->assertInstanceOf('TestParsedown', $testParsedown);
$sameInstanceAgain = TestParsedown::instance('test late static binding');
$this->assertSame($testParsedown, $sameInstanceAgain);
}
}

5
test/TestParsedown.php Normal file
View File

@ -0,0 +1,5 @@
<?php
class TestParsedown extends Parsedown
{
}

View File

@ -1,3 +1,3 @@
<?php
include 'Parsedown.php';
include 'Parsedown.php';

View File

@ -1 +1,2 @@
<p><img src="/md.png" alt="alt" title="title" /></p>
<p><img src="/md.png" alt="alt" title="title" /></p>
<p><img src="/md.png" alt="blank title" title="" /></p>

View File

@ -1 +1,3 @@
![alt](/md.png "title")
![alt](/md.png "title")
![blank title](/md.png "")

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,4 +1,6 @@
<p><a href="http://example.com" title="Title">single quotes</a></p>
<p><a href="http://example.com" title="Title">double quotes</a></p>
<p><a href="http://example.com" title="">single quotes blank</a></p>
<p><a href="http://example.com" title="">double quotes blank</a></p>
<p><a href="http://example.com" title="2 Words">space</a></p>
<p><a href="http://example.com/url-(parentheses)" title="Title">parentheses</a></p>

View File

@ -2,6 +2,10 @@
[double quotes](http://example.com "Title")
[single quotes blank](http://example.com '')
[double quotes blank](http://example.com "")
[space](http://example.com "2 Words")
[parentheses](http://example.com/url-(parentheses) "Title")

View File

@ -8,6 +8,6 @@
<li>two</li>
</ol>
<p>large numbers:</p>
<ol>
<ol start="123">
<li>one</li>
</ol>

View File

@ -0,0 +1,6 @@
<p><a href="https://www.example.com&quot;">xss</a></p>
<p><img src="https://www.example.com&quot;" alt="xss" /></p>
<p><a href="https://www.example.com&#039;">xss</a></p>
<p><img src="https://www.example.com&#039;" alt="xss" /></p>
<p><img src="https://www.example.com" alt="xss&quot;" /></p>
<p><img src="https://www.example.com" alt="xss&#039;" /></p>

View File

@ -0,0 +1,11 @@
[xss](https://www.example.com")
![xss](https://www.example.com")
[xss](https://www.example.com')
![xss](https://www.example.com')
![xss"](https://www.example.com)
![xss'](https://www.example.com)

View File

@ -0,0 +1,16 @@
<p><a href="javascript%3Aalert(1)">xss</a></p>
<p><a href="javascript%3Aalert(1)">xss</a></p>
<p><a href="javascript%3A//alert(1)">xss</a></p>
<p><a href="javascript&amp;colon;alert(1)">xss</a></p>
<p><img src="javascript%3Aalert(1)" alt="xss" /></p>
<p><img src="javascript%3Aalert(1)" alt="xss" /></p>
<p><img src="javascript%3A//alert(1)" alt="xss" /></p>
<p><img src="javascript&amp;colon;alert(1)" alt="xss" /></p>
<p><a href="data%3Atext/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg==">xss</a></p>
<p><a href="data%3Atext/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg==">xss</a></p>
<p><a href="data%3A//text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg==">xss</a></p>
<p><a href="data&amp;colon;text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg==">xss</a></p>
<p><img src="data%3Atext/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg==" alt="xss" /></p>
<p><img src="data%3Atext/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg==" alt="xss" /></p>
<p><img src="data%3A//text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg==" alt="xss" /></p>
<p><img src="data&amp;colon;text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg==" alt="xss" /></p>

31
test/data/xss_bad_url.md Normal file
View File

@ -0,0 +1,31 @@
[xss](javascript:alert(1))
[xss]( javascript:alert(1))
[xss](javascript://alert(1))
[xss](javascript&colon;alert(1))
![xss](javascript:alert(1))
![xss]( javascript:alert(1))
![xss](javascript://alert(1))
![xss](javascript&colon;alert(1))
[xss](data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg==)
[xss]( data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg==)
[xss](data://text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg==)
[xss](data&colon;text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg==)
![xss](data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg==)
![xss]( data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg==)
![xss](data://text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg==)
![xss](data&colon;text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg==)

View File

@ -0,0 +1,7 @@
<p>&lt;script&gt;alert(1)&lt;/script&gt;</p>
<p>&lt;script&gt;</p>
<p>alert(1)</p>
<p>&lt;/script&gt;</p>
<p>&lt;script&gt;
alert(1)
&lt;/script&gt;</p>

View File

@ -0,0 +1,12 @@
<script>alert(1)</script>
<script>
alert(1)
</script>
<script>
alert(1)
</script>