| @@ -15,10 +15,9 @@ import ( | |||
| "strings" | |||
| "github.com/russross/blackfriday" | |||
| "golang.org/x/net/html" | |||
| "github.com/gogits/gogs/modules/setting" | |||
| "golang.org/x/net/html" | |||
| ) | |||
| func isletter(c byte) bool { | |||
| @@ -109,40 +108,22 @@ func (options *CustomRender) Image(out *bytes.Buffer, link []byte, title []byte, | |||
| } | |||
| var ( | |||
| MentionPattern = regexp.MustCompile(`(\s|^)@[0-9a-zA-Z_]+`) | |||
| MentionPattern = regexp.MustCompile(`(\s|^)@[0-9a-zA-Z_\.]+`) | |||
| commitPattern = regexp.MustCompile(`(\s|^)https?.*commit/[0-9a-zA-Z]+(#+[0-9a-zA-Z-]*)?`) | |||
| issueFullPattern = regexp.MustCompile(`(\s|^)https?.*issues/[0-9]+(#+[0-9a-zA-Z-]*)?`) | |||
| issueIndexPattern = regexp.MustCompile(`( |^)#[0-9]+`) | |||
| issueIndexPattern = regexp.MustCompile(`( |^)#[0-9]+\b`) | |||
| sha1CurrentPattern = regexp.MustCompile(`\b[0-9a-f]{40}\b`) | |||
| ) | |||
| func RenderSpecialLink(rawBytes []byte, urlPrefix string) []byte { | |||
| buf := bytes.NewBufferString("") | |||
| inCodeBlock := false | |||
| codeBlockPrefix := []byte("```") | |||
| lineBreak := []byte("\n") | |||
| tab := []byte("\t") | |||
| lines := bytes.Split(rawBytes, lineBreak) | |||
| for _, line := range lines { | |||
| if bytes.HasPrefix(line, codeBlockPrefix) { | |||
| inCodeBlock = !inCodeBlock | |||
| } | |||
| if !inCodeBlock && !bytes.HasPrefix(line, tab) { | |||
| ms := MentionPattern.FindAll(line, -1) | |||
| for _, m := range ms { | |||
| m = bytes.TrimSpace(m) | |||
| line = bytes.Replace(line, m, | |||
| []byte(fmt.Sprintf(`<a href="%s/%s">%s</a>`, setting.AppSubUrl, m[1:], m)), -1) | |||
| } | |||
| } | |||
| buf.Write(line) | |||
| buf.Write(lineBreak) | |||
| ms := MentionPattern.FindAll(rawBytes, -1) | |||
| for _, m := range ms { | |||
| m = bytes.TrimSpace(m) | |||
| rawBytes = bytes.Replace(rawBytes, m, | |||
| []byte(fmt.Sprintf(`<a href="%s/%s">%s</a>`, setting.AppSubUrl, m[1:], m)), -1) | |||
| } | |||
| rawBytes = buf.Bytes() | |||
| ms := commitPattern.FindAll(rawBytes, -1) | |||
| ms = commitPattern.FindAll(rawBytes, -1) | |||
| for _, m := range ms { | |||
| m = bytes.TrimSpace(m) | |||
| i := strings.Index(string(m), "commit/") | |||
| @@ -181,8 +162,14 @@ func RenderSha1CurrentPattern(rawBytes []byte, urlPrefix string) []byte { | |||
| func RenderIssueIndexPattern(rawBytes []byte, urlPrefix string) []byte { | |||
| ms := issueIndexPattern.FindAll(rawBytes, -1) | |||
| for _, m := range ms { | |||
| rawBytes = bytes.Replace(rawBytes, m, []byte(fmt.Sprintf(`<a href="%s/issues/%s">%s</a>`, | |||
| urlPrefix, strings.TrimPrefix(string(m[1:]), "#"), m)), -1) | |||
| var space string | |||
| m2 := m | |||
| if m2[0] == ' ' { | |||
| space = " " | |||
| m2 = m2[1:] | |||
| } | |||
| rawBytes = bytes.Replace(rawBytes, m, []byte(fmt.Sprintf(`%s<a href="%s/issues/%s">%s</a>`, | |||
| space, urlPrefix, m2[1:], m2)), 1) | |||
| } | |||
| return rawBytes | |||
| } | |||
| @@ -219,46 +206,35 @@ func RenderRawMarkdown(body []byte, urlPrefix string) []byte { | |||
| return body | |||
| } | |||
| func RenderMarkdown(rawBytes []byte, urlPrefix string) []byte { | |||
| result := RenderRawMarkdown(rawBytes, urlPrefix) | |||
| result = PostProcessMarkdown(result, urlPrefix) | |||
| result = Sanitizer.SanitizeBytes(result) | |||
| return result | |||
| } | |||
| func RenderMarkdownString(raw, urlPrefix string) string { | |||
| return string(RenderMarkdown([]byte(raw), urlPrefix)) | |||
| } | |||
| // PostProcessMarkdown treats different types of HTML differently, | |||
| // and only renders special links for plain text blocks. | |||
| func PostProcessMarkdown(rawHtml []byte, urlPrefix string) []byte { | |||
| var buf bytes.Buffer | |||
| tokenizer := html.NewTokenizer(bytes.NewReader(rawHtml)) | |||
| for html.ErrorToken != tokenizer.Next() { | |||
| token := tokenizer.Token() | |||
| switch token.Type { | |||
| case html.TextToken: | |||
| text := []byte(token.String()) | |||
| text = RenderSpecialLink(text, urlPrefix) | |||
| buf.Write(text) | |||
| case html.StartTagToken: | |||
| buf.WriteString(token.String()) | |||
| tagName := token.Data | |||
| // If this is an excluded tag, we skip processing all output until a close tag is encountered | |||
| if strings.EqualFold("a", tagName) || strings.EqualFold("code", tagName) || strings.EqualFold("pre", tagName) { | |||
| for html.ErrorToken != tokenizer.Next() { | |||
| token = tokenizer.Token() | |||
| // Copy the token to the output verbatim | |||
| buf.WriteString(token.String()) | |||
| // If this is the close tag, we are done | |||
| if html.EndTagToken == token.Type && strings.EqualFold(tagName, token.Data) { break } | |||
| case html.TextToken: | |||
| buf.Write(RenderSpecialLink([]byte(token.String()), urlPrefix)) | |||
| case html.StartTagToken: | |||
| buf.WriteString(token.String()) | |||
| tagName := token.Data | |||
| // If this is an excluded tag, we skip processing all output until a close tag is encountered. | |||
| if strings.EqualFold("a", tagName) || strings.EqualFold("code", tagName) || strings.EqualFold("pre", tagName) { | |||
| for html.ErrorToken != tokenizer.Next() { | |||
| token = tokenizer.Token() | |||
| // Copy the token to the output verbatim | |||
| buf.WriteString(token.String()) | |||
| // If this is the close tag, we are done | |||
| if html.EndTagToken == token.Type && strings.EqualFold(tagName, token.Data) { | |||
| break | |||
| } | |||
| } | |||
| } | |||
| default: | |||
| buf.WriteString(token.String()) | |||
| default: | |||
| buf.WriteString(token.String()) | |||
| } | |||
| } | |||
| @@ -266,7 +242,18 @@ func PostProcessMarkdown(rawHtml []byte, urlPrefix string) []byte { | |||
| return buf.Bytes() | |||
| } | |||
| // If we are not at the end of the input, then some other parsing error has occurred, so return | |||
| // the input verbatim. | |||
| // If we are not at the end of the input, then some other parsing error has occurred, | |||
| // so return the input verbatim. | |||
| return rawHtml | |||
| } | |||
| func RenderMarkdown(rawBytes []byte, urlPrefix string) []byte { | |||
| result := RenderRawMarkdown(rawBytes, urlPrefix) | |||
| result = PostProcessMarkdown(result, urlPrefix) | |||
| result = Sanitizer.SanitizeBytes(result) | |||
| return result | |||
| } | |||
| func RenderMarkdownString(raw, urlPrefix string) string { | |||
| return string(RenderMarkdown([]byte(raw), urlPrefix)) | |||
| } | |||