base64Captcha

package module
v0.0.0-...-e1b77b5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 27, 2018 License: Apache-2.0 Imports: 25 Imported by: 0

README

Base64captcha supports digits, numbers, alphabet, arithmetic, audio and digit-alphabet captcha.

Go Report Card GoDoc codecov stability-stable codebeat badge Foundation

Base64captcha supports digit, number, alphabet, arithmetic, audio and digit-alphabet captcha. Base64Captcha is used for fast development of RESTful APIs, web apps and backend services in Go. give a string identifier to the package and it returns with a base64-encoding-png-string

Why Base64 for RESTful Application
  Data URIs are now supported by all major browsers. IE supports embedding images since version 8 as well.
  RESTful Application returns small base64 image is more convenient.
Documentation
Playground Powered by Vuejs+elementUI+Axios

golang base64 captcha package

Quick Start

Download and Install
go get -u github.com/mojocn/base64Captcha

For Gopher from mainland China without VPN go get golang.org/x/image failure solution:

    mkdir -p $GOPATH/src/golang.org/x
    cd $GOPATH/src/golang.org/x
    git clone https://github.com/golang/image.git
Create Captcha Code
import "github.com/mojocn/base64Captcha"
func demoCodeCaptchaCreate() {
	//config struct for digits
	//数字验证码配置
	var configD = base64Captcha.ConfigDigit{
		Height:     80,
		Width:      240,
		MaxSkew:    0.7,
		DotCount:   80,
		CaptchaLen: 5,
	}
	//config struct for audio
	//声音验证码配置
	var configA = base64Captcha.ConfigAudio{
		CaptchaLen: 6,
		Language:   "zh",
	}
	//config struct for Character
	//字符,公式,验证码配置
	var configC = base64Captcha.ConfigCharacter{
		Height:             60,
		Width:              240,
		//const CaptchaModeNumber:数字,CaptchaModeAlphabet:字母,CaptchaModeArithmetic:算术,CaptchaModeNumberAlphabet:数字字母混合.
		Mode:               base64Captcha.CaptchaModeNumber,
		ComplexOfNoiseText: base64Captcha.CaptchaComplexLower,
		ComplexOfNoiseDot:  base64Captcha.CaptchaComplexLower,
		IsUseSimpleFont:    true,
		IsShowHollowLine:   false,
		IsShowNoiseDot:     false,
		IsShowNoiseText:    false,
		IsShowSlimeLine:    false,
		IsShowSineLine:     false,
		CaptchaLen:         6,
	}
	//create a audio captcha.
	//GenerateCaptcha first parameter is empty string,so the package will generate a random uuid for you.
	idKeyA, capA := base64Captcha.GenerateCaptcha("", configA)
	//write to base64 string.
	//GenerateCaptcha first parameter is empty string,so the package will generate a random uuid for you.
	base64stringA := base64Captcha.CaptchaWriteToBase64Encoding(capA)
	//create a characters captcha.
	//GenerateCaptcha first parameter is empty string,so the package will generate a random uuid for you.
	idKeyC, capC := base64Captcha.GenerateCaptcha("", configC)
	//write to base64 string.
	base64stringC := base64Captcha.CaptchaWriteToBase64Encoding(capC)
	//create a digits captcha.
	idKeyD, capD := base64Captcha.GenerateCaptcha("", configD)
	//write to base64 string.
	base64stringD := base64Captcha.CaptchaWriteToBase64Encoding(capD)

	fmt.Println(idKeyA, base64stringA, "\n")
	fmt.Println(idKeyC, base64stringC, "\n")
	fmt.Println(idKeyD, base64stringD, "\n")
}

