diff --git a/appview/db/pulls.go b/appview/db/pulls.go --- a/appview/db/pulls.go +++ b/appview/db/pulls.go @@ -83,16 +83,10 @@ err := e.QueryRow(`select pull_at from pulls where repo_at = ? and pull_id = ?`, repoAt, pullId).Scan(&pullAt) return pullAt, err } -func GetPullId(e Execer, repoAt syntax.ATURI) (int, error) { +func NextPullId(e Execer, repoAt syntax.ATURI) (int, error) { var pullId int err := e.QueryRow(`select next_pull_id from repo_pull_seqs where repo_at = ?`, repoAt).Scan(&pullId) return pullId - 1, err -} - -func GetPullOwnerDid(e Execer, repoAt syntax.ATURI, pullId int) (string, error) { - var ownerDid string - err := e.QueryRow(`select owner_did from pulls where repo_at = ? and pull_id = ?`, repoAt, pullId).Scan(&ownerDid) - return ownerDid, err } func GetPulls(e Execer, repoAt syntax.ATURI) ([]Pull, error) { diff --git a/appview/pages/pages.go b/appview/pages/pages.go --- a/appview/pages/pages.go +++ b/appview/pages/pages.go @@ -509,6 +509,7 @@ type RepoNewPullParams struct { LoggedInUser *auth.User RepoInfo RepoInfo + Branches []types.Branch Active string } diff --git a/appview/pages/templates/repo/pulls/new.html b/appview/pages/templates/repo/pulls/new.html --- a/appview/pages/templates/repo/pulls/new.html +++ b/appview/pages/templates/repo/pulls/new.html @@ -15,7 +15,16 @@

The branch you want to make your change against.

- +
diff --git a/appview/pages/templates/settings.html b/appview/pages/templates/settings.html --- a/appview/pages/templates/settings.html +++ b/appview/pages/templates/settings.html @@ -12,7 +12,6 @@
{{ end }} {{ define "profile" }} -<<<<<<< HEAD

profile

@@ -29,7 +28,6 @@
{{ end }} {{ define "keys" }} -<<<<<<< HEAD

ssh keys

diff --git a/appview/randomart/randomart.go b/appview/randomart/randomart.go new file mode 100644 --- /dev/null +++ b/appview/randomart/randomart.go @@ -0,0 +1,173 @@ +package randomart + +/* + * Draw an ASCII-Art representing the fingerprint so human brain can + * profit from its built-in pattern recognition ability. + * This technique is called "random art" and can be found in some + * scientific publications like this original paper: + * + * "Hash Visualization: a New Technique to improve Real-World Security", + * Perrig A. and Song D., 1999, International Workshop on Cryptographic + * Techniques and E-Commerce (CrypTEC '99) + * sparrow.ece.cmu.edu/~adrian/projects/validation/validation.pdf + * + * The subject came up in a talk by Dan Kaminsky, too. + * + * If you see the picture is different, the key is different. + * If the picture looks the same, you still know nothing. + * + * The algorithm used here is a worm crawling over a discrete plane, + * leaving a trace (augmenting the field) everywhere it goes. + * Movement is taken from dgst_raw 2bit-wise. Bumping into walls + * makes the respective movement vector be ignored for this turn. + * Graphs are not unambiguous, because circles in graphs can be + * walked in either direction. + */ + +import ( + "os" +) + +func MAX(a, b int) int { + if a > b { + return a + } + return b +} + +func MIN(a, b int) int { + if a < b { + return a + } + return b +} + +/* + * Field sizes for the random art. Have to be odd, so the starting point + * can be in the exact middle of the picture, and FLDBASE should be >=8 . + * Else pictures would be too dense, and drawing the frame would + * fail, too, because the key type would not fit in anymore. + */ +const ( + FLDBASE = 8 + FLDSIZE_Y = (FLDBASE + 1) + FLDSIZE_X = (FLDBASE*2 + 1) +) + +func FromString(str string) string { + ch := make(chan byte) + + go func() { + defer close(ch) + for _, v := range []byte(str) { + ch <- v + } + }() + return key_fingerprint_randomart(ch) +} + +func FromFile(file *os.File) string { + ch := make(chan byte) + + go func() { + defer close(ch) + // TODO make input a 1 element byte array + input := make([]byte, 1) + nread, err := file.Read(input) + for err == nil && nread > 0 { + ch <- input[0] + nread, err = file.Read(input) + } + }() + return key_fingerprint_randomart(ch) +} + +func key_fingerprint_randomart(ch chan byte) string { + /* + * Chars to be used after each other every time the worm + * intersects with itself. Matter of taste. + */ + augment_string := " .o+=*BOX@%&#/^SE" + var field [FLDSIZE_X][FLDSIZE_Y]byte + len_aug := len(augment_string) - 1 + var retval [(FLDSIZE_X + 3) * (FLDSIZE_Y + 2)]byte + + /* initialize field */ + x := FLDSIZE_X / 2 + y := FLDSIZE_Y / 2 + + /* process raw key */ + for input, ok := <-ch; ok; input, ok = <-ch { + /* each byte conveys four 2-bit move commands */ + for b := 0; b < 4; b++ { + /* evaluate 2 bit, rest is shifted later */ + if input&0x1 > 0 { + x += 1 + } else { + x += -1 + } + + if input&0x2 > 0 { + y++ + } else { + y-- + } + + /* assure we are still in bounds */ + x = MAX(x, 0) + y = MAX(y, 0) + x = MIN(x, FLDSIZE_X-1) + y = MIN(y, FLDSIZE_Y-1) + + /* augment the field */ + if int(field[x][y]) < len_aug-2 { + field[x][y]++ + } + input = input >> 2 + } + } + + /* mark starting point and end point*/ + field[FLDSIZE_X/2][FLDSIZE_Y/2] = byte(len_aug - 1) + field[x][y] = byte(len_aug) + + i := 0 + retval[i] = '+' + i++ + + /* output upper border */ + for x := 0; x < FLDSIZE_X; x++ { + retval[i] = '-' + i++ + } + retval[i] = '+' + i++ + retval[i] = '\n' + i++ + + /* output content */ + for y := 0; y < FLDSIZE_Y; y++ { + retval[i] = '|' + i++ + for x := 0; x < FLDSIZE_X; x++ { + retval[i] = augment_string[MIN(int(field[x][y]), len_aug)] + i++ + } + retval[i] = '|' + i++ + retval[i] = '\n' + i++ + } + + /* output lower border */ + retval[i] = '+' + i++ + for j := 0; j < FLDSIZE_X; j++ { + retval[i] = '-' + i++ + } + retval[i] = '+' + i++ + + return string(retval[0:i]) +} diff --git a/appview/state/repo.go b/appview/state/repo.go --- a/appview/state/repo.go +++ b/appview/state/repo.go @@ -35,19 +35,15 @@ if err != nil { log.Println("failed to fully resolve repo", err) return } - protocol := "http" - if !s.config.Dev { - protocol = "https" - } - var reqUrl string - if ref != "" { - reqUrl = fmt.Sprintf("%s://%s/%s/%s/tree/%s", protocol, f.Knot, f.OwnerDid(), f.RepoName, ref) - } else { - reqUrl = fmt.Sprintf("%s://%s/%s/%s", protocol, f.Knot, f.OwnerDid(), f.RepoName) + us, err := NewUnsignedClient(f.Knot, s.config.Dev) + if err != nil { + log.Printf("failed to create unsigned client for %s", f.Knot) + s.pages.Error503(w) + return } - resp, err := http.Get(reqUrl) + resp, err := us.Index(f.OwnerDid(), f.RepoName, ref) if err != nil { s.pages.Error503(w) log.Println("failed to reach knotserver", err) @@ -303,9 +299,36 @@ } switch r.Method { case http.MethodGet: + us, err := NewUnsignedClient(f.Knot, s.config.Dev) + if err != nil { + log.Printf("failed to create unsigned client for %s", f.Knot) + s.pages.Error503(w) + return + } + + resp, err := us.Branches(f.OwnerDid(), f.RepoName) + if err != nil { + log.Println("failed to reach knotserver", err) + return + } + + body, err := io.ReadAll(resp.Body) + if err != nil { + log.Printf("Error reading response body: %v", err) + return + } + + var result types.RepoBranchesResponse + err = json.Unmarshal(body, &result) + if err != nil { + log.Println("failed to parse response:", err) + return + } + s.pages.RepoNewPull(w, pages.RepoNewPullParams{ LoggedInUser: user, RepoInfo: f.RepoInfo(s, user), + Branches: result.Branches, }) case http.MethodPost: title := r.FormValue("title") @@ -347,7 +370,7 @@ s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") return } client, _ := s.auth.AuthorizedClient(r) - pullId, err := db.GetPullId(s.db, f.RepoAt) + pullId, err := db.NextPullId(s.db, f.RepoAt) if err != nil { log.Println("failed to get pull id", err) s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") @@ -604,7 +627,13 @@ log.Println("failed to get repo and knot", err) return } - resp, err := http.Get(fmt.Sprintf("http://%s/%s/%s/branches", f.Knot, f.OwnerDid(), f.RepoName)) + us, err := NewUnsignedClient(f.Knot, s.config.Dev) + if err != nil { + log.Println("failed to create unsigned client", err) + return + } + + resp, err := us.Branches(f.OwnerDid(), f.RepoName) if err != nil { log.Println("failed to reach knotserver", err) return diff --git a/appview/state/signer.go b/appview/state/signer.go --- a/appview/state/signer.go +++ b/appview/state/signer.go @@ -41,11 +41,11 @@ Secret: secret, }, } - uri := "https" + scheme := "https" if dev { - uri = "http" + scheme = "http" } - url, err := url.Parse(fmt.Sprintf("%s://%s", uri, domain)) + url, err := url.Parse(fmt.Sprintf("%s://%s", scheme, domain)) if err != nil { return nil, err } @@ -193,3 +193,67 @@ } return s.client.Do(req) } + +type UnsignedClient struct { + Url *url.URL + client *http.Client +} + +func NewUnsignedClient(domain string, dev bool) (*UnsignedClient, error) { + client := &http.Client{ + Timeout: 5 * time.Second, + } + + scheme := "https" + if dev { + scheme = "http" + } + url, err := url.Parse(fmt.Sprintf("%s://%s", scheme, domain)) + if err != nil { + return nil, err + } + + unsignedClient := &UnsignedClient{ + client: client, + Url: url, + } + + return unsignedClient, nil +} + +func (us *UnsignedClient) newRequest(method, endpoint string, body []byte) (*http.Request, error) { + return http.NewRequest(method, us.Url.JoinPath(endpoint).String(), bytes.NewReader(body)) +} + +func (us *UnsignedClient) Index(ownerDid, repoName, ref string) (*http.Response, error) { + const ( + Method = "GET" + ) + + endpoint := fmt.Sprintf("/%s/%s/tree/%s", ownerDid, repoName, ref) + if ref == "" { + endpoint = fmt.Sprintf("/%s/%s", ownerDid, repoName) + } + + req, err := us.newRequest(Method, endpoint, nil) + if err != nil { + return nil, err + } + + return us.client.Do(req) +} + +func (us *UnsignedClient) Branches(ownerDid, repoName string) (*http.Response, error) { + const ( + Method = "GET" + ) + + endpoint := fmt.Sprintf("/%s/%s/branches", ownerDid, repoName) + + req, err := us.newRequest(Method, endpoint, nil) + if err != nil { + return nil, err + } + + return us.client.Do(req) +}