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
+
+
+
+
+
+
+
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
}
-*/