瀏覽代碼

添加iframe支持(conf `enable_iframe`)

gsw945 4 年之前
父節點
當前提交
b624fea255

+ 6 - 0
commands/command.go

@@ -263,6 +263,12 @@ func RegisterFunction() {
 		logs.Error("注册函数 urlfor 出错 ->", err)
 		os.Exit(-1)
 	}
+	//读取配置值(未作任何转换)
+	err = web.AddFuncMap("conf", conf.CONF)
+	if err != nil {
+		logs.Error("注册函数 conf 出错 ->", err)
+		os.Exit(-1)
+	}
 	err = web.AddFuncMap("date_format", func(t time.Time, format string) string {
 		return t.Local().Format(format)
 	})

+ 1 - 0
conf/app.conf.example

@@ -8,6 +8,7 @@ sessionon = true
 sessionname = mindoc_id
 copyrequestbody = true
 enablexsrf = "${MINDOC_ENABLE_XSRF||false}"
+enable_iframe = "${MINDOC_ENABLE_IFRAME||false}"
 
 #系统完整URL(http://doc.iminho.me),如果该项不设置,会从请求头中获取地址。
 baseurl="${MINDOC_BASE_URL}"

+ 14 - 0
conf/enumerate.go

@@ -152,6 +152,11 @@ func GetEnableExport() bool {
 	return web.AppConfig.DefaultBool("enable_export", true)
 }
 
