코드 블럭의 내용을 htmlpurifier에서 우회시키고 싶습니다.
본문
1. 환경 : 우분투 20.04, PHP7.4.3, 그누보드 5.4.2.7
2. 테마 : 그누보드 순정에 Vditor 마크다운 에디터 적용 (https://sir.kr/g5_plugin/7337)
마크다운 에디터 적용했지만 결론적으론 순정에 가깝습니다.
3. 현상 :
링크 : https://gnu.hubs.tk/bbs/board.php?bo_table=free&wr_id=2
링크에 있듯이
```
<link rel="stylesheet" href="<?php echo G5_EDITOR_URL; ?>/vditor3/dist/index.css">
<script src="<?php echo G5_EDITOR_URL; ?>/vditor3/dist/index.min.js"></script>
<script src="<?php echo G5_EDITOR_URL; ?>/vditor3/editorOptions.js"></script>
```
위 내용을 넣으면 내용이 사라집니다.
마크다운 에디터 특성상 ```로 감싸면
<pre><div class="vditor-copy"><textarea></textarea><span aria-label="복사" onmouseover="this.setAttribute('aria-label', '복사')" class="vditor-tooltipped vditor-tooltipped__w" onclick="this.previousElementSibling.select();document.execCommand('copy');this.setAttribute('aria-label', '복사완료')"><svg><use xlink:href="#vditor-icon-copy"></use></svg></span></div><code class="language-html hljs xml" style="max-height: 1407px;"><span class="php"><span class="hljs-meta"><?php</span> <span class="hljs-keyword">echo</span> get_view_thumbnail($view[<span class="hljs-string">'content'</span>]);<span class="hljs-meta">?></span></span>
</code></pre>
위와 같이 pre와 code로 감싸게 됩니다.
그래서 htmlpurifier와 관계없는 안전한 구문은
<?php echo get_view_thumbnail(na_view($view)); // 글내용 출력 ?>
위와 같은 내용은 htmlpurifier로 걸러지지 않고 그대로 출력됩니다.
하지만 위에 있는 script 관련 부분은 전부 걸러져서 없어집니다.
4. 원하는 부분
그누보드 코어의 htmlpurifier를 수정해서라도 현상을 정상으로 고치고 싶습니다.
즉 script등 위험한 부분 등이 code 안에 있으면 htmlpurifier로 걸러지지 않고 그냥 text로 그대로 표시되게 하는 방법을 알고 싶습니다.
여기에 @BiHon 님께서 적어주셨는데 아직 초보라 어떻게 적용하는지 감이 오질 않습니다. ㅠㅠ
5. 해결되었을 때 기대되는 부분
이 문제가 해결되면 마크다운 에디터 끝판왕으로 그누보드에서 활용성이 매우 높을 것 같습니다.
https://gnu.hubs.tk/bbs/board.php?bo_table=free&wr_id=9
위와 같이 악보, 차트, 마인드맵 등 기존 에디터에서는 상상하기 힘든 내용들도 출력이 가능합니다.
방법을 알려주시면 그누보드에서도 잘 사용할 수 있게 가이드를 작성하겠습니다.
감사합니다.
!-->!-->!-->답변 8
예제 문자열을 포함한 짧은 코드인데… 안타깝네요.
질문을 기준으로 지정한 문자열 필터 제외에 초점을 맞춘 답변으로,
편의상 구분을 했던 부분을 줄여 필요한 코드만 남겨 등록합니다.
// 그누보드 5.4.2.7
function html_purifier($html)
{
$f = file(G5_PLUGIN_PATH.'/htmlpurifier/safeiframe.txt');
$domains = array();
foreach($f as $domain){
// 첫행이 # 이면 주석 처리
if (!preg_match("/^#/", $domain)) {
$domain = trim($domain);
if ($domain)
array_push($domains, $domain);
}
}
// 내 도메인도 추가
array_push($domains, $_SERVER['HTTP_HOST'].'/');
$safeiframe = implode('|', $domains);
include_once(G5_PLUGIN_PATH.'/htmlpurifier/HTMLPurifier.standalone.php');
include_once(G5_PLUGIN_PATH.'/htmlpurifier/extend.video.php');
$config = HTMLPurifier_Config::createDefault();
// data/cache 디렉토리에 CSS, HTML, URI 디렉토리 등을 만든다.
$config->set('Cache.SerializerPath', G5_DATA_PATH.'/cache');
$config->set('HTML.SafeEmbed', false);
$config->set('HTML.SafeObject', false);
$config->set('Output.FlashCompat', false);
$config->set('HTML.SafeIframe', true);
if( (function_exists('check_html_link_nofollow') && check_html_link_nofollow('html_purifier')) ){
$config->set('HTML.Nofollow', true); // rel=nofollow 으로 스팸유입을 줄임
}
$config->set('URI.SafeIframeRegexp','%^(https?:)?//('.$safeiframe.')%');
$config->set('Attr.AllowedFrameTargets', array('_blank'));
//유튜브, 비메오 전체화면 가능하게 하기
$config->set('Filter.Custom', array(new HTMLPurifier_Filter_Iframevideo()));
$purifier = new HTMLPurifier($config);
return $purifier->purify($html); // 여기에서 걸러집니다. 그럼 이 부분에 넣어야겠죠?
}
// ↓
function html_purifier($html)
{
$f = file(G5_PLUGIN_PATH.'/htmlpurifier/safeiframe.txt');
$domains = array();
foreach($f as $domain){
// 첫행이 # 이면 주석 처리
if (!preg_match("/^#/", $domain)) {
$domain = trim($domain);
if ($domain)
array_push($domains, $domain);
}
}
// 내 도메인도 추가
array_push($domains, $_SERVER['HTTP_HOST'].'/');
$safeiframe = implode('|', $domains);
include_once(G5_PLUGIN_PATH.'/htmlpurifier/HTMLPurifier.standalone.php');
include_once(G5_PLUGIN_PATH.'/htmlpurifier/extend.video.php');
$config = HTMLPurifier_Config::createDefault();
// data/cache 디렉토리에 CSS, HTML, URI 디렉토리 등을 만든다.
$config->set('Cache.SerializerPath', G5_DATA_PATH.'/cache');
$config->set('HTML.SafeEmbed', false);
$config->set('HTML.SafeObject', false);
$config->set('Output.FlashCompat', false);
$config->set('HTML.SafeIframe', true);
if( (function_exists('check_html_link_nofollow') && check_html_link_nofollow('html_purifier')) ){
$config->set('HTML.Nofollow', true); // rel=nofollow 으로 스팸유입을 줄임
}
$config->set('URI.SafeIframeRegexp','%^(https?:)?//('.$safeiframe.')%');
$config->set('Attr.AllowedFrameTargets', array('_blank'));
//유튜브, 비메오 전체화면 가능하게 하기
$config->set('Filter.Custom', array(new HTMLPurifier_Filter_Iframevideo()));
$purifier = new HTMLPurifier($config);
preg_match_all('#```.+?```#s', $html, $matches); // Markdown ```~``` 구간 구하기
$change = []; // 백업↔복구용
foreach ( $matches[0] as $k=>$v ) $change['^[CODE'.($k+1).']^'] = $v; // 내용에 사용되지 않을만한 문자열로 지정
$html = strtr($html, array_flip($change)); // ```코드``` → ^[CODE0]^
$html = $purifier->purify($html); // HTML Purifier 적용
$html = strtr($html, $change); // ^[CODE0]^ → ```코드```
return $html;
}
// 이렇게 하면, ```로 감싸인 문자열 부분은 HTML Purifier를 비껴갈 수 있습니다.
Vditor가 최신 버전인가요? 지금 보니 조금 이상하네요.
아래에 textarea가 들어가면 안되는데..
<pre><div class="vditor-copy"><textarea></textarea>
즉 HTML이기 때문에 아래와 같이 HTML입장으로 보면 textarea가 미리 끝나게 됩니다.
<textarea>
...
<pre><div class="vditor-copy"><textarea></textarea> ...
...
</textarea>
버전업 되면서 저 문장이 들어가는지 이전부터 있었는지 확인해봐야겠습니다.
!-->!-->
제가 이것저것 다 해 봤는데, 그누보드에서는 두가지가 최선이라는 결론 때문에.
그누보드에 요청했던 것입니다.
1. https://github.com/gnuboard/gnuboard5/issues/55
2. https://github.com/gnuboard/gnuboard5/issues/56
두가지가 그리 어렵지 않은 것 같아서, PHP보면서 고쳐서 사용해도 되겠지만, 나중에 업데이트에 문제가 되고.
답변에서 안된다고 하면, Hook이라도 넣어달라고 할려고 기다리고 있는 중입니다.
그래서 그누보드 연동된 버전이 3.0.12로 남아 있는 이유입니다.
그누보드에서 어느정도 응답이 없고, Vditor도 새로운 SV모드를 만들면서 거의 한달이상이 지나간 것입니다.
그래서 에디터 본연의 기능에 더 힘쓰고 있는중입니다.
나머지는 시간이 지나면 다 해결이 됩니다.
그냥 wr_option에 3번에 markdown 또는 text 옵션 넣어서 우회시키세요.
Vditor 최신 버전에는 sanitize: true 옵션이 디폴트여서 에디터 자체에서 xss는 걸러 줍니다.
/bbs/view.php
// $view['content'] = conv_content($view['wr_content'], $html);
$view['content'] = $view['wr_content'];
// 질문 내용 기준으로, 특정 구간 필터 제외 방법의 한 예.
// https://sir.kr/qa/370007 ... 이전 질문에 남긴 댓글의 방법과 다를 바 없습니다.
// 필터 제외할 구간의 문자열을 단순한 문자열로 치환 > 필터링 > 재치환입니다.
preg_match_all('#```.+?```#s', $view, $matches); // ```로 감싸인 문자열 구하기
$change_a = []; // 1) A→B 치환용. 내용1```코드1```내용2```코드2```내용3 → 내용1^[CODE0]^내용2^[CODE1]^내용3
$change_b = []; // 2) B→A 치환용. 내용1^[CODE0]^내용2^[CODE1]^내용3 → 내용1```코드1```내용2```코드2```내용3
foreach ( $matches[0] as $k=>$v ) {
$key = '^[CODE'.$k.']^'; // ^[CODE0]^, ^[CODE1]^, ... 내용에 사용되지 않을만한 구분자 사용
$change_a[$v] = $key;
$change_b[$key] = $v;
}
$view = strtr($view, $change_a); // 1)
$view = get_view_thumbnail(na_view($view)); // HTML Purifier 등등. 어차피 ^[CODE0]^ 문자열이라 그대로.
$view = strtr($view, $change_b); // 2)
echo $view; // 예전 질문에서, code-php를 language-php 처럼 치환해줘야 하면, 출력 전에 치환.
// 어때요? 참 쉽죠?
정말 오랜만에
질문은 이렇게 하는 것이다 하는
질문의 정석과 같은 정성이 느껴지는 글입니다
저는 무지하여 답을 드릴 수 없어
안탑깝습니다 ㅜㅜ