write the captcha content of CaptchaInterfaceInstance to the httpResponseWriter.
func captchaWriterToHttpResponseWriterDemoHandler(w http.ResponseWriter, r *http.Request) {
	//config struct for Character
	var config = base64Captcha.ConfigCharacter{
		Height:             60,
		Width:              240,
		//const CaptchaModeNumber:数字,CaptchaModeAlphabet:字母,CaptchaModeArithmetic:算术,CaptchaModeNumberAlphabet:数字字母混合.
		Mode:               base64Captcha.CaptchaModeNumber,
		ComplexOfNoiseText: base64Captcha.CaptchaComplexLower,
		ComplexOfNoiseDot:  base64Captcha.CaptchaComplexLower,
		IsUseSimpleFont:    true,
		IsShowHollowLine:   false,
		IsShowNoiseDot:     false,
		IsShowNoiseText:    false,
		IsShowSlimeLine:    false,
		IsShowSineLine:     false,
		CaptchaLen:         6,
	}
	//create a captchaInterface instance.
	//GenerateCaptcha first parameter is empty string,so the package will generate a random uuid for you.
	idkey, captchaInterfaceIntance := base64Captcha.GenerateCaptcha("", config)

	//write the httpResponseCookie with idKey.
	//https://golang.org/pkg/net/http/#SetCookie

	//write the content of captcha interface instance to httpResponseWriter.
	n,err := captchaInterfaceIntance.WriteTo(w)
	ftm.Println(n,err)
}
Verify Captcha Code
import "github.com/mojocn/base64Captcha"
func verfiyCaptcha(idkey,verifyValue string){
    verifyResult := base64Captcha.VerifyCaptcha(idkey, verifyValue)
    if verifyResult {
        //success
    } else {
        //fail
    }
}
func SetCustomStore
func SetCustomStore(s Store)

SetCustomStore sets custom storage for captchas, replacing the default memory store. This function must be called before generating any captchas.

func NewMemoryStore
func NewMemoryStore(collectNum int, expiration time.Duration) Store

NewMemoryStore returns a new standard memory store for captchas with the given collection threshold and expiration time in seconds. The returned store must be registered with SetCustomStore to replace the default one.

use base64Captcha quick start a API server

// example of HTTP server that uses the captcha package.
package main

import (
	"encoding/json"
	"fmt"
	"github.com/mojocn/base64Captcha"
	"log"
	"net/http"
)

//ConfigJsonBody json request body.
type ConfigJsonBody struct {
	Id              string
	CaptchaType     string
	VerifyValue     string
	ConfigAudio     base64Captcha.ConfigAudio
	ConfigCharacter base64Captcha.ConfigCharacter
	ConfigDigit     base64Captcha.ConfigDigit
}

// base64Captcha create http handler
func generateCaptchaHandler(w http.ResponseWriter, r *http.Request) {
	//parse request parameters
	//接收客户端发送来的请求参数
	decoder := json.NewDecoder(r.Body)
	var postParameters ConfigJsonBody
	err := decoder.Decode(&postParameters)
	if err != nil {
		log.Println(err)
	}
	defer r.Body.Close()

	//create base64 encoding captcha

	var config interface{}
	switch postParameters.CaptchaType {
	case "audio":
		config = postParameters.ConfigAudio
	case "character":
		config = postParameters.ConfigCharacter
	default:
		config = postParameters.ConfigDigit
	}
	captchaId, captcaInterfaceInstance := base64Captcha.GenerateCaptcha(postParameters.Id, config)
	base64blob := base64Captcha.CaptchaWriteToBase64Encoding(captcaInterfaceInstance)

	//or you can just write the captcha content to the httpResponseWriter.
	//before you put the captchaId into the response COOKIE.
	//captcaInterfaceInstance.WriteTo(w)

	//set json response
	w.Header().Set("Content-Type", "application/json; charset=utf-8")
	body := map[string]interface{}{"code": 1, "data": base64blob, "captchaId": captchaId, "msg": "success"}
	json.NewEncoder(w).Encode(body)
}
// base64Captcha verify http handler
func captchaVerifyHandle(w http.ResponseWriter, r *http.Request) {

	//parse request parameters
	//接收客户端发送来的请求参数
	decoder := json.NewDecoder(r.Body)
	var postParameters ConfigJsonBody
	err := decoder.Decode(&postParameters)
	if err != nil {
		log.Println(err)
	}
	defer r.Body.Close()
	//verify the captcha
	//比较图像验证码
	verifyResult := base64Captcha.VerifyCaptcha(postParameters.Id, postParameters.VerifyValue)

	//set json response
	//设置json响应
	w.Header().Set("Content-Type", "application/json; charset=utf-8")
	body := map[string]interface{}{"code": "error", "data": "验证失败", "msg": "captcha failed"}
	if verifyResult {
		body = map[string]interface{}{"code": "success", "data": "验证通过", "msg": "captcha verified"}
	}
	json.NewEncoder(w).Encode(body)
}

