golang.org/x/playground@v0.0.0-20230418134305-14ebe15bcd59/share.go (about)

     1  // Copyright 2011 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package main
     6  
     7  import (
     8  	"bytes"
     9  	"crypto/sha256"
    10  	"encoding/base64"
    11  	"fmt"
    12  	"io"
    13  	"net/http"
    14  )
    15  
    16  const (
    17  	// This salt is not meant to be kept secret (it’s checked in after all). It’s
    18  	// a tiny bit of paranoia to avoid whatever problems a collision may cause.
    19  	salt = "Go playground salt\n"
    20  
    21  	maxSnippetSize = 64 * 1024
    22  )
    23  
    24  type snippet struct {
    25  	Body []byte `datastore:",noindex"` // golang.org/issues/23253
    26  }
    27  
    28  func (s *snippet) ID() string {
    29  	h := sha256.New()
    30  	io.WriteString(h, salt)
    31  	h.Write(s.Body)
    32  	sum := h.Sum(nil)
    33  	b := make([]byte, base64.URLEncoding.EncodedLen(len(sum)))
    34  	base64.URLEncoding.Encode(b, sum)
    35  	// Web sites don’t always linkify a trailing underscore, making it seem like
    36  	// the link is broken. If there is an underscore at the end of the substring,
    37  	// extend it until there is not.
    38  	hashLen := 11
    39  	for hashLen <= len(b) && b[hashLen-1] == '_' {
    40  		hashLen++
    41  	}
    42  	return string(b)[:hashLen]
    43  }
    44  
    45  func (s *server) handleShare(w http.ResponseWriter, r *http.Request) {
    46  	w.Header().Set("Access-Control-Allow-Origin", "*")
    47  	if r.Method == "OPTIONS" {
    48  		// This is likely a pre-flight CORS request.
    49  		return
    50  	}
    51  	if r.Method != "POST" {
    52  		http.Error(w, "Requires POST", http.StatusMethodNotAllowed)
    53  		return
    54  	}
    55  	if !allowShare(r) {
    56  		http.Error(w, "Either this isn't available in your country due to legal reasons, or our IP geolocation is wrong.",
    57  			http.StatusUnavailableForLegalReasons)
    58  		return
    59  	}
    60  
    61  	var body bytes.Buffer
    62  	_, err := io.Copy(&body, io.LimitReader(r.Body, maxSnippetSize+1))
    63  	r.Body.Close()
    64  	if err != nil {
    65  		s.log.Errorf("reading Body: %v", err)
    66  		http.Error(w, "Server Error", http.StatusInternalServerError)
    67  		return
    68  	}
    69  	if body.Len() > maxSnippetSize {
    70  		http.Error(w, "Snippet is too large", http.StatusRequestEntityTooLarge)
    71  		return
    72  	}
    73  
    74  	snip := &snippet{Body: body.Bytes()}
    75  	id := snip.ID()
    76  	if err := s.db.PutSnippet(r.Context(), id, snip); err != nil {
    77  		s.log.Errorf("putting Snippet: %v", err)
    78  		http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
    79  		return
    80  	}
    81  
    82  	fmt.Fprint(w, id)
    83  }
    84  
    85  func allowShare(r *http.Request) bool {
    86  	if r.Header.Get("X-AppEngine-Country") == "CN" {
    87  		return false
    88  	}
    89  	return true
    90  }