annotate fcgi/fcgi.go @ 1:3e7247db5c6e

show index.html
author Atarwn Gard <a@qwa.su>
date Mon, 09 Mar 2026 01:04:16 +0500
parents 48bdab3eec8a
children eb705d4cdcd7
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
rev   line source
0
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
1 // Package fcgi implements a minimal FastCGI client.
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
2 //
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
3 // Supports sending a single request over a pre-dialed net.Conn and streaming
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
4 // the CGI response back to an http.ResponseWriter.
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
5 package fcgi
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
6
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
7 import (
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
8 "bufio"
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
9 "encoding/binary"
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
10 "io"
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
11 "net/http"
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
12 "net/textproto"
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
13 "strings"
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
14 )
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
15
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
16 // Reference: https://fastcgi-archives.github.io/FastCGI_Specification.html
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
17
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
18 const (
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
19 fcgiVersion = 1
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
20 fcgiBeginRequest = 1
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
21 fcgiParams = 4
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
22 fcgiStdin = 5
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
23 fcgiStdout = 6
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
24 fcgiEndRequest = 3
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
25 fcgiRoleResponder = 1
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
26 fcgiRequestID = 1
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
27 )
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
28
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
29 // Do sends params and the request body over conn as a FastCGI request, then
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
30 // parses the response and writes it to w. conn is closed by the caller.
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
31 func Do(w http.ResponseWriter, r *http.Request, conn io.ReadWriter, params map[string]string) error {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
32 bw := bufio.NewWriter(conn)
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
33
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
34 if err := writeRecord(bw, fcgiBeginRequest, fcgiRequestID,
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
35 []byte{0, fcgiRoleResponder, 0, 0, 0, 0, 0, 0}); err != nil {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
36 return err
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
37 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
38
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
39 if err := writeRecord(bw, fcgiParams, fcgiRequestID, encodeParams(params)); err != nil {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
40 return err
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
41 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
42 if err := writeRecord(bw, fcgiParams, fcgiRequestID, nil); err != nil {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
43 return err
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
44 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
45
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
46 if r.Body != nil {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
47 buf := make([]byte, 4096)
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
48 for {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
49 n, err := r.Body.Read(buf)
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
50 if n > 0 {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
51 if werr := writeRecord(bw, fcgiStdin, fcgiRequestID, buf[:n]); werr != nil {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
52 return werr
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
53 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
54 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
55 if err == io.EOF {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
56 break
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
57 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
58 if err != nil {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
59 return err
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
60 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
61 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
62 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
63 if err := writeRecord(bw, fcgiStdin, fcgiRequestID, nil); err != nil {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
64 return err
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
65 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
66 if err := bw.Flush(); err != nil {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
67 return err
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
68 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
69
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
70 // Read response
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
71 var stdout strings.Builder
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
72 br := bufio.NewReader(conn)
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
73
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
74 for {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
75 hdr := make([]byte, 8)
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
76 if _, err := io.ReadFull(br, hdr); err != nil {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
77 return err
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
78 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
79 recType := hdr[1]
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
80 contentLen := int(binary.BigEndian.Uint16(hdr[4:6]))
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
81 paddingLen := int(hdr[6])
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
82
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
83 content := make([]byte, contentLen)
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
84 if _, err := io.ReadFull(br, content); err != nil {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
85 return err
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
86 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
87 if paddingLen > 0 {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
88 if _, err := io.ReadFull(br, make([]byte, paddingLen)); err != nil {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
89 return err
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
90 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
91 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
92
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
93 switch recType {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
94 case fcgiStdout:
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
95 stdout.Write(content)
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
96 case fcgiEndRequest:
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
97 return forwardResponse(w, stdout.String())
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
98 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
99 // fcgiStderr is silently discarded
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
100 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
101 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
102
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
103 func forwardResponse(w http.ResponseWriter, raw string) error {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
104 sep := "\r\n\r\n"
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
105 idx := strings.Index(raw, sep)
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
106 advance := 4
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
107 if idx == -1 {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
108 sep = "\n\n"
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
109 idx = strings.Index(raw, sep)
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
110 advance = 2
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
111 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
112 if idx == -1 {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
113 w.Write([]byte(raw))
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
114 return nil
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
115 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
116
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
117 headerPart := raw[:idx]
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
118 body := raw[idx+advance:]
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
119
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
120 tp := textproto.NewReader(bufio.NewReader(strings.NewReader(headerPart + "\r\n\r\n")))
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
121 mime, _ := tp.ReadMIMEHeader()
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
122
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
123 status := 200
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
124 if s := mime.Get("Status"); s != "" {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
125 code, _, _ := strings.Cut(s, " ")
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
126 if n := parseIntFast(code); n > 0 {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
127 status = n
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
128 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
129 mime.Del("Status")
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
130 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
131
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
132 for k, vs := range mime {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
133 for _, v := range vs {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
134 w.Header().Add(k, v)
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
135 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
136 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
137 w.WriteHeader(status)
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
138 w.Write([]byte(body))
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
139 return nil
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
140 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
141
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
142 func writeRecord(w io.Writer, recType uint8, reqID uint16, content []byte) error {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
143 length := len(content)
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
144 padding := (8 - (length % 8)) % 8
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
145 hdr := []byte{
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
146 fcgiVersion, recType,
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
147 byte(reqID >> 8), byte(reqID),
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
148 byte(length >> 8), byte(length),
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
149 byte(padding), 0,
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
150 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
151 if _, err := w.Write(hdr); err != nil {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
152 return err
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
153 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
154 if len(content) > 0 {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
155 if _, err := w.Write(content); err != nil {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
156 return err
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
157 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
158 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
159 if padding > 0 {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
160 if _, err := w.Write(make([]byte, padding)); err != nil {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
161 return err
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
162 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
163 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
164 return nil
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
165 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
166
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
167 func encodeParams(params map[string]string) []byte {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
168 var buf []byte
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
169 for k, v := range params {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
170 buf = appendLen(buf, len(k))
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
171 buf = appendLen(buf, len(v))
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
172 buf = append(buf, k...)
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
173 buf = append(buf, v...)
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
174 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
175 return buf
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
176 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
177
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
178 func appendLen(buf []byte, n int) []byte {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
179 if n <= 127 {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
180 return append(buf, byte(n))
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
181 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
182 return append(buf, byte((n>>24)|0x80), byte(n>>16), byte(n>>8), byte(n))
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
183 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
184
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
185 func parseIntFast(s string) int {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
186 n := 0
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
187 for i := 0; i < len(s); i++ {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
188 c := s[i]
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
189 if c < '0' || c > '9' {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
190 return 0
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
191 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
192 n = n*10 + int(c-'0')
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
193 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
194 return n
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
195 }