//start a net/http server
//启动golang net/http 服务器
func main() {

	//serve Vuejs+ElementUI+Axios Web Application
	http.Handle("/", http.FileServer(http.Dir("./static")))

	//api for create captcha
	http.HandleFunc("/api/getCaptcha", generateCaptchaHandler)

	//api for verify captcha
	http.HandleFunc("/api/verifyCaptcha", captchaVerifyHandle)

	fmt.Println("Server is at localhost:3333")
	if err := http.ListenAndServe("localhost:3333", nil); err != nil {
		log.Fatal(err)
	}
}
base64Captcha package function
  • ConfigAudio captcha config for captcha-engine-audio.
    type ConfigAudio struct {
    	// CaptchaLen Default number of digits in captcha solution.
    	CaptchaLen int
    	// Language possible values for lang are "en", "ja", "ru", "zh".
    	Language string
    }
    
  • ConfigDigit config for captcha-engine-digit.
    type ConfigDigit struct {
    
        // Height png height in pixel.
        // 图像验证码的高度像素.
        Height int
        // Width Captcha png width in pixel.
        // 图像验证码的宽度像素
        Width int
        // DefaultLen Default number of digits in captcha solution.
        // 默认数字验证长度6.
        CaptchaLen int
        // MaxSkew max absolute skew factor of a single digit.
        // 图像验证码的最大干扰洗漱.
        MaxSkew float64
        // DotCount Number of background circles.
        // 图像验证码干扰圆点的数量.
        DotCount int
    }
    
  • ConfigCharacter captcha config for captcha-engine-characters.
    type ConfigCharacter struct {
        // Height png height in pixel.
        // 图像验证码的高度像素.
        Height int
        // Width Captcha png width in pixel.
        // 图像验证码的宽度像素
        Width int
        //Mode : base64captcha.CaptchaModeNumber=0, base64captcha.CaptchaModeAlphabet=1, base64captcha.CaptchaModeArithmetic=2, base64captcha.CaptchaModeNumberAlphabet=3.
        Mode int
        //ComplexOfNoiseText text noise count.
        ComplexOfNoiseText int
        //ComplexOfNoiseDot dot noise count.
        ComplexOfNoiseDot int
        //IsUseSimpleFont is only use this (...fonts/RitaSmith.ttf)font.
        IsUseSimpleFont bool
        //IsShowHollowLine is show hollow line.
        IsShowHollowLine bool
        //IsShowNoiseDot is show noise dot.
        IsShowNoiseDot bool
        //IsShowNoiseText is show noise text.
        IsShowNoiseText bool
        //IsShowSlimeLine is show slime line.
        IsShowSlimeLine bool
        //IsShowSineLine is show sine line.
        IsShowSineLine bool
        // CaptchaLen Default number of digits in captcha solution.
        // 默认数字验证长度6.
        CaptchaLen int
    }
    
  • CaptchaInterface
    type CaptchaInterface interface {
    	//BinaryEncodeing covert to bytes
    	BinaryEncodeing() []byte
    	//WriteTo output captcha entity
    	WriteTo(w io.Writer) (n int64, err error)
    }
    
  • func GenerateCaptcha(idKey string, configuration interface{}) (id string, captchaInstance CaptchaInterface) return CaptchaInterface instance.
  • func CaptchaWriteToBase64Encoding(cap CaptchaInterface) string captcha base64 encodeing.
  • func VerifyCaptcha(identifier, verifyValue string) bool verify the captcha content by identifierKey
  • func RandomId() string Server Create Random IdentifierKey
Build and Run the Demo
cd $GOPATH/src/github.com/mojocn/captcha/examples
go run main.go
demo nginx configuration captcha.mojotv.cn.config
server {
        listen 80;
        server_name captcha.mojotv.cn;
        charset utf-8;

        location / {
            try_files /_not_exists_ @backend;
        }
        location @backend {
           proxy_set_header X-Forwarded-For $remote_addr;
           pro=xy_set_header Host $http_host;
           proxy_pass http://127.0.0.1:3333;
        }
        access_log  /home/wwwlogs/captcha.mojotv.cn.log;
}
Go to http://localhost:777

