blob: 4c0e5b0a281349ac12f1ce542bc4ea5997912eeb [file] [log] [blame]
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +00001package main
2
3import (
4 "bytes"
5 "crypto/md5"
commit-bot@chromium.org282333f2014-04-14 14:54:07 +00006 "database/sql"
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +00007 "encoding/base64"
8 "encoding/json"
9 "flag"
10 "fmt"
commit-bot@chromium.org282333f2014-04-14 14:54:07 +000011 _ "github.com/go-sql-driver/mysql"
commit-bot@chromium.orgc81d1c42014-04-14 18:53:10 +000012 _ "github.com/mattn/go-sqlite3"
13 htemplate "html/template"
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +000014 "io/ioutil"
15 "log"
commit-bot@chromium.orgd04e1dd2014-04-19 13:55:50 +000016 "math/rand"
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +000017 "net/http"
18 "os"
19 "os/exec"
20 "path/filepath"
commit-bot@chromium.orgc81d1c42014-04-14 18:53:10 +000021 "regexp"
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +000022 "strings"
23 "text/template"
commit-bot@chromium.org06aca012014-04-14 20:12:08 +000024 "time"
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +000025)
26
27const (
28 RESULT_COMPILE = `c++ -DSK_GAMMA_SRGB -DSK_GAMMA_APPLY_TO_A8 -DSK_SCALAR_TO_FLOAT_EXCLUDED -DSK_ALLOW_STATIC_GLOBAL_INITIALIZERS=1 -DSK_SUPPORT_GPU=0 -DSK_SUPPORT_OPENCL=0 -DSK_FORCE_DISTANCEFIELD_FONTS=0 -DSK_SCALAR_IS_FLOAT -DSK_CAN_USE_FLOAT -DSK_SAMPLES_FOR_X -DSK_BUILD_FOR_UNIX -DSK_USE_POSIX_THREADS -DSK_SYSTEM_ZLIB=1 -DSK_DEBUG -DSK_DEVELOPER=1 -I../../src/core -I../../src/images -I../../tools/flags -I../../include/config -I../../include/core -I../../include/pathops -I../../include/pipe -I../../include/effects -I../../include/ports -I../../src/sfnt -I../../include/utils -I../../src/utils -I../../include/images -g -fno-exceptions -fstrict-aliasing -Wall -Wextra -Winit-self -Wpointer-arith -Wno-unused-parameter -Wno-c++11-extensions -Werror -m64 -fno-rtti -Wnon-virtual-dtor -c ../../../cache/%s.cpp -o ../../../cache/%s.o`
commit-bot@chromium.orgd6cab4a2014-04-09 21:35:18 +000029 LINK = `c++ -m64 -lstdc++ -lm -o ../../../inout/%s -Wl,--start-group ../../../cache/%s.o obj/experimental/webtry/webtry.main.o obj/gyp/libflags.a libskia_images.a libskia_core.a libskia_effects.a obj/gyp/libjpeg.a obj/gyp/libwebp_dec.a obj/gyp/libwebp_demux.a obj/gyp/libwebp_dsp.a obj/gyp/libwebp_enc.a obj/gyp/libwebp_utils.a libskia_utils.a libskia_opts.a libskia_opts_ssse3.a libskia_ports.a libskia_sfnt.a -Wl,--end-group -lpng -lz -lgif -lpthread -lfontconfig -ldl -lfreetype`
commit-bot@chromium.orgc81d1c42014-04-14 18:53:10 +000030 DEFAULT_SAMPLE = `SkPaint p;
31p.setColor(SK_ColorRED);
32p.setAntiAlias(true);
33p.setStyle(SkPaint::kStroke_Style);
34p.setStrokeWidth(10);
35
36canvas->drawLine(20, 20, 100, 100, p);
37`
commit-bot@chromium.org4bd8fdc2014-04-15 00:43:51 +000038 // Don't increase above 2^16 w/o altering the db tables to accept something bigger than TEXT.
39 MAX_TRY_SIZE = 64000
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +000040)
41
42var (
43 // codeTemplate is the cpp code template the user's code is copied into.
44 codeTemplate *template.Template = nil
45
commit-bot@chromium.org06aca012014-04-14 20:12:08 +000046 // indexTemplate is the main index.html page we serve.
47 indexTemplate *htemplate.Template = nil
48
commit-bot@chromium.org2dceeda2014-04-19 14:50:23 +000049 // iframeTemplate is the main index.html page we serve.
50 iframeTemplate *htemplate.Template = nil
51
commit-bot@chromium.orgd04e1dd2014-04-19 13:55:50 +000052 // recentTemplate is a list of recent images.
commit-bot@chromium.org06aca012014-04-14 20:12:08 +000053 recentTemplate *htemplate.Template = nil
commit-bot@chromium.org282333f2014-04-14 14:54:07 +000054
commit-bot@chromium.orgd04e1dd2014-04-19 13:55:50 +000055 // workspaceTemplate is the page for workspaces, a series of webtrys.
56 workspaceTemplate *htemplate.Template = nil
57
commit-bot@chromium.org282333f2014-04-14 14:54:07 +000058 // db is the database, nil if we don't have an SQL database to store data into.
59 db *sql.DB = nil
commit-bot@chromium.orgc81d1c42014-04-14 18:53:10 +000060
61 // directLink is the regex that matches URLs paths that are direct links.
commit-bot@chromium.org06aca012014-04-14 20:12:08 +000062 directLink = regexp.MustCompile("^/c/([a-f0-9]+)$")
63
commit-bot@chromium.org2dceeda2014-04-19 14:50:23 +000064 // iframeLink is the regex that matches URLs paths that are links to iframes.
65 iframeLink = regexp.MustCompile("^/iframe/([a-f0-9]+)$")
66
commit-bot@chromium.org06aca012014-04-14 20:12:08 +000067 // imageLink is the regex that matches URLs paths that are direct links to PNGs.
68 imageLink = regexp.MustCompile("^/i/([a-f0-9]+.png)$")
commit-bot@chromium.orgd04e1dd2014-04-19 13:55:50 +000069
70 // workspaceLink is the regex that matches URLs paths for workspaces.
71 workspaceLink = regexp.MustCompile("^/w/([a-z0-9-]+)$")
72
73 // workspaceNameAdj is a list of adjectives for building workspace names.
74 workspaceNameAdj = []string{
75 "autumn", "hidden", "bitter", "misty", "silent", "empty", "dry", "dark",
76 "summer", "icy", "delicate", "quiet", "white", "cool", "spring", "winter",
77 "patient", "twilight", "dawn", "crimson", "wispy", "weathered", "blue",
78 "billowing", "broken", "cold", "damp", "falling", "frosty", "green",
79 "long", "late", "lingering", "bold", "little", "morning", "muddy", "old",
80 "red", "rough", "still", "small", "sparkling", "throbbing", "shy",
81 "wandering", "withered", "wild", "black", "young", "holy", "solitary",
82 "fragrant", "aged", "snowy", "proud", "floral", "restless", "divine",
83 "polished", "ancient", "purple", "lively", "nameless",
84 }
85
86 // workspaceNameNoun is a list of nouns for building workspace names.
87 workspaceNameNoun = []string{
88 "waterfall", "river", "breeze", "moon", "rain", "wind", "sea", "morning",
89 "snow", "lake", "sunset", "pine", "shadow", "leaf", "dawn", "glitter",
90 "forest", "hill", "cloud", "meadow", "sun", "glade", "bird", "brook",
91 "butterfly", "bush", "dew", "dust", "field", "fire", "flower", "firefly",
92 "feather", "grass", "haze", "mountain", "night", "pond", "darkness",
93 "snowflake", "silence", "sound", "sky", "shape", "surf", "thunder",
94 "violet", "water", "wildflower", "wave", "water", "resonance", "sun",
95 "wood", "dream", "cherry", "tree", "fog", "frost", "voice", "paper",
96 "frog", "smoke", "star",
97 }
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +000098)
99
100// flags
101var (
102 useChroot = flag.Bool("use_chroot", false, "Run the compiled code in the schroot jail.")
commit-bot@chromium.org282333f2014-04-14 14:54:07 +0000103 port = flag.String("port", ":8000", "HTTP service address (e.g., ':8000')")
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +0000104)
105
106// lineNumbers adds #line numbering to the user's code.
107func LineNumbers(c string) string {
108 lines := strings.Split(c, "\n")
109 ret := []string{}
110 for i, line := range lines {
111 ret = append(ret, fmt.Sprintf("#line %d", i+1))
112 ret = append(ret, line)
113 }
114 return strings.Join(ret, "\n")
115}
116
117func init() {
commit-bot@chromium.org2dceeda2014-04-19 14:50:23 +0000118 rand.Seed(time.Now().UnixNano())
commit-bot@chromium.orgc81d1c42014-04-14 18:53:10 +0000119
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +0000120 // Change the current working directory to the directory of the executable.
121 var err error
122 cwd, err := filepath.Abs(filepath.Dir(os.Args[0]))
123 if err != nil {
124 log.Fatal(err)
125 }
126 os.Chdir(cwd)
127
128 codeTemplate, err = template.ParseFiles(filepath.Join(cwd, "templates/template.cpp"))
129 if err != nil {
130 panic(err)
131 }
commit-bot@chromium.orgd04e1dd2014-04-19 13:55:50 +0000132 indexTemplate, err = htemplate.ParseFiles(
133 filepath.Join(cwd, "templates/index.html"),
134 filepath.Join(cwd, "templates/titlebar.html"),
135 )
commit-bot@chromium.org06aca012014-04-14 20:12:08 +0000136 if err != nil {
137 panic(err)
138 }
commit-bot@chromium.org2dceeda2014-04-19 14:50:23 +0000139 iframeTemplate, err = htemplate.ParseFiles(
140 filepath.Join(cwd, "templates/iframe.html"),
141 )
142 if err != nil {
143 panic(err)
144 }
commit-bot@chromium.orgd04e1dd2014-04-19 13:55:50 +0000145 recentTemplate, err = htemplate.ParseFiles(
146 filepath.Join(cwd, "templates/recent.html"),
147 filepath.Join(cwd, "templates/titlebar.html"),
148 )
149 if err != nil {
150 panic(err)
151 }
152 workspaceTemplate, err = htemplate.ParseFiles(
153 filepath.Join(cwd, "templates/workspace.html"),
154 filepath.Join(cwd, "templates/titlebar.html"),
155 )
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +0000156 if err != nil {
157 panic(err)
158 }
commit-bot@chromium.org282333f2014-04-14 14:54:07 +0000159
160 // Connect to MySQL server. First, get the password from the metadata server.
161 // See https://developers.google.com/compute/docs/metadata#custom.
162 req, err := http.NewRequest("GET", "http://metadata/computeMetadata/v1/instance/attributes/password", nil)
163 if err != nil {
164 panic(err)
165 }
166 client := http.Client{}
167 req.Header.Add("X-Google-Metadata-Request", "True")
168 if resp, err := client.Do(req); err == nil {
169 password, err := ioutil.ReadAll(resp.Body)
170 if err != nil {
171 log.Printf("ERROR: Failed to read password from metadata server: %q\n", err)
172 panic(err)
173 }
174 // The IP address of the database is found here:
175 // https://console.developers.google.com/project/31977622648/sql/instances/webtry/overview
176 // And 3306 is the default port for MySQL.
commit-bot@chromium.org4bd8fdc2014-04-15 00:43:51 +0000177 db, err = sql.Open("mysql", fmt.Sprintf("webtry:%s@tcp(173.194.83.52:3306)/webtry?parseTime=true", password))
commit-bot@chromium.org282333f2014-04-14 14:54:07 +0000178 if err != nil {
179 log.Printf("ERROR: Failed to open connection to SQL server: %q\n", err)
180 panic(err)
181 }
182 } else {
commit-bot@chromium.orgd04e1dd2014-04-19 13:55:50 +0000183 log.Printf("INFO: Failed to find metadata, unable to connect to MySQL server (Expected when running locally): %q\n", err)
commit-bot@chromium.orgc81d1c42014-04-14 18:53:10 +0000184 // Fallback to sqlite for local use.
185 db, err = sql.Open("sqlite3", "./webtry.db")
186 if err != nil {
187 log.Printf("ERROR: Failed to open: %q\n", err)
188 panic(err)
189 }
190 sql := `CREATE TABLE webtry (
191 code TEXT DEFAULT '' NOT NULL,
192 create_ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
193 hash CHAR(64) DEFAULT '' NOT NULL,
194 PRIMARY KEY(hash)
195 )`
commit-bot@chromium.orgd04e1dd2014-04-19 13:55:50 +0000196 _, err = db.Exec(sql)
197 log.Printf("Info: status creating sqlite table for webtry: %q\n", err)
198 sql = `CREATE TABLE workspace (
199 name CHAR(64) DEFAULT '' NOT NULL,
200 create_ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
201 PRIMARY KEY(name)
202 )`
203 _, err = db.Exec(sql)
204 log.Printf("Info: status creating sqlite table for workspace: %q\n", err)
205 sql = `CREATE TABLE workspacetry (
206 name CHAR(64) DEFAULT '' NOT NULL,
207 create_ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
208 hash CHAR(64) DEFAULT '' NOT NULL,
209 hidden INTEGER DEFAULT 0 NOT NULL,
210
211 FOREIGN KEY (name) REFERENCES workspace(name)
212 )`
213 _, err = db.Exec(sql)
214 log.Printf("Info: status creating sqlite table for workspace try: %q\n", err)
commit-bot@chromium.org282333f2014-04-14 14:54:07 +0000215 }
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +0000216}
217
218// userCode is used in template expansion.
219type userCode struct {
220 UserCode string
commit-bot@chromium.org2dceeda2014-04-19 14:50:23 +0000221 Hash string
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +0000222}
223
224// expandToFile expands the template and writes the result to the file.
225func expandToFile(filename string, code string, t *template.Template) error {
226 f, err := os.Create(filename)
227 if err != nil {
228 return err
229 }
230 defer f.Close()
commit-bot@chromium.orgc81d1c42014-04-14 18:53:10 +0000231 return t.Execute(f, userCode{UserCode: code})
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +0000232}
233
234// expandCode expands the template into a file and calculate the MD5 hash.
235func expandCode(code string) (string, error) {
236 h := md5.New()
237 h.Write([]byte(code))
238 hash := fmt.Sprintf("%x", h.Sum(nil))
239 // At this point we are running in skia/experimental/webtry, making cache a
240 // peer directory to skia.
241 // TODO(jcgregorio) Make all relative directories into flags.
242 err := expandToFile(fmt.Sprintf("../../../cache/%s.cpp", hash), code, codeTemplate)
243 return hash, err
244}
245
246// response is serialized to JSON as a response to POSTs.
247type response struct {
248 Message string `json:"message"`
249 Img string `json:"img"`
commit-bot@chromium.orgc81d1c42014-04-14 18:53:10 +0000250 Hash string `json:"hash"`
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +0000251}
252
253// doCmd executes the given command line string in either the out/Debug
254// directory or the inout directory. Returns the stdout, and stderr in the case
255// of a non-zero exit code.
256func doCmd(commandLine string, moveToDebug bool) (string, error) {
257 log.Printf("Command: %q\n", commandLine)
258 programAndArgs := strings.SplitN(commandLine, " ", 2)
259 program := programAndArgs[0]
260 args := []string{}
261 if len(programAndArgs) > 1 {
262 args = strings.Split(programAndArgs[1], " ")
263 }
264 cmd := exec.Command(program, args...)
265 abs, err := filepath.Abs("../../out/Debug")
266 if err != nil {
267 return "", fmt.Errorf("Failed to find absolute path to Debug directory.")
268 }
269 if moveToDebug {
270 cmd.Dir = abs
271 } else if !*useChroot { // Don't set cmd.Dir when using chroot.
272 abs, err := filepath.Abs("../../../inout")
273 if err != nil {
274 return "", fmt.Errorf("Failed to find absolute path to inout directory.")
275 }
276 cmd.Dir = abs
277 }
278 log.Printf("Run in directory: %q\n", cmd.Dir)
279 var stdOut bytes.Buffer
280 cmd.Stdout = &stdOut
281 var stdErr bytes.Buffer
282 cmd.Stderr = &stdErr
283 cmd.Start()
284 err = cmd.Wait()
285 message := stdOut.String()
286 log.Printf("StdOut: %s\n", message)
287 if err != nil {
288 log.Printf("Exit status: %s\n", err.Error())
289 log.Printf("StdErr: %s\n", stdErr.String())
290 message += stdErr.String()
291 return message, fmt.Errorf("Failed to run command.")
292 }
293 return message, nil
294}
295
296// reportError formats an HTTP error response and also logs the detailed error message.
297func reportError(w http.ResponseWriter, r *http.Request, err error, message string) {
298 m := response{
299 Message: message,
300 }
301 log.Printf("Error: %s\n%s", message, err.Error())
302 resp, err := json.Marshal(m)
303 if err != nil {
304 http.Error(w, "Failed to serialize a response", 500)
305 return
306 }
307 w.Write(resp)
308}
309
commit-bot@chromium.orgd04e1dd2014-04-19 13:55:50 +0000310func writeToDatabase(hash string, code string, workspaceName string) {
commit-bot@chromium.org282333f2014-04-14 14:54:07 +0000311 if db == nil {
312 return
313 }
314 if _, err := db.Exec("INSERT INTO webtry (code, hash) VALUES(?, ?)", code, hash); err != nil {
315 log.Printf("ERROR: Failed to insert code into database: %q\n", err)
316 }
commit-bot@chromium.orgd04e1dd2014-04-19 13:55:50 +0000317 if workspaceName != "" {
318 if _, err := db.Exec("INSERT INTO workspacetry (name, hash) VALUES(?, ?)", workspaceName, hash); err != nil {
319 log.Printf("ERROR: Failed to insert into workspacetry table: %q\n", err)
320 }
321 }
commit-bot@chromium.org282333f2014-04-14 14:54:07 +0000322}
323
commit-bot@chromium.org06aca012014-04-14 20:12:08 +0000324func cssHandler(w http.ResponseWriter, r *http.Request) {
325 http.ServeFile(w, r, "css/webtry.css")
326}
327
commit-bot@chromium.orgd04e1dd2014-04-19 13:55:50 +0000328func jsHandler(w http.ResponseWriter, r *http.Request) {
329 http.ServeFile(w, r, "js/run.js")
330}
331
commit-bot@chromium.org06aca012014-04-14 20:12:08 +0000332// imageHandler serves up the PNG of a specific try.
333func imageHandler(w http.ResponseWriter, r *http.Request) {
334 log.Printf("Image Handler: %q\n", r.URL.Path)
335 if r.Method != "GET" {
336 http.NotFound(w, r)
337 return
338 }
339 match := imageLink.FindStringSubmatch(r.URL.Path)
340 if len(match) != 2 {
341 http.NotFound(w, r)
342 return
343 }
344 filename := match[1]
345 http.ServeFile(w, r, fmt.Sprintf("../../../inout/%s", filename))
346}
347
348type Try struct {
349 Hash string
350 CreateTS string
351}
352
353type Recent struct {
354 Tries []Try
355}
356
357// recentHandler shows the last 20 tries.
358func recentHandler(w http.ResponseWriter, r *http.Request) {
359 log.Printf("Recent Handler: %q\n", r.URL.Path)
360
361 var err error
362 rows, err := db.Query("SELECT create_ts, hash FROM webtry ORDER BY create_ts DESC LIMIT 20")
363 if err != nil {
364 http.NotFound(w, r)
365 return
366 }
367 recent := []Try{}
368 for rows.Next() {
369 var hash string
370 var create_ts time.Time
371 if err := rows.Scan(&create_ts, &hash); err != nil {
372 log.Printf("Error: failed to fetch from database: %q", err)
373 continue
374 }
375 recent = append(recent, Try{Hash: hash, CreateTS: create_ts.Format("2006-02-01")})
376 }
377 if err := recentTemplate.Execute(w, Recent{Tries: recent}); err != nil {
378 log.Printf("ERROR: Failed to expand template: %q\n", err)
379 }
380}
381
commit-bot@chromium.orgd04e1dd2014-04-19 13:55:50 +0000382type Workspace struct {
383 Name string
384 Code string
385 Tries []Try
386}
387
388// newWorkspace generates a new random workspace name and stores it in the database.
389func newWorkspace() (string, error) {
390 for i := 0; i < 10; i++ {
391 adj := workspaceNameAdj[rand.Intn(len(workspaceNameAdj))]
392 noun := workspaceNameNoun[rand.Intn(len(workspaceNameNoun))]
393 suffix := rand.Intn(1000)
394 name := fmt.Sprintf("%s-%s-%d", adj, noun, suffix)
395 if _, err := db.Exec("INSERT INTO workspace (name) VALUES(?)", name); err == nil {
396 return name, nil
397 } else {
398 log.Printf("ERROR: Failed to insert workspace into database: %q\n", err)
399 }
400 }
401 return "", fmt.Errorf("Failed to create a new workspace")
402}
403
404// getCode returns the code for a given hash, or the empty string if not found.
405func getCode(hash string) string {
406 code := ""
407 if err := db.QueryRow("SELECT code FROM webtry WHERE hash=?", hash).Scan(&code); err != nil {
408 log.Printf("ERROR: Code for hash is missing: %q\n", err)
409 }
410 return code
411}
412
413func workspaceHandler(w http.ResponseWriter, r *http.Request) {
414 log.Printf("Workspace Handler: %q\n", r.URL.Path)
415 if r.Method == "GET" {
416 tries := []Try{}
417 match := workspaceLink.FindStringSubmatch(r.URL.Path)
418 name := ""
419 if len(match) == 2 {
420 name = match[1]
421 rows, err := db.Query("SELECT create_ts, hash FROM workspacetry WHERE name=? ORDER BY create_ts DESC ", name)
422 if err != nil {
423 reportError(w, r, err, "Failed to select.")
424 return
425 }
426 for rows.Next() {
427 var hash string
428 var create_ts time.Time
429 if err := rows.Scan(&create_ts, &hash); err != nil {
430 log.Printf("Error: failed to fetch from database: %q", err)
431 continue
432 }
433 tries = append(tries, Try{Hash: hash, CreateTS: create_ts.Format("2006-02-01")})
434 }
435 }
436 var code string
437 if len(tries) == 0 {
438 code = DEFAULT_SAMPLE
439 } else {
440 code = getCode(tries[len(tries)-1].Hash)
441 }
442 if err := workspaceTemplate.Execute(w, Workspace{Tries: tries, Code: code, Name: name}); err != nil {
443 log.Printf("ERROR: Failed to expand template: %q\n", err)
444 }
445 } else if r.Method == "POST" {
446 name, err := newWorkspace()
447 if err != nil {
448 http.Error(w, "Failed to create a new workspace.", 500)
449 return
450 }
451 http.Redirect(w, r, "/w/"+name, 302)
452 }
453}
454
commit-bot@chromium.org4bd8fdc2014-04-15 00:43:51 +0000455// hasPreProcessor returns true if any line in the code begins with a # char.
456func hasPreProcessor(code string) bool {
457 lines := strings.Split(code, "\n")
458 for _, s := range lines {
459 if strings.HasPrefix(strings.TrimSpace(s), "#") {
460 return true
461 }
462 }
463 return false
464}
465
commit-bot@chromium.orgd04e1dd2014-04-19 13:55:50 +0000466type TryRequest struct {
467 Code string `json:"code"`
468 Name string `json:"name"`
469}
470
commit-bot@chromium.org2dceeda2014-04-19 14:50:23 +0000471// iframeHandler handles the GET and POST of the main page.
472func iframeHandler(w http.ResponseWriter, r *http.Request) {
473 log.Printf("IFrame Handler: %q\n", r.URL.Path)
474 if r.Method != "GET" {
475 http.NotFound(w, r)
476 return
477 }
478 match := iframeLink.FindStringSubmatch(r.URL.Path)
479 if len(match) != 2 {
480 http.NotFound(w, r)
481 return
482 }
483 hash := match[1]
484 if db == nil {
485 http.NotFound(w, r)
486 return
487 }
488 var code string
489 // Load 'code' with the code found in the database.
490 if err := db.QueryRow("SELECT code FROM webtry WHERE hash=?", hash).Scan(&code); err != nil {
491 http.NotFound(w, r)
492 return
493 }
494 // Expand the template.
495 if err := iframeTemplate.Execute(w, userCode{UserCode: code, Hash: hash}); err != nil {
496 log.Printf("ERROR: Failed to expand template: %q\n", err)
497 }
498}
499
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +0000500// mainHandler handles the GET and POST of the main page.
501func mainHandler(w http.ResponseWriter, r *http.Request) {
commit-bot@chromium.org06aca012014-04-14 20:12:08 +0000502 log.Printf("Main Handler: %q\n", r.URL.Path)
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +0000503 if r.Method == "GET" {
commit-bot@chromium.orgc81d1c42014-04-14 18:53:10 +0000504 code := DEFAULT_SAMPLE
commit-bot@chromium.orgc81d1c42014-04-14 18:53:10 +0000505 match := directLink.FindStringSubmatch(r.URL.Path)
commit-bot@chromium.org06aca012014-04-14 20:12:08 +0000506 if len(match) == 2 && r.URL.Path != "/" {
commit-bot@chromium.orgc81d1c42014-04-14 18:53:10 +0000507 hash := match[1]
508 if db == nil {
509 http.NotFound(w, r)
510 return
511 }
512 // Update 'code' with the code found in the database.
513 if err := db.QueryRow("SELECT code FROM webtry WHERE hash=?", hash).Scan(&code); err != nil {
514 http.NotFound(w, r)
515 return
516 }
517 }
518 // Expand the template.
commit-bot@chromium.org06aca012014-04-14 20:12:08 +0000519 if err := indexTemplate.Execute(w, userCode{UserCode: code}); err != nil {
commit-bot@chromium.orgc81d1c42014-04-14 18:53:10 +0000520 log.Printf("ERROR: Failed to expand template: %q\n", err)
521 }
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +0000522 } else if r.Method == "POST" {
523 w.Header().Set("Content-Type", "application/json")
commit-bot@chromium.org4bd8fdc2014-04-15 00:43:51 +0000524 buf := bytes.NewBuffer(make([]byte, 0, MAX_TRY_SIZE))
525 n, err := buf.ReadFrom(r.Body)
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +0000526 if err != nil {
527 reportError(w, r, err, "Failed to read a request body.")
528 return
529 }
commit-bot@chromium.org4bd8fdc2014-04-15 00:43:51 +0000530 if n == MAX_TRY_SIZE {
531 err := fmt.Errorf("Code length equal to, or exceeded, %d", MAX_TRY_SIZE)
532 reportError(w, r, err, "Code too large.")
533 return
534 }
commit-bot@chromium.orgd04e1dd2014-04-19 13:55:50 +0000535 request := TryRequest{}
536 if err := json.Unmarshal(buf.Bytes(), &request); err != nil {
537 reportError(w, r, err, "Coulnd't decode JSON.")
538 return
539 }
540 if hasPreProcessor(request.Code) {
commit-bot@chromium.org4bd8fdc2014-04-15 00:43:51 +0000541 err := fmt.Errorf("Found preprocessor macro in code.")
542 reportError(w, r, err, "Preprocessor macros aren't allowed.")
543 return
544 }
commit-bot@chromium.orgd04e1dd2014-04-19 13:55:50 +0000545 hash, err := expandCode(LineNumbers(request.Code))
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +0000546 if err != nil {
547 reportError(w, r, err, "Failed to write the code to compile.")
548 return
549 }
commit-bot@chromium.orgd04e1dd2014-04-19 13:55:50 +0000550 writeToDatabase(hash, request.Code, request.Name)
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +0000551 message, err := doCmd(fmt.Sprintf(RESULT_COMPILE, hash, hash), true)
552 if err != nil {
553 reportError(w, r, err, "Failed to compile the code:\n"+message)
554 return
555 }
556 linkMessage, err := doCmd(fmt.Sprintf(LINK, hash, hash), true)
557 if err != nil {
558 reportError(w, r, err, "Failed to link the code:\n"+linkMessage)
559 return
560 }
561 message += linkMessage
562 cmd := hash + " --out " + hash + ".png"
563 if *useChroot {
564 cmd = "schroot -c webtry --directory=/inout -- /inout/" + cmd
565 } else {
566 abs, err := filepath.Abs("../../../inout")
567 if err != nil {
568 reportError(w, r, err, "Failed to find executable directory.")
569 return
570 }
571 cmd = abs + "/" + cmd
572 }
573
574 execMessage, err := doCmd(cmd, false)
575 if err != nil {
576 reportError(w, r, err, "Failed to run the code:\n"+execMessage)
577 return
578 }
579 png, err := ioutil.ReadFile("../../../inout/" + hash + ".png")
580 if err != nil {
581 reportError(w, r, err, "Failed to open the generated PNG.")
582 return
583 }
584
585 m := response{
586 Message: message,
587 Img: base64.StdEncoding.EncodeToString([]byte(png)),
commit-bot@chromium.orgc81d1c42014-04-14 18:53:10 +0000588 Hash: hash,
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +0000589 }
590 resp, err := json.Marshal(m)
591 if err != nil {
592 reportError(w, r, err, "Failed to serialize a response.")
593 return
594 }
595 w.Write(resp)
596 }
597}
598
599func main() {
600 flag.Parse()
commit-bot@chromium.org06aca012014-04-14 20:12:08 +0000601 http.HandleFunc("/i/", imageHandler)
commit-bot@chromium.orgd04e1dd2014-04-19 13:55:50 +0000602 http.HandleFunc("/w/", workspaceHandler)
commit-bot@chromium.org06aca012014-04-14 20:12:08 +0000603 http.HandleFunc("/recent/", recentHandler)
commit-bot@chromium.org2dceeda2014-04-19 14:50:23 +0000604 http.HandleFunc("/iframe/", iframeHandler)
commit-bot@chromium.org06aca012014-04-14 20:12:08 +0000605 http.HandleFunc("/css/", cssHandler)
commit-bot@chromium.orgd04e1dd2014-04-19 13:55:50 +0000606 http.HandleFunc("/js/", jsHandler)
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +0000607 http.HandleFunc("/", mainHandler)
commit-bot@chromium.org282333f2014-04-14 14:54:07 +0000608 log.Fatal(http.ListenAndServe(*port, nil))
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +0000609}