github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/x/net/http2/h2i/h2i.go (about)

     1  // Copyright 2015 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  /*
     6  The h2i command is an interactive HTTP/2 console.
     7  
     8  Usage:
     9    $ h2i [flags] <hostname>
    10  
    11  Interactive commands in the console: (all parts case-insensitive)
    12  
    13    ping [data]
    14    settings ack
    15    settings FOO=n BAR=z
    16    headers      (open a new stream by typing HTTP/1.1)
    17  */
    18  package main
    19  
    20  import (
    21  	"bufio"
    22  	"bytes"
    23  	"crypto/tls"
    24  	"errors"
    25  	"flag"
    26  	"fmt"
    27  	"io"
    28  	"log"
    29  	"net"
    30  	"net/http"
    31  	"os"
    32  	"regexp"
    33  	"strconv"
    34  	"strings"
    35  
    36  	"golang.org/x/crypto/ssh/terminal"
    37  	"golang.org/x/net/http2"
    38  	"golang.org/x/net/http2/hpack"
    39  )
    40  
    41  // Flags
    42  var (
    43  	flagNextProto = flag.String("nextproto", "h2,h2-14", "Comma-separated list of NPN/ALPN protocol names to negotiate.")
    44  	flagInsecure  = flag.Bool("insecure", false, "Whether to skip TLS cert validation")
    45  	flagSettings  = flag.String("settings", "empty", "comma-separated list of KEY=value settings for the initial SETTINGS frame. The magic value 'empty' sends an empty initial settings frame, and the magic value 'omit' causes no initial settings frame to be sent.")
    46  )
    47  
    48  type command struct {
    49  	run func(*h2i, []string) error // required
    50  
    51  	// complete optionally specifies tokens (case-insensitive) which are
    52  	// valid for this subcommand.
    53  	complete func() []string
    54  }
    55  
    56  var commands = map[string]command{
    57  	"ping": command{run: (*h2i).cmdPing},
    58  	"settings": command{
    59  		run: (*h2i).cmdSettings,
    60  		complete: func() []string {
    61  			return []string{
    62  				"ACK",
    63  				http2.SettingHeaderTableSize.String(),
    64  				http2.SettingEnablePush.String(),
    65  				http2.SettingMaxConcurrentStreams.String(),
    66  				http2.SettingInitialWindowSize.String(),
    67  				http2.SettingMaxFrameSize.String(),
    68  				http2.SettingMaxHeaderListSize.String(),
    69  			}
    70  		},
    71  	},
    72  	"quit":    command{run: (*h2i).cmdQuit},
    73  	"headers": command{run: (*h2i).cmdHeaders},
    74  }
    75  
    76  func usage() {
    77  	fmt.Fprintf(os.Stderr, "Usage: h2i <hostname>\n\n")
    78  	flag.PrintDefaults()
    79  	os.Exit(1)
    80  }
    81  
    82  // withPort adds ":443" if another port isn't already present.
    83  func withPort(host string) string {
    84  	if _, _, err := net.SplitHostPort(host); err != nil {
    85  		return net.JoinHostPort(host, "443")
    86  	}
    87  	return host
    88  }
    89  
    90  // h2i is the app's state.
    91  type h2i struct {
    92  	host   string
    93  	tc     *tls.Conn
    94  	framer *http2.Framer
    95  	term   *terminal.Terminal
    96  
    97  	// owned by the command loop:
    98  	streamID uint32
    99  	hbuf     bytes.Buffer
   100  	henc     *hpack.Encoder
   101  
   102  	// owned by the readFrames loop:
   103  	peerSetting map[http2.SettingID]uint32
   104  	hdec        *hpack.Decoder
   105  }
   106  
   107  func main() {
   108  	flag.Usage = usage
   109  	flag.Parse()
   110  	if flag.NArg() != 1 {
   111  		usage()
   112  	}
   113  	log.SetFlags(0)
   114  
   115  	host := flag.Arg(0)
   116  	app := &h2i{
   117  		host:        host,
   118  		peerSetting: make(map[http2.SettingID]uint32),
   119  	}
   120  	app.henc = hpack.NewEncoder(&app.hbuf)
   121  
   122  	if err := app.Main(); err != nil {
   123  		if app.term != nil {
   124  			app.logf("%v\n", err)
   125  		} else {
   126  			fmt.Fprintf(os.Stderr, "%v\n", err)
   127  		}
   128  		os.Exit(1)
   129  	}
   130  	fmt.Fprintf(os.Stdout, "\n")
   131  }
   132  
   133  func (app *h2i) Main() error {
   134  	cfg := &tls.Config{
   135  		ServerName:         app.host,
   136  		NextProtos:         strings.Split(*flagNextProto, ","),
   137  		InsecureSkipVerify: *flagInsecure,
   138  	}
   139  
   140  	hostAndPort := withPort(app.host)
   141  	log.Printf("Connecting to %s ...", hostAndPort)
   142  	tc, err := tls.Dial("tcp", hostAndPort, cfg)
   143  	if err != nil {
   144  		return fmt.Errorf("Error dialing %s: %v", withPort(app.host), err)
   145  	}
   146  	log.Printf("Connected to %v", tc.RemoteAddr())
   147  	defer tc.Close()
   148  
   149  	if err := tc.Handshake(); err != nil {
   150  		return fmt.Errorf("TLS handshake: %v", err)
   151  	}
   152  	if !*flagInsecure {
   153  		if err := tc.VerifyHostname(app.host); err != nil {
   154  			return fmt.Errorf("VerifyHostname: %v", err)
   155  		}
   156  	}
   157  	state := tc.ConnectionState()
   158  	log.Printf("Negotiated protocol %q", state.NegotiatedProtocol)
   159  	if !state.NegotiatedProtocolIsMutual || state.NegotiatedProtocol == "" {
   160  		return fmt.Errorf("Could not negotiate protocol mutually")
   161  	}
   162  
   163  	if _, err := io.WriteString(tc, http2.ClientPreface); err != nil {
   164  		return err
   165  	}
   166  
   167  	app.framer = http2.NewFramer(tc, tc)
   168  
   169  	oldState, err := terminal.MakeRaw(0)
   170  	if err != nil {
   171  		return err
   172  	}
   173  	defer terminal.Restore(0, oldState)
   174  
   175  	var screen = struct {
   176  		io.Reader
   177  		io.Writer
   178  	}{os.Stdin, os.Stdout}
   179  
   180  	app.term = terminal.NewTerminal(screen, "h2i> ")
   181  	lastWord := regexp.MustCompile(`.+\W(\w+)$`)
   182  	app.term.AutoCompleteCallback = func(line string, pos int, key rune) (newLine string, newPos int, ok bool) {
   183  		if key != '\t' {
   184  			return
   185  		}
   186  		if pos != len(line) {
   187  			// TODO: we're being lazy for now, only supporting tab completion at the end.
   188  			return
   189  		}
   190  		// Auto-complete for the command itself.
   191  		if !strings.Contains(line, " ") {
   192  			var name string
   193  			name, _, ok = lookupCommand(line)
   194  			if !ok {
   195  				return
   196  			}
   197  			return name, len(name), true
   198  		}
   199  		_, c, ok := lookupCommand(line[:strings.IndexByte(line, ' ')])
   200  		if !ok || c.complete == nil {
   201  			return
   202  		}
   203  		if strings.HasSuffix(line, " ") {
   204  			app.logf("%s", strings.Join(c.complete(), " "))
   205  			return line, pos, true
   206  		}
   207  		m := lastWord.FindStringSubmatch(line)
   208  		if m == nil {
   209  			return line, len(line), true
   210  		}
   211  		soFar := m[1]
   212  		var match []string
   213  		for _, cand := range c.complete() {
   214  			if len(soFar) > len(cand) || !strings.EqualFold(cand[:len(soFar)], soFar) {
   215  				continue
   216  			}
   217  			match = append(match, cand)
   218  		}
   219  		if len(match) == 0 {
   220  			return
   221  		}
   222  		if len(match) > 1 {
   223  			// TODO: auto-complete any common prefix
   224  			app.logf("%s", strings.Join(match, " "))
   225  			return line, pos, true
   226  		}
   227  		newLine = line[:len(line)-len(soFar)] + match[0]
   228  		return newLine, len(newLine), true
   229  
   230  	}
   231  
   232  	errc := make(chan error, 2)
   233  	go func() { errc <- app.readFrames() }()
   234  	go func() { errc <- app.readConsole() }()
   235  	return <-errc
   236  }
   237  
   238  func (app *h2i) logf(format string, args ...interface{}) {
   239  	fmt.Fprintf(app.term, format+"\n", args...)
   240  }
   241  
   242  func (app *h2i) readConsole() error {
   243  	if s := *flagSettings; s != "omit" {
   244  		var args []string
   245  		if s != "empty" {
   246  			args = strings.Split(s, ",")
   247  		}
   248  		_, c, ok := lookupCommand("settings")
   249  		if !ok {
   250  			panic("settings command not found")
   251  		}
   252  		c.run(app, args)
   253  	}
   254  
   255  	for {
   256  		line, err := app.term.ReadLine()
   257  		if err == io.EOF {
   258  			return nil
   259  		}
   260  		if err != nil {
   261  			return fmt.Errorf("terminal.ReadLine: %v", err)
   262  		}
   263  		f := strings.Fields(line)
   264  		if len(f) == 0 {
   265  			continue
   266  		}
   267  		cmd, args := f[0], f[1:]
   268  		if _, c, ok := lookupCommand(cmd); ok {
   269  			err = c.run(app, args)
   270  		} else {
   271  			app.logf("Unknown command %q", line)
   272  		}
   273  		if err == errExitApp {
   274  			return nil
   275  		}
   276  		if err != nil {
   277  			return err
   278  		}
   279  	}
   280  }
   281  
   282  func lookupCommand(prefix string) (name string, c command, ok bool) {
   283  	prefix = strings.ToLower(prefix)
   284  	if c, ok = commands[prefix]; ok {
   285  		return prefix, c, ok
   286  	}
   287  
   288  	for full, candidate := range commands {
   289  		if strings.HasPrefix(full, prefix) {
   290  			if c.run != nil {
   291  				return "", command{}, false // ambiguous
   292  			}
   293  			c = candidate
   294  			name = full
   295  		}
   296  	}
   297  	return name, c, c.run != nil
   298  }
   299  
   300  var errExitApp = errors.New("internal sentinel error value to quit the console reading loop")
   301  
   302  func (a *h2i) cmdQuit(args []string) error {
   303  	if len(args) > 0 {
   304  		a.logf("the QUIT command takes no argument")
   305  		return nil
   306  	}
   307  	return errExitApp
   308  }
   309  
   310  func (a *h2i) cmdSettings(args []string) error {
   311  	if len(args) == 1 && strings.EqualFold(args[0], "ACK") {
   312  		return a.framer.WriteSettingsAck()
   313  	}
   314  	var settings []http2.Setting
   315  	for _, arg := range args {
   316  		if strings.EqualFold(arg, "ACK") {
   317  			a.logf("Error: ACK must be only argument with the SETTINGS command")
   318  			return nil
   319  		}
   320  		eq := strings.Index(arg, "=")
   321  		if eq == -1 {
   322  			a.logf("Error: invalid argument %q (expected SETTING_NAME=nnnn)", arg)
   323  			return nil
   324  		}
   325  		sid, ok := settingByName(arg[:eq])
   326  		if !ok {
   327  			a.logf("Error: unknown setting name %q", arg[:eq])
   328  			return nil
   329  		}
   330  		val, err := strconv.ParseUint(arg[eq+1:], 10, 32)
   331  		if err != nil {
   332  			a.logf("Error: invalid argument %q (expected SETTING_NAME=nnnn)", arg)
   333  			return nil
   334  		}
   335  		settings = append(settings, http2.Setting{
   336  			ID:  sid,
   337  			Val: uint32(val),
   338  		})
   339  	}
   340  	a.logf("Sending: %v", settings)
   341  	return a.framer.WriteSettings(settings...)
   342  }
   343  
   344  func settingByName(name string) (http2.SettingID, bool) {
   345  	for _, sid := range [...]http2.SettingID{
   346  		http2.SettingHeaderTableSize,
   347  		http2.SettingEnablePush,
   348  		http2.SettingMaxConcurrentStreams,
   349  		http2.SettingInitialWindowSize,
   350  		http2.SettingMaxFrameSize,
   351  		http2.SettingMaxHeaderListSize,
   352  	} {
   353  		if strings.EqualFold(sid.String(), name) {
   354  			return sid, true
   355  		}
   356  	}
   357  	return 0, false
   358  }
   359  
   360  func (app *h2i) cmdPing(args []string) error {
   361  	if len(args) > 1 {
   362  		app.logf("invalid PING usage: only accepts 0 or 1 args")
   363  		return nil // nil means don't end the program
   364  	}
   365  	var data [8]byte
   366  	if len(args) == 1 {
   367  		copy(data[:], args[0])
   368  	} else {
   369  		copy(data[:], "h2i_ping")
   370  	}
   371  	return app.framer.WritePing(false, data)
   372  }
   373  
   374  func (app *h2i) cmdHeaders(args []string) error {
   375  	if len(args) > 0 {
   376  		app.logf("Error: HEADERS doesn't yet take arguments.")
   377  		// TODO: flags for restricting window size, to force CONTINUATION
   378  		// frames.
   379  		return nil
   380  	}
   381  	var h1req bytes.Buffer
   382  	app.term.SetPrompt("(as HTTP/1.1)> ")
   383  	defer app.term.SetPrompt("h2i> ")
   384  	for {
   385  		line, err := app.term.ReadLine()
   386  		if err != nil {
   387  			return err
   388  		}
   389  		h1req.WriteString(line)
   390  		h1req.WriteString("\r\n")
   391  		if line == "" {
   392  			break
   393  		}
   394  	}
   395  	req, err := http.ReadRequest(bufio.NewReader(&h1req))
   396  	if err != nil {
   397  		app.logf("Invalid HTTP/1.1 request: %v", err)
   398  		return nil
   399  	}
   400  	if app.streamID == 0 {
   401  		app.streamID = 1
   402  	} else {
   403  		app.streamID += 2
   404  	}
   405  	app.logf("Opening Stream-ID %d:", app.streamID)
   406  	hbf := app.encodeHeaders(req)
   407  	if len(hbf) > 16<<10 {
   408  		app.logf("TODO: h2i doesn't yet write CONTINUATION frames. Copy it from transport.go")
   409  		return nil
   410  	}
   411  	return app.framer.WriteHeaders(http2.HeadersFrameParam{
   412  		StreamID:      app.streamID,
   413  		BlockFragment: hbf,
   414  		EndStream:     req.Method == "GET" || req.Method == "HEAD", // good enough for now
   415  		EndHeaders:    true,                                        // for now
   416  	})
   417  }
   418  
   419  func (app *h2i) readFrames() error {
   420  	for {
   421  		f, err := app.framer.ReadFrame()
   422  		if err != nil {
   423  			return fmt.Errorf("ReadFrame: %v", err)
   424  		}
   425  		app.logf("%v", f)
   426  		switch f := f.(type) {
   427  		case *http2.PingFrame:
   428  			app.logf("  Data = %q", f.Data)
   429  		case *http2.SettingsFrame:
   430  			f.ForeachSetting(func(s http2.Setting) error {
   431  				app.logf("  %v", s)
   432  				app.peerSetting[s.ID] = s.Val
   433  				return nil
   434  			})
   435  		case *http2.WindowUpdateFrame:
   436  			app.logf("  Window-Increment = %v\n", f.Increment)
   437  		case *http2.GoAwayFrame:
   438  			app.logf("  Last-Stream-ID = %d; Error-Code = %v (%d)\n", f.LastStreamID, f.ErrCode, f.ErrCode)
   439  		case *http2.DataFrame:
   440  			app.logf("  %q", f.Data())
   441  		case *http2.HeadersFrame:
   442  			if f.HasPriority() {
   443  				app.logf("  PRIORITY = %v", f.Priority)
   444  			}
   445  			if app.hdec == nil {
   446  				// TODO: if the user uses h2i to send a SETTINGS frame advertising
   447  				// something larger, we'll need to respect SETTINGS_HEADER_TABLE_SIZE
   448  				// and stuff here instead of using the 4k default. But for now:
   449  				tableSize := uint32(4 << 10)
   450  				app.hdec = hpack.NewDecoder(tableSize, app.onNewHeaderField)
   451  			}
   452  			app.hdec.Write(f.HeaderBlockFragment())
   453  		}
   454  	}
   455  }
   456  
   457  // called from readLoop
   458  func (app *h2i) onNewHeaderField(f hpack.HeaderField) {
   459  	if f.Sensitive {
   460  		app.logf("  %s = %q (SENSITIVE)", f.Name, f.Value)
   461  	}
   462  	app.logf("  %s = %q", f.Name, f.Value)
   463  }
   464  
   465  func (app *h2i) encodeHeaders(req *http.Request) []byte {
   466  	app.hbuf.Reset()
   467  
   468  	// TODO(bradfitz): figure out :authority-vs-Host stuff between http2 and Go
   469  	host := req.Host
   470  	if host == "" {
   471  		host = req.URL.Host
   472  	}
   473  
   474  	path := req.URL.Path
   475  	if path == "" {
   476  		path = "/"
   477  	}
   478  
   479  	app.writeHeader(":authority", host) // probably not right for all sites
   480  	app.writeHeader(":method", req.Method)
   481  	app.writeHeader(":path", path)
   482  	app.writeHeader(":scheme", "https")
   483  
   484  	for k, vv := range req.Header {
   485  		lowKey := strings.ToLower(k)
   486  		if lowKey == "host" {
   487  			continue
   488  		}
   489  		for _, v := range vv {
   490  			app.writeHeader(lowKey, v)
   491  		}
   492  	}
   493  	return app.hbuf.Bytes()
   494  }
   495  
   496  func (app *h2i) writeHeader(name, value string) {
   497  	app.henc.WriteField(hpack.HeaderField{Name: name, Value: value})
   498  	app.logf(" %s = %s", name, value)
   499  }