Congratulations! You've just built your first base64Captcha-APIs app. Any question you can leave a message. If you like the package please star this repo

License

base64Captcha source code is licensed under the Apache Licence, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0.html).

Documentation

Overview

Package base64Captcha supports digits, numbers,alphabet, arithmetic, audio and digit-alphabet captcha. base64Captcha is used for fast development of RESTful APIs, web apps and backend services in Go. give a string identifier to the package and it returns with a base64-encoding-png-string

Index

Constants

View Source
const (

	//TxtNumbers chacters for numbers.
	TxtNumbers = "012346789"
	//TxtAlphabet characters for alphabet.
	TxtAlphabet = "ABCDEFGHJKMNOQRSTUVXYZabcdefghjkmnoqrstuvxyz"
	//TxtSimpleCharaters simple numbers and alphabet
	TxtSimpleCharaters = "13467ertyiadfhjkxcvbnERTYADFGHJKXCVBN"

	//MimeTypeCaptchaAudio output base64 mine-type.
	MimeTypeCaptchaAudio = "audio/wav"
	//MimeTypeCaptchaImage output base64 mine-type.
	MimeTypeCaptchaImage = "image/png"

	//FileExtCaptchaAudio output file extension.
	FileExtCaptchaAudio = "wav"
	//FileExtCaptchaImage output file extension.
	FileExtCaptchaImage = "png"
)
View Source
const (
	//CaptchaComplexLower complex level lower.
	CaptchaComplexLower = iota
	//CaptchaComplexMedium complex level medium.
	CaptchaComplexMedium
	//CaptchaComplexHigh complex level high.
	CaptchaComplexHigh
)
View Source
const (
	//CaptchaModeNumber mode number.
	CaptchaModeNumber = iota
	//CaptchaModeAlphabet mode alphabet.
	CaptchaModeAlphabet
	//CaptchaModeArithmetic mode arithmetic.
	CaptchaModeArithmetic
	//CaptchaModeNumberAlphabet mode mix number and alphabet,this is also default mode.
	CaptchaModeNumberAlphabet
)
View Source
const (
	// DefaultLen Default number of digits in captcha solution.
	// 默认数字验证长度.
	DefaultLen = 6
	// MaxSkew max absolute skew factor of a single digit.
	// 图像验证码的最大干扰洗漱.
	MaxSkew = 0.7
	// DotCount Number of background circles.
	// 图像验证码干扰圆点的数量.
	DotCount = 20
)

Variables

View Source
var (
	// GCLimitNumber The number of captchas created that triggers garbage collection used by default store.
	// 默认图像验证GC清理的上限个数
	GCLimitNumber = 10240
	// Expiration time of captchas used by default store.
	// 内存保存验证码的时限
	Expiration = 10 * time.Minute
)
View Source
var GoTestOutputDir = "/Users/ericzhou/go/src/github.com/mojocn/base64Captcha/goTestOutPutPng"

GoTestOutputDir run go test command where the png and wav file output

Functions

func Asset

func Asset(name string) ([]byte, error)

Asset loads and returns the asset for the given name. It returns an error if the asset could not be found or could not be loaded.

func CaptchaWriteToBase64Encoding

func CaptchaWriteToBase64Encoding(cap CaptchaInterface) string

CaptchaWriteToBase64Encoding converts captcha to base64 encoding string. mimeType is one of "audio/wav" "image/png".

func CaptchaWriteToFile

func CaptchaWriteToFile(cap CaptchaInterface, outputDir, fileName, fileExt string) error

CaptchaWriteToFile output captcha to file. fileExt is one of "png","wav"

func SetCustomStore

func SetCustomStore(s Store)

SetCustomStore sets custom storage for captchas, replacing the default memory store. This function must be called before generating any captchas.

func VerifyCaptcha

func VerifyCaptcha(identifier, verifyValue string) bool

VerifyCaptcha by given id key and remove the captcha value in store, return boolean value. 验证图像验证码,返回boolean.

func VerifyCaptchaAndIsClear

func VerifyCaptchaAndIsClear(identifier, verifyValue string, isClear bool) bool

VerifyCaptchaAndIsClear verify captcha, return boolean value. identifier is the captcha id, verifyValue is the captcha image value, isClear is whether to clear the value in store. 验证图像验证码,返回boolean.

Types

type Audio

