// Copyright 2020 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package main import ( "archive/zip" "bytes" "embed" "flag" "fmt" "html" "io/fs" "log" "net/http" "os" "path" "path/filepath" "strings" "time" "unicode/utf8" "github.com/google/codesearch/index" "github.com/google/codesearch/regexp" ) var ( verboseFlag = flag.Bool("verbose", false, "print extra information") ) func main() { http.HandleFunc("/", home) http.Handle("/_static/", http.FileServer(http.FS(static))) http.HandleFunc("/show/", show) log.Fatal(http.ListenAndServe("localhost:2473", nil)) } //go:embed _static var static embed.FS func home(w http.ResponseWriter, r *http.Request) { qarg := r.FormValue("q") w.Write([]byte(strings.ReplaceAll(homePage, "QUERY", html.EscapeString(qarg)))) if qarg == "" { return } g := regexp.Grep{ HTML: true, Limit: 100000, Stdout: w, Stderr: w, } pat := "(?m)" + qarg re, err := regexp.Compile(pat) if err != nil { fmt.Fprintf(w, "Bad query: %v\n", err) return } g.Regexp = re var fre *regexp.Regexp farg := r.FormValue("f") if farg != "" { fre, err = regexp.Compile(farg) if err != nil { fmt.Fprintf(w, "Bad -f flag: %v\n", err) return } } q := index.RegexpQuery(re.Syntax) if *verboseFlag { log.Printf("query: %s\n", q) } start := time.Now() ix := index.Open(index.File()) ix.Verbose = *verboseFlag post := ix.PostingQuery(q) if *verboseFlag { fmt.Fprintf(w, "post query identified %d possible files\n", len(post)) } if fre != nil { fnames := make([]int, 0, len(post)) for _, fileid := range post { name := ix.Name(fileid) if fre.MatchString(name.String(), true, true) < 0 { continue } fnames = append(fnames, fileid) } if *verboseFlag { fmt.Fprintf(w, "filename regexp matched %d files\n", len(fnames)) } post = fnames } var ( zipFile string zipReader *zip.ReadCloser zipMap map[string]*zip.File ) for _, fileid := range post { if g.Limited { break } name := ix.Name(fileid).String() file, err := os.Open(name) if err != nil { if i := strings.Index(name, ".zip\x01"); i >= 0 { zfile, zname := name[:i+4], name[i+5:] if zfile != zipFile { if zipReader != nil { zipReader.Close() zipMap = nil } zipFile = zfile zipReader, err = zip.OpenReader(zfile) if err != nil { zipReader = nil } if zipReader != nil { zipMap = make(map[string]*zip.File) for _, file := range zipReader.File { zipMap[file.Name] = file } } } file := zipMap[zname] if file != nil { r, err := file.Open() if err != nil { continue } g.Reader(r, name) r.Close() continue } } continue } g.Reader(file, name) file.Close() } fmt.Fprintf(w, "\n%d matches in %.3fs\n", g.Matches, time.Since(start).Seconds()) if g.Limited { fmt.Fprintf(w, "more matches not shown due to match limit\n") } } var homePage = `
Code Search
`
func show(w http.ResponseWriter, r *http.Request) {
file := strings.TrimPrefix(r.URL.Path, "/show")
if strings.HasPrefix(file, "/") && filepath.IsAbs(file[1:]) {
// Turn /c:/foo into c:/foo on Windows.
file = file[1:]
}
// TODO maybe trim file by ix.roots
// TODO zips
info, err := os.Stat(file)
if err != nil {
// TODO
http.Error(w, err.Error(), 500)
return
}
if info.IsDir() {
dirs, err := os.ReadDir(file)
if err != nil {
http.Error(w, err.Error(), 500)
return
}
w.Write(serveDir(file, dirs))
return
}
data, err := os.ReadFile(file)
if err != nil {
http.Error(w, err.Error(), 500)
return
}
w.Write(serveFile(file, data))
}
func printHeader(buf *bytes.Buffer, file string) {
e := html.EscapeString
buf.WriteString("\n\n")
buf.WriteString("\n")
buf.WriteString("\n")
fmt.Fprintf(buf, `%s - code search `, e(file))
buf.WriteString("\n\n")
f := ""
for _, elem := range strings.Split(file, "/") {
f += "/" + elem
fmt.Fprintf(buf, `/%s`, e(f), e(elem))
}
fmt.Fprintf(buf, ` (about)`)
fmt.Fprintf(buf, "\n\n")
}
func serveDir(file string, dir []fs.DirEntry) []byte {
var buf bytes.Buffer
e := html.EscapeString
printHeader(&buf, file)
for _, d := range dir {
// Note: file is the full path including mod@vers.
file := path.Join(file, d.Name())
fmt.Fprintf(&buf, "%s\n", e(file), e(path.Base(file)))
}
return buf.Bytes()
}
var nl = []byte("\n")
func serveFile(file string, data []byte) []byte {
if !isText(data) {
return data
}
var buf bytes.Buffer
e := html.EscapeString
printHeader(&buf, file)
n := 1 + bytes.Count(data, nl)
wid := len(fmt.Sprintf("%d", n))
wid = (wid+2+7)&^7 - 2
n = 1
for len(data) > 0 {
var line []byte
line, data, _ = bytes.Cut(data, nl)
fmt.Fprintf(&buf, "%*d %s\n", n, wid, n, e(string(line)))
n++
}
return buf.Bytes()
}
// isText reports whether a significant prefix of s looks like correct UTF-8;
// that is, if it is likely that s is human-readable text.
func isText(s []byte) bool {
const max = 1024 // at least utf8.UTFMax
if len(s) > max {
s = s[0:max]
}
for i, c := range string(s) {
if i+utf8.UTFMax > len(s) {
// last char may be incomplete - ignore
break
}
if c == 0xFFFD || c < ' ' && c != '\n' && c != '\t' && c != '\f' {
// decoding error or control character - not a text file
return false
}
}
return true
}