+//是否启用iframe
+func GetEnableIframe() bool {
+	return web.AppConfig.DefaultBool("enable_iframe", false)
+}
+
 //同一项目导出线程的并发数
 func GetExportProcessNum() int {
 	exportProcessNum := web.AppConfig.DefaultInt("export_process_num", 1)
@@ -208,6 +213,15 @@ func IsAllowUploadFileExt(ext string) bool {
 	return false
 }
 
+//读取配置文件值
+func CONF(key string, value ...string) string {
+	defaultValue := ""
+	if len(value) > 0 {
+		defaultValue = value[0]
+	}
+	return web.AppConfig.DefaultString(key, defaultValue)
+}
+
 //重写生成URL的方法,加上完整的域名
 func URLFor(endpoint string, values ...interface{}) string {
 	baseUrl := web.AppConfig.DefaultString("baseurl", "")

+ 71 - 0
routers/router.go

@@ -1,11 +1,82 @@
 package routers
 
 import (
+	"crypto/tls"
+	"log"
+	"net/http"
+	"net/http/httputil"
+	"net/url"
+	"strings"
+
+	"github.com/beego/beego/v2/core/logs"
 	"github.com/beego/beego/v2/server/web"
+	"github.com/beego/beego/v2/server/web/context"
+	"github.com/mindoc-org/mindoc/conf"
 	"github.com/mindoc-org/mindoc/controllers"
 )
 
+func rt(req *http.Request) (*http.Response, error) {
+	log.Printf("request received. url=%s", req.URL)
+	// req.Header.Set("Host", "httpbin.org") // <--- I set it here as well
+	defer log.Printf("request complete. url=%s", req.URL)
+
+	return http.DefaultTransport.RoundTrip(req)
+}
+
+// roundTripper makes func signature a http.RoundTripper
+type roundTripper func(*http.Request) (*http.Response, error)
+
+func (f roundTripper) RoundTrip(req *http.Request) (*http.Response, error) { return f(req) }
+
+type CorsTransport struct {
+	http.RoundTripper
+}
+
+func (t *CorsTransport) RoundTrip(req *http.Request) (resp *http.Response, err error) {
+	// refer:
+	// - https://stackoverflow.com/questions/31535569/golang-how-to-read-response-body-of-reverseproxy/31536962#31536962
+	// - https://gist.github.com/simon-cj/b4da0b2bca793ec3b8a5abe04c8fca41
+	resp, err = t.RoundTripper.RoundTrip(req)
+	logs.Debug(resp)
+	if err != nil {
+		return nil, err
+	}
+	resp.Header.Del("Access-Control-Request-Method")
+	resp.Header.Set("Access-Control-Allow-Origin", "*")
+	return resp, nil
+}
+
 func init() {
+	web.Any("/cors-anywhere", func(ctx *context.Context) {
+		u, _ := url.PathUnescape(ctx.Input.Query("url"))
+		logs.Error("ReverseProxy: ", u)
+		if len(u) > 0 && strings.HasPrefix(u, "http") {
+			if strings.TrimRight(conf.BaseUrl, "/") == ctx.Input.Site() {
+				ctx.Redirect(302, u)
+			} else {
+				target, _ := url.Parse(u)
+				logs.Debug("target: ", target)
+
+				proxy := &httputil.ReverseProxy{
+					Transport: roundTripper(rt),
+					Director: func(req *http.Request) {
+						req.Header = ctx.Request.Header
+						req.URL.Scheme = target.Scheme
+						req.URL.Host = target.Host
+						req.URL.Path = target.Path
+						req.Header.Set("Host", target.Host)
+					},
+				}
+
+				http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
+				proxy.ServeHTTP(ctx.ResponseWriter, ctx.Request)
+			}
+		} else {
+			ctx.ResponseWriter.WriteHeader(http.StatusBadRequest)
+			ctx.Output.Body([]byte("400 Bad Request"))
+		}
+	})
+
 	web.Router("/", &controllers.HomeController{}, "*:Index")
 
 	web.Router("/login", &controllers.AccountController{}, "*:Login")

+ 6 - 2
static/js/blog.js

@@ -3,6 +3,10 @@ $(function () {
         js  : window.katex.js,
         css : window.katex.css
     };
+    var htmlDecodeList = ["style","script","title","onmouseover","onmouseout","style"];
+    if (!window.IS_ENABLE_IFRAME) {
+        htmlDecodeList.unshift("iframe");
+    }
     window.editor = editormd("docEditor", {
         width: "100%",
         height: "100%",
@@ -18,8 +22,8 @@ $(function () {
         taskList: true,
         flowChart: true,
         mermaid: true,
-        htmlDecode: "style,script,iframe,title,onmouseover,onmouseout,style",
-        lineNumbers: false,
+        htmlDecode: htmlDecodeList.join(','),
+        lineNumbers: true,
         sequenceDiagram: true,
         highlightStyle: window.highlightStyle ? window.highlightStyle : "github",
         tocStartLevel: 1,

文件差異過大導致無法顯示
+ 0 - 0
static/js/custom-elements-builtin-0.6.5.min.js


+ 7 - 4
static/js/markdown.js

@@ -54,7 +54,10 @@ $(function () {
             remark: 'Remark',
         }
     };
-
+    var htmlDecodeList = ["style","script","title","onmouseover","onmouseout","style"];
+    if (!window.IS_ENABLE_IFRAME) {
+        htmlDecodeList.unshift("iframe");
+    }
     window.editor = editormd("docEditor", {
         width: "100%",
         height: "100%",
@@ -69,8 +72,8 @@ $(function () {
         fileUploadURL: window.fileUploadURL,
         taskList: true,
         flowChart: true,
-        htmlDecode: "style,script,iframe,title,onmouseover,onmouseout,style",
-        lineNumbers: false,
+        htmlDecode: htmlDecodeList.join(','),
+        lineNumbers: true,
         sequenceDiagram: true,
         tocStartLevel: 1,
         tocm: true,
@@ -193,7 +196,7 @@ $(function () {
        } else {
            var action = window.editor.toolbarHandlers[name];
 
-           if (action !== "undefined") {
+           if (!!action && action !== "undefined") {
                $.proxy(action, window.editor)();
                window.editor.focus();
            }

+ 87 - 0
static/js/x-frame-bypass-1.0.2.js

@@ -0,0 +1,87 @@
+customElements.define('x-frame-bypass', class extends HTMLIFrameElement {
+	static get observedAttributes() {
+		return ['src']
+	}
+	constructor () {
+		super()
+	}
+	attributeChangedCallback () {
+		this.load(this.src)
+	}
+	connectedCallback () {
+		this.sandbox = '' + this.sandbox || 'allow-forms allow-modals allow-pointer-lock allow-popups allow-popups-to-escape-sandbox allow-presentation allow-same-origin allow-scripts allow-top-navigation-by-user-activation' // all except allow-top-navigation
+	}
+	load (url, options) {
+		if (!url || !url.startsWith('http'))
+			throw new Error(`X-Frame-Bypass src ${url} does not start with http(s)://`)
+		console.log('X-Frame-Bypass loading:', url)
+		this.srcdoc = `<html>
+<head>
+	<style>
+	.loader {
+		position: absolute;
+		top: calc(50% - 25px);
+		left: calc(50% - 25px);
+		width: 50px;
+		height: 50px;
+		background-color: #333;
+		border-radius: 50%;  
+		animation: loader 1s infinite ease-in-out;
+	}
+	@keyframes loader {
+		0% {
+		transform: scale(0);
+		}
+		100% {
+		transform: scale(1);
+		opacity: 0;
+		}
+	}
+	</style>
+</head>
+<body>
+	<div class="loader"></div>
+</body>
+</html>`
+		this.fetchProxy(url, options, 0).then(res => res.text()).then(data => {
+			if (data)
+				this.srcdoc = data.replace(/<head([^>]*)>/i, `<head$1>
+	<base href="${url}">
+	<script>
+	// X-Frame-Bypass navigation event handlers
+	document.addEventListener('click', e => {
+		if (frameElement && document.activeElement && document.activeElement.href) {
+			e.preventDefault()
+			frameElement.load(document.activeElement.href)
+		}
+	})
+	document.addEventListener('submit', e => {
+		if (frameElement && document.activeElement && document.activeElement.form && document.activeElement.form.action) {
+			e.preventDefault()
+			if (document.activeElement.form.method === 'post')
+				frameElement.load(document.activeElement.form.action, {method: 'post', body: new FormData(document.activeElement.form)})
+			else
+				frameElement.load(document.activeElement.form.action + '?' + new URLSearchParams(new FormData(document.activeElement.form)))
+		}
+	})
+	</script>`)
+		}).catch(e => console.error('Cannot load X-Frame-Bypass:', e))
+	}
+	fetchProxy (url, options, i) {
+		const proxies = (options || {}).proxies || [
+		    window.BASE_URL + 'cors-anywhere?url=',
+			'https://cors-anywhere.herokuapp.com/',
+			'https://yacdn.org/proxy/',
+			'https://api.codetabs.com/v1/proxy/?quest='
+		]
+		return fetch(proxies[i] + url, options).then(res => {
+			if (!res.ok)
+				throw new Error(`${res.status} ${res.statusText}`);
+			return res
+		}).catch(error => {
+			if (i === proxies.length - 1)
+				throw error
+			return this.fetchProxy(url, options, i + 1)
+		})
+	}
+}, {extends: 'iframe'})

+ 11 - 9
utils/html.go

@@ -9,7 +9,7 @@ import (
 	"github.com/mindoc-org/mindoc/conf"
 )
 
-func StripTags(s string) string  {
+func StripTags(s string) string {
 
 	//将HTML标签全转换成小写
 	re, _ := regexp.Compile("\\<[\\S\\s]+?\\>")
@@ -33,8 +33,9 @@ func StripTags(s string) string  {
 
 	return src
 }
+
 //自动提取文章摘要
-func AutoSummary(body string,l int) string {
+func AutoSummary(body string, l int) string {
 
 	//匹配图片,如果图片语法是在代码块中,这里同样会处理
 	re := regexp.MustCompile(`<p>(.*?)</p>`)
@@ -42,11 +43,11 @@ func AutoSummary(body string,l int) string {
 	contents := re.FindAllString(body, -1)
 
 	if len(contents) <= 0 {
-		return  ""
+		return ""
 	}
 	content := ""
-	for _,s := range contents {
-		b := strings.Replace(StripTags(s),"\n","", -1)
+	for _, s := range contents {
+		b := strings.Replace(StripTags(s), "\n", "", -1)
 
 		if l <= 0 {
 			break
@@ -70,7 +71,9 @@ func SafetyProcessor(html string) string {
 		docQuery.Find("applet").Remove()
 		docQuery.Find("frame").Remove()
 		docQuery.Find("meta").Remove()
-		docQuery.Find("iframe").Remove()
+		if !conf.GetEnableIframe() {
+			docQuery.Find("iframe").Remove()
+		}
 		docQuery.Find("*").Each(func(i int, selection *goquery.Selection) {
 
 			if href, ok := selection.Attr("href"); ok && strings.HasPrefix(href, "javascript:") {
@@ -117,10 +120,9 @@ func SafetyProcessor(html string) string {
 			}
 		}
 
-
 		if html, err := docQuery.Html(); err == nil {
-			return  strings.TrimSuffix(strings.TrimPrefix(strings.TrimSpace(html), "<html><head></head><body>"), "</body></html>")
+			return strings.TrimSuffix(strings.TrimPrefix(strings.TrimSpace(html), "<html><head></head><body>"), "</body></html>")
 		}
 	}
 	return html
-}
+}

+ 6 - 0
views/document/default_read.tpl

@@ -29,6 +29,10 @@
     <link href="{{cdncss "/static/katex/katex.min.css"}}" rel="stylesheet">
     <link href="{{cdncss "/static/css/print.css" "version"}}" media="print" rel="stylesheet">
 
+    <script type="text/javascript">
+        window.IS_ENABLE_IFRAME = '{{conf "enable_iframe" }}' === 'true';
+        window.BASE_URL = '{{urlfor "HomeController.Index" }}';
+    </script>
     <script type="text/javascript">window.book={"identify":"{{.Model.Identify}}"};</script>
     <style>
         .btn-mobile {
@@ -251,6 +255,8 @@
 <script src="{{cdnjs "/static/js/jquery.highlight.js"}}" type="text/javascript"></script>
 <script src="{{cdnjs "/static/js/kancloud.js" "version"}}" type="text/javascript"></script>
 <script src="{{cdnjs "/static/js/splitbar.js" "version"}}" type="text/javascript"></script>
+<script src="{{cdnjs "/static/js/custom-elements-builtin-0.6.5.min.js"}}" type="text/javascript"></script>
+<script src="{{cdnjs "/static/js/x-frame-bypass-1.0.2.js"}}" type="text/javascript"></script>
 <script type="text/javascript">
 $(function () {
     $("#searchList").on("click","a",function () {

+ 6 - 0
views/document/document_password.tpl

@@ -92,6 +92,10 @@
         border: 1px solid #357ebd;
     }
     </style>
+    <script type="text/javascript">
+        window.IS_ENABLE_IFRAME = '{{conf "enable_iframe" }}' === 'true';
+        window.BASE_URL = '{{urlfor "HomeController.Index" }}';
+    </script>
 </head>
 <body>
 <div class="auth_form">
@@ -109,6 +113,8 @@
         </form>
 </div>
 </div>
+<script src="{{cdnjs "/static/js/custom-elements-builtin-0.6.5.min.js"}}" type="text/javascript"></script>
+<script src="{{cdnjs "/static/js/x-frame-bypass-1.0.2.js"}}" type="text/javascript"></script>
 <script type="text/javascript">
 $("#auth_form").ajaxForm({
     beforeSerialize: function () {

+ 6 - 1
views/document/html_edit_template.tpl

@@ -11,7 +11,10 @@
             color: #44B036 !important;
         }
     </style>
-
+    <script type="text/javascript">
+        window.IS_ENABLE_IFRAME = '{{conf "enable_iframe" }}' === 'true';
+        window.BASE_URL = '{{urlfor "HomeController.Index" }}';
+    </script>
     <script type="text/javascript">
         window.editor = null;
         window.imageUploadURL = "{{urlfor "DocumentController.Upload" "identify" .Model.Identify}}";
@@ -207,6 +210,8 @@
 <script src="{{cdnjs "/static/js/jquery.form.js"}}" type="text/javascript"></script>
 <script src="{{cdnjs "/static/js/editor.js"}}" type="text/javascript"></script>
 <script src="{{cdnjs "/static/js/html-editor.js"}}" type="text/javascript"></script>
+<script src="{{cdnjs "/static/js/custom-elements-builtin-0.6.5.min.js"}}" type="text/javascript"></script>
+<script src="{{cdnjs "/static/js/x-frame-bypass-1.0.2.js"}}" type="text/javascript"></script>
 <script type="text/javascript">
     $(function () {
         lang = {{i18n $.Lang "common.js_lang"}};

+ 6 - 0
views/document/kancloud_read_template.tpl

@@ -19,6 +19,10 @@
     <script src="/static/html5shiv/3.7.3/html5shiv.min.js"></script>
     <script src="/static/respond.js/1.4.2/respond.min.js"></script>
     <![endif]-->
+    <script type="text/javascript">
+        window.IS_ENABLE_IFRAME = '{{conf "enable_iframe" }}' === 'true';
+        window.BASE_URL = '{{urlfor "HomeController.Index" }}';
+    </script>
 </head>
 <body>
 <div class="m-manual manual-reader">
@@ -173,6 +177,8 @@
 <script src="{{cdnjs "/static/bootstrap/js/bootstrap.min.js"}}"></script>
 <script src="{{cdnjs "/static/jstree/3.3.4/jstree.min.js"}}" type="text/javascript"></script>
 <script src="{{cdnjs "/static/jquery/plugins/imgbox/jquery.imgbox.pack.js"}}"></script>
+<script src="{{cdnjs "/static/js/custom-elements-builtin-0.6.5.min.js"}}" type="text/javascript"></script>
+<script src="{{cdnjs "/static/js/x-frame-bypass-1.0.2.js"}}" type="text/javascript"></script>
 <script type="text/javascript">
     $(function () {
         $("#sidebar").jstree({

+ 6 - 0
views/document/markdown_edit_template.tpl

@@ -6,6 +6,10 @@
     <meta name="viewport" content="width=device-width, initial-scale=1">
 
     <title>{{i18n .Lang "doc.edit_doc"}} - Powered by MinDoc</title>
+    <script type="text/javascript">
+        window.IS_ENABLE_IFRAME = '{{conf "enable_iframe" }}' === 'true';
+        window.BASE_URL = '{{urlfor "HomeController.Index" }}';
+    </script>
     <script type="text/javascript">
         window.treeCatalog = null;
         window.baseUrl = "{{.BaseUrl}}";
@@ -452,6 +456,8 @@
 <script src="{{cdnjs "/static/js/array.js" "version"}}" type="text/javascript"></script>
 <script src="{{cdnjs "/static/js/editor.js" "version"}}" type="text/javascript"></script>
 <script src="{{cdnjs "/static/js/markdown.js" "version"}}" type="text/javascript"></script>
+<script src="{{cdnjs "/static/js/custom-elements-builtin-0.6.5.min.js"}}" type="text/javascript"></script>
+<script src="{{cdnjs "/static/js/x-frame-bypass-1.0.2.js"}}" type="text/javascript"></script>
 <script type="text/javascript">
     $(function () {
         editLangPath = {{cdnjs "/static/editor.md/languages/"}} + lang

+ 6 - 0
views/document/new_html_edit_template.tpl

@@ -6,6 +6,10 @@
     <meta name="viewport" content="width=device-width, initial-scale=1">
 
     <title>{{i18n .Lang "doc.edit_doc"}} - Powered by MinDoc</title>
+    <script type="text/javascript">
+        window.IS_ENABLE_IFRAME = '{{conf "enable_iframe" }}' === 'true';
+        window.BASE_URL = '{{urlfor "HomeController.Index" }}';
+    </script>
     <script type="text/javascript">
         window.editor = null;
         window.imageUploadURL = "{{urlfor "DocumentController.Upload" "identify" .Model.Identify}}";
@@ -384,6 +388,8 @@
 <script src="{{cdnjs "/static/js/array.js" "version"}}" type="text/javascript"></script>
 <script src="{{cdnjs "/static/js/editor.js"}}" type="text/javascript"></script>
 <script src="{{cdnjs "/static/js/quill.js"}}" type="text/javascript"></script>
+<script src="{{cdnjs "/static/js/custom-elements-builtin-0.6.5.min.js"}}" type="text/javascript"></script>
+<script src="{{cdnjs "/static/js/x-frame-bypass-1.0.2.js"}}" type="text/javascript"></script>
 <script type="text/javascript">
     $(function () {
         lang = {{i18n $.Lang "common.js_lang"}};

部分文件因文件數量過多而無法顯示