type Audio struct {
	CaptchaItem
	// contains filtered or unexported fields
}

Audio captcha-audio-engine return type.

func EngineAudioCreate

func EngineAudioCreate(id string, config ConfigAudio) *Audio

EngineAudioCreate create captcha with configAudio.

func (*Audio) BinaryEncodeing

func (a *Audio) BinaryEncodeing() []byte

BinaryEncodeing encodes an sound to wave and returns the result as a byte slice.

func (*Audio) WriteTo

func (a *Audio) WriteTo(w io.Writer) (n int64, err error)

WriteTo writes captcha audio in WAVE format into the given io.Writer, and returns the number of bytes written and an error if any.

type CaptchaImageChar

type CaptchaImageChar struct {
	CaptchaItem

	Complex int
	// contains filtered or unexported fields
}

CaptchaImageChar captcha-engine-char return type.

func EngineCharCreate

func EngineCharCreate(config ConfigCharacter) *CaptchaImageChar

EngineCharCreate create captcha with config struct.

func (*CaptchaImageChar) BinaryEncodeing

func (captcha *CaptchaImageChar) BinaryEncodeing() []byte

BinaryEncodeing save captcha image to binary. 保存图片到io.

func (*CaptchaImageChar) WriteTo

func (captcha *CaptchaImageChar) WriteTo(w io.Writer) (int64, error)

WriteTo writes captcha image in PNG format into the given writer.

type CaptchaImageDigit

type CaptchaImageDigit struct {
	CaptchaItem
	*image.Paletted
	// contains filtered or unexported fields
}

CaptchaImageDigit digits captcha Struct

func EngineDigitsCreate

func EngineDigitsCreate(id string, config ConfigDigit) *CaptchaImageDigit

EngineDigitsCreate create captcha by engine-digits with configuration.

func (*CaptchaImageDigit) BinaryEncodeing

func (m *CaptchaImageDigit) BinaryEncodeing() []byte

BinaryEncodeing encodes an image to PNG and returns the result as a byte slice.

func (*CaptchaImageDigit) WriteTo

func (m *CaptchaImageDigit) WriteTo(w io.Writer) (int64, error)

WriteTo writes captcha image in PNG format into the given writer.

type CaptchaInterface

type CaptchaInterface interface {
	//BinaryEncodeing covert to bytes
	BinaryEncodeing() []byte
	//WriteTo output captcha entity
	WriteTo(w io.Writer) (n int64, err error)
}

CaptchaInterface captcha interface for captcha engine to to write staff

func GenerateCaptcha

func GenerateCaptcha(idKey string, configuration interface{}) (id string, captchaInstance CaptchaInterface)

GenerateCaptcha create captcha by config struct and id. idkey can be an empty string, base64 will create a unique id four you. if idKey is a empty string, the package will generate a random unique identifier for you. configuration struct should be one of those struct ConfigAudio, ConfigCharacter, ConfigDigit.

Example Code

//config struct for digits
var configD = base64Captcha.ConfigDigit{
	Height:     80,
	Width:      240,
	MaxSkew:    0.7,
	DotCount:   80,
	CaptchaLen: 5,
}
//config struct for audio
var configA = base64Captcha.ConfigAudio{
	CaptchaLen: 6,
	Language:   "zh",
}
//config struct for Character
var configC = base64Captcha.ConfigCharacter{
	Height:             60,
	Width:              240,
	//const CaptchaModeNumber:数字,CaptchaModeAlphabet:字母,CaptchaModeArithmetic:算术,CaptchaModeNumberAlphabet:数字字母混合.
	Mode:               base64Captcha.CaptchaModeNumber,
	ComplexOfNoiseText: base64Captcha.CaptchaComplexLower,
	ComplexOfNoiseDot:  base64Captcha.CaptchaComplexLower,
	IsUseSimpleFont:    true,
	IsShowHollowLine:   false,
	IsShowNoiseDot:     false,
	IsShowNoiseText:    false,
	IsShowSlimeLine:    false,
	IsShowSineLine:     false,
	CaptchaLen:         6,
}
//create a audio captcha.
//GenerateCaptcha first parameter is empty string,so the package will generate a random uuid for you.
idKeyA,capA := base64Captcha.GenerateCaptcha("",configA)
//write to base64 string.
//GenerateCaptcha first parameter is empty string,so the package will generate a random uuid for you.
base64stringA := base64Captcha.CaptchaWriteToBase64Encoding(capA)
//create a characters captcha.
//GenerateCaptcha first parameter is empty string,so the package will generate a random uuid for you.
idKeyC,capC := base64Captcha.GenerateCaptcha("",configC)
//write to base64 string.
base64stringC := base64Captcha.CaptchaWriteToBase64Encoding(capC)
//create a digits captcha.
idKeyD,capD := base64Captcha.GenerateCaptcha("",configD)
//write to base64 string.
base64stringD := base64Captcha.CaptchaWriteToBase64Encoding(capD)

