From 5c669c8b937e35a11baf40c0b9db305578b45a0c Mon Sep 17 00:00:00 2001 From: cdramey Date: Sat, 17 Nov 2018 16:47:39 +0000 Subject: [PATCH] Added URL submission --- assets/index.html | 2 +- assets/qurl.css | 1 + assets/submit.html | 24 ++++++++++++ load.go | 3 +- main.go | 13 ++++++- pages/submit.go | 79 ++++++++++++++++++++++++++++++++++++++ qurl/qurl.go | 22 +++++++++++ static/static.go | 2 +- storage/bolt/qurl.go | 91 +++++++++++++++++--------------------------- 9 files changed, 175 insertions(+), 62 deletions(-) create mode 100644 assets/submit.html create mode 100644 pages/submit.go diff --git a/assets/index.html b/assets/index.html index 9217d6c..c7ca6b1 100644 --- a/assets/index.html +++ b/assets/index.html @@ -9,7 +9,7 @@
qurl.org is a simple url shortening service, in the same vein as - bit.ly, and + bit.ly and tinyurl.com. qurl.org is open source, it's code is freely available diff --git a/assets/qurl.css b/assets/qurl.css index a94463b..e6daf42 100644 --- a/assets/qurl.css +++ b/assets/qurl.css @@ -10,6 +10,7 @@ input:focus { outline: 0; } #u:focus { border-color: #129fea; } #s { margin-top: 8px; color: #fff; background-color: #0078e7; border-color: transparent; } #s:hover { background-image: linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1)); cursor: hover; } +#m { margin-bottom: 16px; } @media screen and (min-width: 640px) { #c { max-width: 600px; } #u, #s { display: inline-block; margin: 0; } diff --git a/assets/submit.html b/assets/submit.html new file mode 100644 index 0000000..b441af3 --- /dev/null +++ b/assets/submit.html @@ -0,0 +1,24 @@ + + + + + qurl.org + + + + +
+
+
{{if .Message}}{{.Message}}{{end}}
+
{{if .URL}}{{.URL}}{{end}}
+
+ + qurl.org is a simple url shortening service, in the same vein as + bit.ly and + tinyurl.com. + qurl.org is open source, + it's code is freely available + and has an easy to use API. +
+ + diff --git a/load.go b/load.go index 6cd603e..f1c7134 100644 --- a/load.go +++ b/load.go @@ -61,7 +61,8 @@ func loadjson(stor storage.Storage, filename string) error { err := stor.AddQURL(qurl) if err != nil { - return fmt.Errorf("Error adding qurl: %s", err.Error()) + fmt.Printf("\nError adding qurl: %s\n", err.Error()) + continue } count++ diff --git a/main.go b/main.go index 7299ecc..e6006f6 100644 --- a/main.go +++ b/main.go @@ -6,14 +6,14 @@ import ( "net" "net/http" "os" - "qurl/storage" + "qurl/pages" "qurl/static" + "qurl/storage" "runtime" ) //go:generate bindata -m Assets -r assets -p static -o static/assets.go assets - func main() { dburl := flag.String("u", "bolt:./qurl.db", "url to database") lsaddr := flag.String("l", "127.0.0.1:8080", "listen address/port") @@ -59,6 +59,15 @@ func main() { mux.Handle("/favicon.ico", &static.StaticContent{Content: "favicon.ico"}) mux.Handle("/qurl.css", &static.StaticContent{Content: "qurl.css"}) + submit := &pages.SubmitHandler{Storage: stor} + err = submit.Init() + if err != nil { + fmt.Fprintf(os.Stderr, "Submit init error: %s\n", err.Error()) + return + } + + mux.Handle("/submit.html", submit) + fmt.Fprintf(os.Stdout, "qurl listening .. \n") err = http.Serve(listen, mux) if err != nil { diff --git a/pages/submit.go b/pages/submit.go new file mode 100644 index 0000000..3467747 --- /dev/null +++ b/pages/submit.go @@ -0,0 +1,79 @@ +package pages + +import ( + "bytes" + "fmt" + "html/template" + "net/http" + "qurl/qurl" + "qurl/static" + "qurl/storage" + "time" +) + +type SubmitHandler struct { + Storage storage.Storage + template *template.Template +} + +type submitPage struct { + Message string + URL string +} + +func (ctx *SubmitHandler) Init() error { + ctx.template = template.New("submit.html") + + _, err := ctx.template.Parse(string(static.Assets["submit.html"])) + if err != nil { + return err + } + return nil +} + +func (ctx *SubmitHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + var pg submitPage + + u := r.FormValue("url") + if u == "" { + pg.Message = "Not a valid URL." + } else { + q, err := ctx.Storage.GetQURLByURL(u) + if err != nil { + http.Error(w, fmt.Sprintf("Database error: %s", err.Error()), + http.StatusInternalServerError) + return + } + + if q != nil { + pg.Message = "URL already exists." + pg.URL = fmt.Sprintf("https://qurl.org/%s", qurl.ToString(q.ID)) + } else { + q = &qurl.QURL{ + URL: u, + Created: time.Now(), + } + + err := ctx.Storage.AddQURL(q) + if err != nil { + http.Error(w, fmt.Sprintf("Database error: %s", err.Error()), + http.StatusInternalServerError) + return + } + + pg.Message = "URL Added." + pg.URL = fmt.Sprintf("https://qurl.org/%s", qurl.ToString(q.ID)) + } + } + + var buf bytes.Buffer + err := ctx.template.Execute(&buf, pg) + if err != nil { + http.Error(w, fmt.Sprintf("Template execute error: %s", err.Error()), + http.StatusInternalServerError) + return + } + w.Header().Set("Content-Length", fmt.Sprintf("%d", buf.Len())) + w.Header().Set("Content-Type", "text/html") + w.Write(buf.Bytes()) +} diff --git a/qurl/qurl.go b/qurl/qurl.go index d98e04c..fd1812d 100644 --- a/qurl/qurl.go +++ b/qurl/qurl.go @@ -4,6 +4,7 @@ import ( "fmt" "strings" "time" + "net/url" ) type QURL struct { @@ -14,6 +15,27 @@ type QURL struct { Browser string } +func (q *QURL) CheckValid() error { + if q == nil { + return fmt.Errorf("QURL is nil") + } + + if q.URL == "" { + return fmt.Errorf("URL may not be empty") + } + + u, err := url.Parse(q.URL) + if err != nil { + return err + } + + if !u.IsAbs() { + return fmt.Errorf("Relative URLs are not allowed") + } + + return nil +} + const alpha = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" const alphalen = uint64(len(alpha)) diff --git a/static/static.go b/static/static.go index cc0d326..7cb44a5 100644 --- a/static/static.go +++ b/static/static.go @@ -6,8 +6,8 @@ import ( "crypto/md5" "fmt" "mime" - "path" "net/http" + "path" "strings" ) diff --git a/storage/bolt/qurl.go b/storage/bolt/qurl.go index 15b2094..1134bca 100644 --- a/storage/bolt/qurl.go +++ b/storage/bolt/qurl.go @@ -3,12 +3,10 @@ package bolt import ( "encoding/binary" "qurl/qurl" -// "bytes" -// "fmt" ) var ( - QURLBucket = []byte{0x00} + QURLBucket = []byte{0x00} ReverseBucket = []byte{0x01} URLField = []byte{0x00} @@ -17,7 +15,12 @@ var ( BrowserField = []byte{0x03} ) -func (stor *BoltStorage) AddQURL(qurl *qurl.QURL) error { +func (stor *BoltStorage) AddQURL(q *qurl.QURL) error { + err := q.CheckValid() + if err != nil { + return err + } + tx, err := stor.DB.Begin(true) if err != nil { return err @@ -30,16 +33,21 @@ func (stor *BoltStorage) AddQURL(qurl *qurl.QURL) error { } // Populate the ID from the sequence if we don't have one - if qurl.ID == 0 { - qurl.ID, err = rb.NextSequence() + if q.ID == 0 { + s, err := rb.NextSequence() if err != nil { return err } + + // Bolt's sequence starts at 1, so for + // backwards compatibility we have subtract + // one so we're zero-based + q.ID = (s - 1) } // Create a byte array from the ID bid := make([]byte, 8) - binary.BigEndian.PutUint64(bid, qurl.ID) + binary.BigEndian.PutUint64(bid, q.ID) // Add an entry into the reverse indexed bucket for quickly // determining if a URL is already in the database @@ -47,7 +55,7 @@ func (stor *BoltStorage) AddQURL(qurl *qurl.QURL) error { if err != nil { return err } - err = ab.Put([]byte(qurl.URL), bid) + err = ab.Put([]byte(q.URL), bid) qb, err := rb.CreateBucketIfNotExists(bid) if err != nil { @@ -55,14 +63,14 @@ func (stor *BoltStorage) AddQURL(qurl *qurl.QURL) error { } // Write the ID to URL - err = qb.Put(URLField, []byte(qurl.URL)) + err = qb.Put(URLField, []byte(q.URL)) if err != nil { return err } - if !qurl.Created.IsZero() { + if !q.Created.IsZero() { // Create byte array from the Created date - bdt, err := qurl.Created.MarshalBinary() + bdt, err := q.Created.MarshalBinary() if err != nil { return err } @@ -74,15 +82,15 @@ func (stor *BoltStorage) AddQURL(qurl *qurl.QURL) error { } } - if qurl.IP != nil { - err = qb.Put(IPField, qurl.IP) + if q.IP != nil { + err = qb.Put(IPField, q.IP) if err != nil { return err } } - if len(qurl.Browser) > 0 { - err = qb.Put(BrowserField, []byte(qurl.Browser)) + if len(q.Browser) > 0 { + err = qb.Put(BrowserField, []byte(q.Browser)) if err != nil { return err } @@ -107,7 +115,10 @@ func (stor *BoltStorage) SetQURLSequence(seq uint64) error { return err } - qb.SetSequence(seq) + // Since the sequence number is decremented by one + // for backwards compatibility (see above) + // we increment it by one when setting the sequence + qb.SetSequence(seq + 1) if err := tx.Commit(); err != nil { return err @@ -118,10 +129,10 @@ func (stor *BoltStorage) SetQURLSequence(seq uint64) error { func (stor *BoltStorage) GetQURLByURL(u string) (*qurl.QURL, error) { tx, err := stor.DB.Begin(false) - if err != nil { - return nil, err - } - defer tx.Rollback() + if err != nil { + return nil, err + } + defer tx.Rollback() ab := tx.Bucket(ReverseBucket) if ab == nil { @@ -143,46 +154,12 @@ func (stor *BoltStorage) GetQURLByURL(u string) (*qurl.QURL, error) { return nil, nil } - qurl := &qurl.QURL{ ID: binary.BigEndian.Uint64(bid) } + q := &qurl.QURL{ID: binary.BigEndian.Uint64(bid)} qu := qb.Get(URLField) if qu != nil { - qurl.URL = string(qu) + q.URL = string(qu) } - return qurl, nil -} - -/* -func (stor *BoltStorage) GetQURLByURL(u string) (*qurl.QURL, error) { - tx, err := stor.DB.Begin(false) - if err != nil { - return nil, err - } - defer tx.Rollback() - - rb := tx.Bucket(QURLBucket) - if rb == nil { - return nil, nil - } - - bu := []byte(u) - rc := rb.Cursor() - for k, _ := rc.First(); k != nil; k, _ = rc.Next() { - qb := rb.Bucket(k) - if qb == nil { - continue - } - - qu := qb.Get(URLField) - if bytes.Equal(bu, qu) { - qurl := &qurl.QURL{ - ID: binary.BigEndian.Uint64(k), - URL: string(qu), - } - return qurl, nil - } - } - return nil, nil + return q, nil } -*/