@ -6,39 +6,79 @@ import (
"errors"
"fmt"
"io"
"log"
"net/http"
"net/url"
"os"
"strings"
"time"
"github.com/PuerkitoBio/goquery"
"github.com/joncrlsn/dque"
"willnorris.com/go/microformats"
)
func startWebmentionVerifier ( ) {
var wmQueue * dque . DQue
func wmMentionBuilder ( ) interface { } {
return & mention { }
}
func initWebmentionQueue ( ) ( err error ) {
queuePath := "queues"
if _ , err := os . Stat ( queuePath ) ; os . IsNotExist ( err ) {
os . Mkdir ( queuePath , 0755 )
}
wmQueue , err = dque . NewOrOpen ( "webmention" , queuePath , 5 , wmMentionBuilder )
if err != nil {
return err
}
startWebmentionQueue ( )
return nil
}
func startWebmentionQueue ( ) {
go func ( ) {
for {
time . Sleep ( 30 * time . Second )
verifyNextWebmention ( )
if i , err := wmQueue . PeekBlock ( ) ; err == nil {
if i == nil {
// Empty request
_ , _ = wmQueue . Dequeue ( )
continue
}
if m , ok := i . ( * mention ) ; ok {
err = m . verifyMention ( )
if err != nil {
log . Println ( fmt . Sprintf ( "Failed to verify webmention from %s to %s: %s" , m . Source , m . Target , err . Error ( ) ) )
}
_ , _ = wmQueue . Dequeue ( )
} else {
// Invalid type
_ , _ = wmQueue . Dequeue ( )
}
}
}
} ( )
}
func verifyNextWebmention ( ) error {
m := & mention { }
oldStatus := ""
row , err := appDbQueryRow ( "select id, source, target, status from webmentions where (status = ? or status = ?) limit 1" , webmentionStatusNew , webmentionStatusRenew )
func queueMention ( m * mention ) error {
return wmQueue . Enqueue ( m )
}
func ( m * mention ) verifyMention ( ) error {
req , err := http . NewRequest ( http . MethodGet , m . Source , nil )
if err != nil {
return err
}
if err := row . Scan ( & m . ID , & m . Source , & m . Target , & oldStatus ) ; err == sql . ErrNoRows {
return nil
} else if err != nil {
req . Header . Set ( userAgent , appUserAgent )
resp , err := http . DefaultClient . Do ( req )
if err != nil {
return err
}
if err := wmVerify ( m ) ; err != nil {
// Invalid
return deleteWebmention ( m . ID )
err = m . verifyReader ( resp . Body )
_ = resp . Body . Close ( )
if err != nil {
_ , err := appDbExec ( "delete from webmentions where source = @source and target = @target" , sql . Named ( "source" , m . Source ) , sql . Named ( "target" , m . Target ) )
return err
}
if len ( m . Content ) > 500 {
m . Content = m . Content [ 0 : 497 ] + "…"
@ -48,28 +88,18 @@ func verifyNextWebmention() error {
// Approve if it's server-intern
newStatus = webmentionStatusApproved
}
_ , err = appDbExec ( "update webmentions set status = ?, title = ?, content = ?, author = ? where id = ?" , newStatus , m . Title , m . Content , m . Author , m . ID )
if oldStatus == string ( webmentionStatusNew ) {
if webmentionExists ( m . Source , m . Target ) {
_ , err = appDbExec ( "update webmentions set status = @status, title = @title, content = @content, author = @author where source = @source and target = @target" ,
sql . Named ( "status" , newStatus ) , sql . Named ( "title" , m . Title ) , sql . Named ( "content" , m . Content ) , sql . Named ( "author" , m . Author ) , sql . Named ( "source" , m . Source ) , sql . Named ( "target" , m . Target ) )
} else {
_ , err = appDbExec ( "insert into webmentions (source, target, created, status, title, content, author) values (@source, @target, @created, @status, @title, @content, @author)" ,
sql . Named ( "source" , m . Source ) , sql . Named ( "target" , m . Target ) , sql . Named ( "created" , m . Created ) , sql . Named ( "status" , newStatus ) , sql . Named ( "title" , m . Title ) , sql . Named ( "content" , m . Content ) , sql . Named ( "author" , m . Author ) )
sendNotification ( fmt . Sprintf ( "New webmention from %s to %s" , m . Source , m . Target ) )
}
return err
}
func wmVerify ( m * mention ) error {
req , err := http . NewRequest ( http . MethodGet , m . Source , nil )
if err != nil {
return err
}
req . Header . Set ( userAgent , appUserAgent )
resp , err := http . DefaultClient . Do ( req )
if err != nil {
return err
}
defer resp . Body . Close ( )
return wmVerifyReader ( resp . Body , m )
}
func wmVerifyReader ( body io . Reader , m * mention ) error {
func ( m * mention ) verifyReader ( body io . Reader ) error {
var linksBuffer , gqBuffer , mfBuffer bytes . Buffer
io . Copy ( io . MultiWriter ( & linksBuffer , & gqBuffer , & mfBuffer ) , body )
// Check if source mentions target
@ -100,17 +130,17 @@ func wmVerifyReader(body io.Reader, m *mention) error {
if err != nil {
return err
}
mfFillMentionFromData ( m , microformats . Parse ( & mfBuffer , sourceURL ) )
m . fillFromData ( microformats . Parse ( & mfBuffer , sourceURL ) )
return nil
}
func mfFillMentionFromData ( m * mention , mf * microformats . Data ) {
func ( m * mention ) fillFromData ( mf * microformats . Data ) {
for _ , i := range mf . Items {
mfFillMention ( m , i )
m . fill ( i )
}
}
func mfFillMention ( m * mention , mf * microformats . Microformat ) bool {
func ( m * mention ) fill ( mf * microformats . Microformat ) bool {
if mfHasType ( mf , "h-entry" ) {
if name , ok := mf . Properties [ "name" ] ; ok && len ( name ) > 0 {
if title , ok := name [ 0 ] . ( string ) ; ok {
@ -136,7 +166,7 @@ func mfFillMention(m *mention, mf *microformats.Microformat) bool {
return true
} else if len ( mf . Children ) > 0 {
for _ , mfc := range mf . Children {
if mfFillMention ( m , mfc ) {
if m . fill ( mfc ) {
return true
}
}