type CaptchaItem

type CaptchaItem struct {
	//Content captcha entity content.
	Content string
	//VerifyValue captcha verify value.
	VerifyValue string
	//ImageWidth image width pixel.
	ImageWidth int
	//ImageHeight image height pixel.
	ImageHeight int
}

CaptchaItem captcha basic information.

type ConfigAudio

type ConfigAudio struct {
	// CaptchaLen Default number of digits in captcha solution.
	CaptchaLen int
	// Language possible values for lang are "en", "ja", "ru", "zh".
	Language string
}

ConfigAudio captcha config for captcha-engine-audio.

type ConfigCharacter

type ConfigCharacter struct {
	// Height png height in pixel.
	// 图像验证码的高度像素.
	Height int
	// Width Captcha png width in pixel.
	// 图像验证码的宽度像素
	Width int
	//Mode : base64captcha.CaptchaModeNumber=0, base64captcha.CaptchaModeAlphabet=1, base64captcha.CaptchaModeArithmetic=2, base64captcha.CaptchaModeNumberAlphabet=3.
	Mode int
	//IsUseSimpleFont is use simply font(...base64Captcha/fonts/RitaSmith.ttf).
	IsUseSimpleFont bool
	//ComplexOfNoiseText text noise count.
	ComplexOfNoiseText int
	//ComplexOfNoiseDot dot noise count.
	ComplexOfNoiseDot int
	//IsShowHollowLine is show hollow line.
	IsShowHollowLine bool
	//IsShowNoiseDot is show noise dot.
	IsShowNoiseDot bool
	//IsShowNoiseText is show noise text.
	IsShowNoiseText bool
	//IsShowSlimeLine is show slime line.
	IsShowSlimeLine bool
	//IsShowSineLine is show sine line.
	IsShowSineLine bool

	// CaptchaLen Default number of digits in captcha solution.
	// 默认数字验证长度6.
	CaptchaLen int
}

ConfigCharacter captcha config for captcha-engine-characters.

type ConfigDigit

type ConfigDigit struct {

	// Height png height in pixel.
	// 图像验证码的高度像素.
	Height int
	// Width Captcha png width in pixel.
	// 图像验证码的宽度像素
	Width int
	// DefaultLen Default number of digits in captcha solution.
	// 默认数字验证长度6.
	CaptchaLen int
	// MaxSkew max absolute skew factor of a single digit.
	// 图像验证码的最大干扰洗漱.
	MaxSkew float64
	// DotCount Number of background circles.
	// 图像验证码干扰圆点的数量.
	DotCount int
}

ConfigDigit config for captcha-engine-digit.

type Store

type Store interface {
	// Set sets the digits for the captcha id.
	Set(id string, value string)

	// Get returns stored digits for the captcha id. Clear indicates
	// whether the captcha must be deleted from the store.
	Get(id string, clear bool) string
}

Store An object implementing Store interface can be registered with SetCustomStore function to handle storage and retrieval of captcha ids and solutions for them, replacing the default memory store.

It is the responsibility of an object to delete expired and used captchas when necessary (for example, the default memory store collects them in Set method after the certain amount of captchas has been stored.)

func NewMemoryStore

func NewMemoryStore(collectNum int, expiration time.Duration) Store

NewMemoryStore returns a new standard memory store for captchas with the given collection threshold and expiration time (duration). The returned store must be registered with SetCustomStore to replace the default one.

Directories

Path Synopsis
example of HTTP server that uses the captcha package.
example of HTTP server that uses the captcha package.
example of HTTP server that uses the captcha package.
example of HTTP server that uses the captcha package.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL