blob: 16f6f4ff223051c8240d30cc3cd844979d835999 [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.orgd04e1dd2014-04-19 13:55:50 +000049 // recentTemplate is a list of recent images.
commit-bot@chromium.org06aca012014-04-14 20:12:08 +000050 recentTemplate *htemplate.Template = nil
commit-bot@chromium.org282333f2014-04-14 14:54:07 +000051
commit-bot@chromium.orgd04e1dd2014-04-19 13:55:50 +000052 // workspaceTemplate is the page for workspaces, a series of webtrys.
53 workspaceTemplate *htemplate.Template = nil
54
commit-bot@chromium.org282333f2014-04-14 14:54:07 +000055 // db is the database, nil if we don't have an SQL database to store data into.
56 db *sql.DB = nil
commit-bot@chromium.orgc81d1c42014-04-14 18:53:10 +000057
58 // directLink is the regex that matches URLs paths that are direct links.
commit-bot@chromium.org06aca012014-04-14 20:12:08 +000059 directLink = regexp.MustCompile("^/c/([a-f0-9]+)$")
60
61 // imageLink is the regex that matches URLs paths that are direct links to PNGs.
62 imageLink = regexp.MustCompile("^/i/([a-f0-9]+.png)$")
commit-bot@chromium.orgd04e1dd2014-04-19 13:55:50 +000063
64 // workspaceLink is the regex that matches URLs paths for workspaces.
65 workspaceLink = regexp.MustCompile("^/w/([a-z0-9-]+)$")
66
67 // workspaceNameAdj is a list of adjectives for building workspace names.
68 workspaceNameAdj = []string{
69 "autumn", "hidden", "bitter", "misty", "silent", "empty", "dry", "dark",
70 "summer", "icy", "delicate", "quiet", "white", "cool", "spring", "winter",
71 "patient", "twilight", "dawn", "crimson", "wispy", "weathered", "blue",
72 "billowing", "broken", "cold", "damp", "falling", "frosty", "green",
73 "long", "late", "lingering", "bold", "little", "morning", "muddy", "old",
74 "red", "rough", "still", "small", "sparkling", "throbbing", "shy",
75 "wandering", "withered", "wild", "black", "young", "holy", "solitary",
76 "fragrant", "aged", "snowy", "proud", "floral", "restless", "divine",
77 "polished", "ancient", "purple", "lively", "nameless",
78 }
79
80 // workspaceNameNoun is a list of nouns for building workspace names.
81 workspaceNameNoun = []string{
82 "waterfall", "river", "breeze", "moon", "rain", "wind", "sea", "morning",
83 "snow", "lake", "sunset", "pine", "shadow", "leaf", "dawn", "glitter",
84 "forest", "hill", "cloud", "meadow", "sun", "glade", "bird", "brook",
85 "butterfly", "bush", "dew", "dust", "field", "fire", "flower", "firefly",
86 "feather", "grass", "haze", "mountain", "night", "pond", "darkness",
87 "snowflake", "silence", "sound", "sky", "shape", "surf", "thunder",
88 "violet", "water", "wildflower", "wave", "water", "resonance", "sun",
89 "wood", "dream", "cherry", "tree", "fog", "frost", "voice", "paper",
90 "frog", "smoke", "star",
91 }
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +000092)
93
94// flags
95var (
96 useChroot = flag.Bool("use_chroot", false, "Run the compiled code in the schroot jail.")
commit-bot@chromium.org282333f2014-04-14 14:54:07 +000097 port = flag.String("port", ":8000", "HTTP service address (e.g., ':8000')")
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +000098)
99
100// lineNumbers adds #line numbering to the user's code.
101func LineNumbers(c string) string {
102 lines := strings.Split(c, "\n")
103 ret := []string{}
104 for i, line := range lines {
105 ret = append(ret, fmt.Sprintf("#line %d", i+1))
106 ret = append(ret, line)
107 }
108 return strings.Join(ret, "\n")
109}
110
111func init() {
commit-bot@chromium.orgc81d1c42014-04-14 18:53:10 +0000112
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +0000113 // Change the current working directory to the directory of the executable.
114 var err error
115 cwd, err := filepath.Abs(filepath.Dir(os.Args[0]))
116 if err != nil {
117 log.Fatal(err)
118 }
119 os.Chdir(cwd)
120
121 codeTemplate, err = template.ParseFiles(filepath.Join(cwd, "templates/template.cpp"))
122 if err != nil {
123 panic(err)
124 }
commit-bot@chromium.orgc81d1c42014-04-14 18:53:10 +0000125 // Convert index.html into a template, which is expanded with the code.
commit-bot@chromium.orgd04e1dd2014-04-19 13:55:50 +0000126 indexTemplate, err = htemplate.ParseFiles(
127 filepath.Join(cwd, "templates/index.html"),
128 filepath.Join(cwd, "templates/titlebar.html"),
129 )
commit-bot@chromium.org06aca012014-04-14 20:12:08 +0000130 if err != nil {
131 panic(err)
132 }
commit-bot@chromium.orgd04e1dd2014-04-19 13:55:50 +0000133 recentTemplate, err = htemplate.ParseFiles(
134 filepath.Join(cwd, "templates/recent.html"),
135 filepath.Join(cwd, "templates/titlebar.html"),
136 )
137 if err != nil {
138 panic(err)
139 }
140 workspaceTemplate, err = htemplate.ParseFiles(
141 filepath.Join(cwd, "templates/workspace.html"),
142 filepath.Join(cwd, "templates/titlebar.html"),
143 )
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +0000144 if err != nil {
145 panic(err)
146 }
commit-bot@chromium.org282333f2014-04-14 14:54:07 +0000147
148 // Connect to MySQL server. First, get the password from the metadata server.
149 // See https://developers.google.com/compute/docs/metadata#custom.
150 req, err := http.NewRequest("GET", "http://metadata/computeMetadata/v1/instance/attributes/password", nil)
151 if err != nil {
152 panic(err)
153 }
154 client := http.Client{}
155 req.Header.Add("X-Google-Metadata-Request", "True")
156 if resp, err := client.Do(req); err == nil {
157 password, err := ioutil.ReadAll(resp.Body)
158 if err != nil {
159 log.Printf("ERROR: Failed to read password from metadata server: %q\n", err)
160 panic(err)
161 }
162 // The IP address of the database is found here:
163 // https://console.developers.google.com/project/31977622648/sql/instances/webtry/overview
164 // And 3306 is the default port for MySQL.
commit-bot@chromium.org4bd8fdc2014-04-15 00:43:51 +0000165 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 +0000166 if err != nil {
167 log.Printf("ERROR: Failed to open connection to SQL server: %q\n", err)
168 panic(err)
169 }
170 } else {
commit-bot@chromium.orgd04e1dd2014-04-19 13:55:50 +0000171 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 +0000172 // Fallback to sqlite for local use.
173 db, err = sql.Open("sqlite3", "./webtry.db")
174 if err != nil {
175 log.Printf("ERROR: Failed to open: %q\n", err)
176 panic(err)
177 }
178 sql := `CREATE TABLE webtry (
179 code TEXT DEFAULT '' NOT NULL,
180 create_ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
181 hash CHAR(64) DEFAULT '' NOT NULL,
182 PRIMARY KEY(hash)
183 )`
commit-bot@chromium.orgd04e1dd2014-04-19 13:55:50 +0000184 _, err = db.Exec(sql)
185 log.Printf("Info: status creating sqlite table for webtry: %q\n", err)
186 sql = `CREATE TABLE workspace (
187 name CHAR(64) DEFAULT '' NOT NULL,
188 create_ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
189 PRIMARY KEY(name)
190 )`
191 _, err = db.Exec(sql)
192 log.Printf("Info: status creating sqlite table for workspace: %q\n", err)
193 sql = `CREATE TABLE workspacetry (
194 name CHAR(64) DEFAULT '' NOT NULL,
195 create_ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
196 hash CHAR(64) DEFAULT '' NOT NULL,
197 hidden INTEGER DEFAULT 0 NOT NULL,
198
199 FOREIGN KEY (name) REFERENCES workspace(name)
200 )`
201 _, err = db.Exec(sql)
202 log.Printf("Info: status creating sqlite table for workspace try: %q\n", err)
commit-bot@chromium.org282333f2014-04-14 14:54:07 +0000203 }
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +0000204}
205
206// userCode is used in template expansion.
207type userCode struct {
208 UserCode string
209}
210
211// expandToFile expands the template and writes the result to the file.
212func expandToFile(filename string, code string, t *template.Template) error {
213 f, err := os.Create(filename)
214 if err != nil {
215 return err
216 }
217 defer f.Close()
commit-bot@chromium.orgc81d1c42014-04-14 18:53:10 +0000218 return t.Execute(f, userCode{UserCode: code})
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +0000219}
220
221// expandCode expands the template into a file and calculate the MD5 hash.
222func expandCode(code string) (string, error) {
223 h := md5.New()
224 h.Write([]byte(code))
225 hash := fmt.Sprintf("%x", h.Sum(nil))
226 // At this point we are running in skia/experimental/webtry, making cache a
227 // peer directory to skia.
228 // TODO(jcgregorio) Make all relative directories into flags.
229 err := expandToFile(fmt.Sprintf("../../../cache/%s.cpp", hash), code, codeTemplate)
230 return hash, err
231}
232
233// response is serialized to JSON as a response to POSTs.
234type response struct {
235 Message string `json:"message"`
236 Img string `json:"img"`
commit-bot@chromium.orgc81d1c42014-04-14 18:53:10 +0000237 Hash string `json:"hash"`
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +0000238}
239
240// doCmd executes the given command line string in either the out/Debug
241// directory or the inout directory. Returns the stdout, and stderr in the case
242// of a non-zero exit code.
243func doCmd(commandLine string, moveToDebug bool) (string, error) {
244 log.Printf("Command: %q\n", commandLine)
245 programAndArgs := strings.SplitN(commandLine, " ", 2)
246 program := programAndArgs[0]
247 args := []string{}
248 if len(programAndArgs) > 1 {
249 args = strings.Split(programAndArgs[1], " ")
250 }
251 cmd := exec.Command(program, args...)
252 abs, err := filepath.Abs("../../out/Debug")
253 if err != nil {
254 return "", fmt.Errorf("Failed to find absolute path to Debug directory.")
255 }
256 if moveToDebug {
257 cmd.Dir = abs
258 } else if !*useChroot { // Don't set cmd.Dir when using chroot.
259 abs, err := filepath.Abs("../../../inout")
260 if err != nil {
261 return "", fmt.Errorf("Failed to find absolute path to inout directory.")
262 }
263 cmd.Dir = abs
264 }
265 log.Printf("Run in directory: %q\n", cmd.Dir)
266 var stdOut bytes.Buffer
267 cmd.Stdout = &stdOut
268 var stdErr bytes.Buffer
269 cmd.Stderr = &stdErr
270 cmd.Start()
271 err = cmd.Wait()
272 message := stdOut.String()
273 log.Printf("StdOut: %s\n", message)
274 if err != nil {
275 log.Printf("Exit status: %s\n", err.Error())
276 log.Printf("StdErr: %s\n", stdErr.String())
277 message += stdErr.String()
278 return message, fmt.Errorf("Failed to run command.")
279 }
280 return message, nil
281}
282
283// reportError formats an HTTP error response and also logs the detailed error message.
284func reportError(w http.ResponseWriter, r *http.Request, err error, message string) {
285 m := response{
286 Message: message,
287 }
288 log.Printf("Error: %s\n%s", message, err.Error())
289 resp, err := json.Marshal(m)
290 if err != nil {
291 http.Error(w, "Failed to serialize a response", 500)
292 return
293 }
294 w.Write(resp)
295}
296
commit-bot@chromium.orgd04e1dd2014-04-19 13:55:50 +0000297func writeToDatabase(hash string, code string, workspaceName string) {
commit-bot@chromium.org282333f2014-04-14 14:54:07 +0000298 if db == nil {
299 return
300 }
301 if _, err := db.Exec("INSERT INTO webtry (code, hash) VALUES(?, ?)", code, hash); err != nil {
302 log.Printf("ERROR: Failed to insert code into database: %q\n", err)
303 }
commit-bot@chromium.orgd04e1dd2014-04-19 13:55:50 +0000304 if workspaceName != "" {
305 if _, err := db.Exec("INSERT INTO workspacetry (name, hash) VALUES(?, ?)", workspaceName, hash); err != nil {
306 log.Printf("ERROR: Failed to insert into workspacetry table: %q\n", err)
307 }
308 }
commit-bot@chromium.org282333f2014-04-14 14:54:07 +0000309}
310
commit-bot@chromium.org06aca012014-04-14 20:12:08 +0000311func cssHandler(w http.ResponseWriter, r *http.Request) {
312 http.ServeFile(w, r, "css/webtry.css")
313}
314
commit-bot@chromium.orgd04e1dd2014-04-19 13:55:50 +0000315func jsHandler(w http.ResponseWriter, r *http.Request) {
316 http.ServeFile(w, r, "js/run.js")
317}
318
commit-bot@chromium.org06aca012014-04-14 20:12:08 +0000319// imageHandler serves up the PNG of a specific try.
320func imageHandler(w http.ResponseWriter, r *http.Request) {
321 log.Printf("Image Handler: %q\n", r.URL.Path)
322 if r.Method != "GET" {
323 http.NotFound(w, r)
324 return
325 }
326 match := imageLink.FindStringSubmatch(r.URL.Path)
327 if len(match) != 2 {
328 http.NotFound(w, r)
329 return
330 }
331 filename := match[1]
332 http.ServeFile(w, r, fmt.Sprintf("../../../inout/%s", filename))
333}
334
335type Try struct {
336 Hash string
337 CreateTS string
338}
339
340type Recent struct {
341 Tries []Try
342}
343
344// recentHandler shows the last 20 tries.
345func recentHandler(w http.ResponseWriter, r *http.Request) {
346 log.Printf("Recent Handler: %q\n", r.URL.Path)
347
348 var err error
349 rows, err := db.Query("SELECT create_ts, hash FROM webtry ORDER BY create_ts DESC LIMIT 20")
350 if err != nil {
351 http.NotFound(w, r)
352 return
353 }
354 recent := []Try{}
355 for rows.Next() {
356 var hash string
357 var create_ts time.Time
358 if err := rows.Scan(&create_ts, &hash); err != nil {
359 log.Printf("Error: failed to fetch from database: %q", err)
360 continue
361 }
362 recent = append(recent, Try{Hash: hash, CreateTS: create_ts.Format("2006-02-01")})
363 }
364 if err := recentTemplate.Execute(w, Recent{Tries: recent}); err != nil {
365 log.Printf("ERROR: Failed to expand template: %q\n", err)
366 }
367}
368
commit-bot@chromium.orgd04e1dd2014-04-19 13:55:50 +0000369type Workspace struct {
370 Name string
371 Code string
372 Tries []Try
373}
374
375// newWorkspace generates a new random workspace name and stores it in the database.
376func newWorkspace() (string, error) {
377 for i := 0; i < 10; i++ {
378 adj := workspaceNameAdj[rand.Intn(len(workspaceNameAdj))]
379 noun := workspaceNameNoun[rand.Intn(len(workspaceNameNoun))]
380 suffix := rand.Intn(1000)
381 name := fmt.Sprintf("%s-%s-%d", adj, noun, suffix)
382 if _, err := db.Exec("INSERT INTO workspace (name) VALUES(?)", name); err == nil {
383 return name, nil
384 } else {
385 log.Printf("ERROR: Failed to insert workspace into database: %q\n", err)
386 }
387 }
388 return "", fmt.Errorf("Failed to create a new workspace")
389}
390
391// getCode returns the code for a given hash, or the empty string if not found.
392func getCode(hash string) string {
393 code := ""
394 if err := db.QueryRow("SELECT code FROM webtry WHERE hash=?", hash).Scan(&code); err != nil {
395 log.Printf("ERROR: Code for hash is missing: %q\n", err)
396 }
397 return code
398}
399
400func workspaceHandler(w http.ResponseWriter, r *http.Request) {
401 log.Printf("Workspace Handler: %q\n", r.URL.Path)
402 if r.Method == "GET" {
403 tries := []Try{}
404 match := workspaceLink.FindStringSubmatch(r.URL.Path)
405 name := ""
406 if len(match) == 2 {
407 name = match[1]
408 rows, err := db.Query("SELECT create_ts, hash FROM workspacetry WHERE name=? ORDER BY create_ts DESC ", name)
409 if err != nil {
410 reportError(w, r, err, "Failed to select.")
411 return
412 }
413 for rows.Next() {
414 var hash string
415 var create_ts time.Time
416 if err := rows.Scan(&create_ts, &hash); err != nil {
417 log.Printf("Error: failed to fetch from database: %q", err)
418 continue
419 }
420 tries = append(tries, Try{Hash: hash, CreateTS: create_ts.Format("2006-02-01")})
421 }
422 }
423 var code string
424 if len(tries) == 0 {
425 code = DEFAULT_SAMPLE
426 } else {
427 code = getCode(tries[len(tries)-1].Hash)
428 }
429 if err := workspaceTemplate.Execute(w, Workspace{Tries: tries, Code: code, Name: name}); err != nil {
430 log.Printf("ERROR: Failed to expand template: %q\n", err)
431 }
432 } else if r.Method == "POST" {
433 name, err := newWorkspace()
434 if err != nil {
435 http.Error(w, "Failed to create a new workspace.", 500)
436 return
437 }
438 http.Redirect(w, r, "/w/"+name, 302)
439 }
440}
441
commit-bot@chromium.org4bd8fdc2014-04-15 00:43:51 +0000442// hasPreProcessor returns true if any line in the code begins with a # char.
443func hasPreProcessor(code string) bool {
444 lines := strings.Split(code, "\n")
445 for _, s := range lines {
446 if strings.HasPrefix(strings.TrimSpace(s), "#") {
447 return true
448 }
449 }
450 return false
451}
452
commit-bot@chromium.orgd04e1dd2014-04-19 13:55:50 +0000453type TryRequest struct {
454 Code string `json:"code"`
455 Name string `json:"name"`
456}
457
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +0000458// mainHandler handles the GET and POST of the main page.
459func mainHandler(w http.ResponseWriter, r *http.Request) {
commit-bot@chromium.org06aca012014-04-14 20:12:08 +0000460 log.Printf("Main Handler: %q\n", r.URL.Path)
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +0000461 if r.Method == "GET" {
commit-bot@chromium.orgc81d1c42014-04-14 18:53:10 +0000462 code := DEFAULT_SAMPLE
commit-bot@chromium.orgc81d1c42014-04-14 18:53:10 +0000463 match := directLink.FindStringSubmatch(r.URL.Path)
commit-bot@chromium.org06aca012014-04-14 20:12:08 +0000464 if len(match) == 2 && r.URL.Path != "/" {
commit-bot@chromium.orgc81d1c42014-04-14 18:53:10 +0000465 hash := match[1]
466 if db == nil {
467 http.NotFound(w, r)
468 return
469 }
470 // Update 'code' with the code found in the database.
471 if err := db.QueryRow("SELECT code FROM webtry WHERE hash=?", hash).Scan(&code); err != nil {
472 http.NotFound(w, r)
473 return
474 }
475 }
476 // Expand the template.
commit-bot@chromium.org06aca012014-04-14 20:12:08 +0000477 if err := indexTemplate.Execute(w, userCode{UserCode: code}); err != nil {
commit-bot@chromium.orgc81d1c42014-04-14 18:53:10 +0000478 log.Printf("ERROR: Failed to expand template: %q\n", err)
479 }
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +0000480 } else if r.Method == "POST" {
481 w.Header().Set("Content-Type", "application/json")
commit-bot@chromium.org4bd8fdc2014-04-15 00:43:51 +0000482 buf := bytes.NewBuffer(make([]byte, 0, MAX_TRY_SIZE))
483 n, err := buf.ReadFrom(r.Body)
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +0000484 if err != nil {
485 reportError(w, r, err, "Failed to read a request body.")
486 return
487 }
commit-bot@chromium.org4bd8fdc2014-04-15 00:43:51 +0000488 if n == MAX_TRY_SIZE {
489 err := fmt.Errorf("Code length equal to, or exceeded, %d", MAX_TRY_SIZE)
490 reportError(w, r, err, "Code too large.")
491 return
492 }
commit-bot@chromium.orgd04e1dd2014-04-19 13:55:50 +0000493 request := TryRequest{}
494 if err := json.Unmarshal(buf.Bytes(), &request); err != nil {
495 reportError(w, r, err, "Coulnd't decode JSON.")
496 return
497 }
498 if hasPreProcessor(request.Code) {
commit-bot@chromium.org4bd8fdc2014-04-15 00:43:51 +0000499 err := fmt.Errorf("Found preprocessor macro in code.")
500 reportError(w, r, err, "Preprocessor macros aren't allowed.")
501 return
502 }
commit-bot@chromium.orgd04e1dd2014-04-19 13:55:50 +0000503 hash, err := expandCode(LineNumbers(request.Code))
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +0000504 if err != nil {
505 reportError(w, r, err, "Failed to write the code to compile.")
506 return
507 }
commit-bot@chromium.orgd04e1dd2014-04-19 13:55:50 +0000508 writeToDatabase(hash, request.Code, request.Name)
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +0000509 message, err := doCmd(fmt.Sprintf(RESULT_COMPILE, hash, hash), true)
510 if err != nil {
511 reportError(w, r, err, "Failed to compile the code:\n"+message)
512 return
513 }
514 linkMessage, err := doCmd(fmt.Sprintf(LINK, hash, hash), true)
515 if err != nil {
516 reportError(w, r, err, "Failed to link the code:\n"+linkMessage)
517 return
518 }
519 message += linkMessage
520 cmd := hash + " --out " + hash + ".png"
521 if *useChroot {
522 cmd = "schroot -c webtry --directory=/inout -- /inout/" + cmd
523 } else {
524 abs, err := filepath.Abs("../../../inout")
525 if err != nil {
526 reportError(w, r, err, "Failed to find executable directory.")
527 return
528 }
529 cmd = abs + "/" + cmd
530 }
531
532 execMessage, err := doCmd(cmd, false)
533 if err != nil {
534 reportError(w, r, err, "Failed to run the code:\n"+execMessage)
535 return
536 }
537 png, err := ioutil.ReadFile("../../../inout/" + hash + ".png")
538 if err != nil {
539 reportError(w, r, err, "Failed to open the generated PNG.")
540 return
541 }
542
543 m := response{
544 Message: message,
545 Img: base64.StdEncoding.EncodeToString([]byte(png)),
commit-bot@chromium.orgc81d1c42014-04-14 18:53:10 +0000546 Hash: hash,
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +0000547 }
548 resp, err := json.Marshal(m)
549 if err != nil {
550 reportError(w, r, err, "Failed to serialize a response.")
551 return
552 }
553 w.Write(resp)
554 }
555}
556
557func main() {
558 flag.Parse()
commit-bot@chromium.org06aca012014-04-14 20:12:08 +0000559 http.HandleFunc("/i/", imageHandler)
commit-bot@chromium.orgd04e1dd2014-04-19 13:55:50 +0000560 http.HandleFunc("/w/", workspaceHandler)
commit-bot@chromium.org06aca012014-04-14 20:12:08 +0000561 http.HandleFunc("/recent/", recentHandler)
562 http.HandleFunc("/css/", cssHandler)
commit-bot@chromium.orgd04e1dd2014-04-19 13:55:50 +0000563 http.HandleFunc("/js/", jsHandler)
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +0000564 http.HandleFunc("/", mainHandler)
commit-bot@chromium.org282333f2014-04-14 14:54:07 +0000565 log.Fatal(http.ListenAndServe(*port, nil))
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +0000566}