Initial commit.
commit
3a15f46e5a
|
@ -0,0 +1,23 @@
|
||||||
|
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||||
|
*.o
|
||||||
|
*.a
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Folders
|
||||||
|
_obj
|
||||||
|
_test
|
||||||
|
|
||||||
|
# Architecture specific extensions/prefixes
|
||||||
|
*.[568vq]
|
||||||
|
[568vq].out
|
||||||
|
|
||||||
|
*.cgo1.go
|
||||||
|
*.cgo2.c
|
||||||
|
_cgo_defun.c
|
||||||
|
_cgo_gotypes.go
|
||||||
|
_cgo_export.*
|
||||||
|
|
||||||
|
_testmain.go
|
||||||
|
|
||||||
|
*.exe
|
||||||
|
*.test
|
|
@ -0,0 +1,71 @@
|
||||||
|
package css
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
"github.com/gorilla/css/scanner"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ParseCSS(cssString string, log io.Writer) (result []*CSSToken, token *scanner.Token, err error) {
|
||||||
|
cssScanner := &CSSParser{scanner.New(cssString)}
|
||||||
|
result = make([]*CSSToken, 0)
|
||||||
|
io.WriteString(log, "/*******************\n")
|
||||||
|
io.WriteString(log, "PARSER LOG:\n")
|
||||||
|
token = cssScanner.tolerantRead(result)
|
||||||
|
generalLoop:
|
||||||
|
for token.Type != scanner.TokenEOF && token.Type != scanner.TokenError {
|
||||||
|
token = cssScanner.Next()
|
||||||
|
|
||||||
|
// process token
|
||||||
|
switch {
|
||||||
|
case token.Type == scanner.TokenIdent,
|
||||||
|
token.Type == scanner.TokenChar,
|
||||||
|
token.Type == scanner.TokenHash:
|
||||||
|
var intermediate []*CSSToken
|
||||||
|
|
||||||
|
rule := &CSSToken{Type: TokenType_Rule, SubTokens: []*CSSToken{}}
|
||||||
|
|
||||||
|
// selectors
|
||||||
|
intermediate, token, err = cssScanner.readSelectors(token)
|
||||||
|
if err != nil {
|
||||||
|
break generalLoop
|
||||||
|
}
|
||||||
|
rule.SubTokens = append(rule.SubTokens, intermediate...)
|
||||||
|
io.WriteString(log, spew.Sdump(intermediate)+"\n")
|
||||||
|
|
||||||
|
// "{"
|
||||||
|
if token.Type != scanner.TokenChar || token.Value != "{" {
|
||||||
|
err = fmt.Errorf("expected closure begin for rule but got %s", token.String())
|
||||||
|
break generalLoop
|
||||||
|
}
|
||||||
|
rule.SubTokens = append(rule.SubTokens, &CSSToken{Type: TokenType_Native, NativeToken: token})
|
||||||
|
|
||||||
|
// closure content
|
||||||
|
intermediate, token, err = cssScanner.readRuleClosure()
|
||||||
|
if err != nil {
|
||||||
|
break generalLoop
|
||||||
|
}
|
||||||
|
rule.SubTokens = append(rule.SubTokens, intermediate...)
|
||||||
|
|
||||||
|
// "}"
|
||||||
|
if token.Type != scanner.TokenChar || token.Value != "}" {
|
||||||
|
err = fmt.Errorf("expected closure end for rule but got %s", token.String())
|
||||||
|
break generalLoop
|
||||||
|
}
|
||||||
|
rule.SubTokens = append(rule.SubTokens, &CSSToken{Type: TokenType_Native, NativeToken: token})
|
||||||
|
|
||||||
|
result = append(result, rule)
|
||||||
|
|
||||||
|
default:
|
||||||
|
result = append(result, &CSSToken{Type: TokenType_Native, NativeToken: token})
|
||||||
|
}
|
||||||
|
|
||||||
|
io.WriteString(log, token.String()+"\n")
|
||||||
|
}
|
||||||
|
io.WriteString(log, token.String()+"\n")
|
||||||
|
io.WriteString(log, "*******************/\n\n")
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,127 @@
|
||||||
|
package css
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/gorilla/css/scanner"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CSSParser struct {
|
||||||
|
*scanner.Scanner
|
||||||
|
}
|
||||||
|
|
||||||
|
func (parser *CSSParser) tolerantRead(extraTokens []*CSSToken) (token *scanner.Token) {
|
||||||
|
tokenLoop:
|
||||||
|
for {
|
||||||
|
token = parser.Scanner.Next()
|
||||||
|
switch {
|
||||||
|
case isNativeNonMachineContent(token):
|
||||||
|
if extraTokens != nil {
|
||||||
|
extraTokens = append(extraTokens, &CSSToken{Type: TokenType_Native, NativeToken: token})
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break tokenLoop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (parser *CSSParser) readPropertyValue() (result *CSSToken, token *scanner.Token, err error) {
|
||||||
|
result = &CSSToken{Type: TokenType_PropertyValue, SubTokens: []*CSSToken{}}
|
||||||
|
propValueLoop:
|
||||||
|
for {
|
||||||
|
token = parser.tolerantRead(result.SubTokens)
|
||||||
|
switch {
|
||||||
|
case token.Type == scanner.TokenChar && token.Value == "}" || token.Value == ";": // closure or prop end
|
||||||
|
break propValueLoop
|
||||||
|
case token.Type == scanner.TokenEOF || token.Type == scanner.TokenError:
|
||||||
|
err = fmt.Errorf("expected continuation of property value but got %s", token.String())
|
||||||
|
default:
|
||||||
|
result.SubTokens = append(result.SubTokens, &CSSToken{Type: TokenType_Native, NativeToken: token})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ",selector,selector,...selector{"
|
||||||
|
func (parser *CSSParser) readSelectors(currentToken *scanner.Token) (result []*CSSToken, token *scanner.Token, err error) {
|
||||||
|
result = []*CSSToken{}
|
||||||
|
currentSelector := &CSSToken{Type: TokenType_Selector, SubTokens: []*CSSToken{}}
|
||||||
|
token = currentToken
|
||||||
|
selectorsLoop:
|
||||||
|
for {
|
||||||
|
switch {
|
||||||
|
case token.Type == scanner.TokenChar && token.Value == "{": // closure begin
|
||||||
|
result = append(result,
|
||||||
|
currentSelector)
|
||||||
|
break selectorsLoop
|
||||||
|
case token.Type == scanner.TokenChar && token.Value == ",": // new selector upcoming
|
||||||
|
result = append(result,
|
||||||
|
currentSelector,
|
||||||
|
&CSSToken{Type: TokenType_Native, NativeToken: token})
|
||||||
|
currentSelector = &CSSToken{Type: TokenType_Selector, SubTokens: []*CSSToken{}}
|
||||||
|
case token.Type == scanner.TokenS && len(currentSelector.SubTokens) <= 0: // space, current selector still empty
|
||||||
|
result = append(result,
|
||||||
|
&CSSToken{Type: TokenType_Native, NativeToken: token})
|
||||||
|
case token.Type == scanner.TokenEOF || token.Type == scanner.TokenError:
|
||||||
|
err = fmt.Errorf("expected selector but got %s", token.String())
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
currentSelector.SubTokens = append(currentSelector.SubTokens, &CSSToken{Type: TokenType_Native, NativeToken: token})
|
||||||
|
}
|
||||||
|
token = parser.tolerantRead(result)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// "prop;prop;...prop}"
|
||||||
|
func (parser *CSSParser) readRuleClosure() (result []*CSSToken, token *scanner.Token, err error) {
|
||||||
|
result = []*CSSToken{}
|
||||||
|
closureLoop:
|
||||||
|
for {
|
||||||
|
token = parser.tolerantRead(result)
|
||||||
|
switch {
|
||||||
|
case token.Type == scanner.TokenChar && token.Value == "}": // closure end
|
||||||
|
break closureLoop
|
||||||
|
case token.Type == scanner.TokenEOF || token.Type == scanner.TokenError:
|
||||||
|
err = fmt.Errorf("expected continuation of rule but got %s", token.String())
|
||||||
|
return
|
||||||
|
case token.Type == scanner.TokenChar,
|
||||||
|
token.Type == scanner.TokenIdent: // property name
|
||||||
|
property := &CSSToken{Type: TokenType_Property, SubTokens: []*CSSToken{}}
|
||||||
|
propName := &CSSToken{Type: TokenType_PropertyName, SubTokens: []*CSSToken{}}
|
||||||
|
propNameLoop:
|
||||||
|
for {
|
||||||
|
switch {
|
||||||
|
case token.Type == scanner.TokenChar && token.Value == ":": // end of prop name
|
||||||
|
property.SubTokens = append(property.SubTokens, propName,
|
||||||
|
&CSSToken{Type: TokenType_Native, NativeToken: token})
|
||||||
|
break propNameLoop
|
||||||
|
default:
|
||||||
|
propName.SubTokens = append(propName.SubTokens,
|
||||||
|
&CSSToken{Type: TokenType_Native, NativeToken: token})
|
||||||
|
}
|
||||||
|
token = parser.tolerantRead(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// prop value
|
||||||
|
var intermediate *CSSToken
|
||||||
|
intermediate, token, err = parser.readPropertyValue()
|
||||||
|
if err != nil {
|
||||||
|
break closureLoop
|
||||||
|
}
|
||||||
|
property.SubTokens = append(property.SubTokens, intermediate)
|
||||||
|
|
||||||
|
result = append(result, property)
|
||||||
|
if token.Value == ";" {
|
||||||
|
result = append(result, &CSSToken{Type: TokenType_Native, NativeToken: token})
|
||||||
|
}
|
||||||
|
if token.Value == "}" {
|
||||||
|
break closureLoop
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
result = append(result, &CSSToken{Type: TokenType_Native, NativeToken: token})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
package css
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gorilla/css/scanner"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CSSToken struct {
|
||||||
|
Type tokenType
|
||||||
|
NativeToken *scanner.Token `json:"NativeToken,omitempty"`
|
||||||
|
SubTokens []*CSSToken `json:"SubTokens,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (token *CSSToken) Value() string {
|
||||||
|
if token.NativeToken != nil {
|
||||||
|
return token.NativeToken.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
value := ""
|
||||||
|
for _, token := range token.SubTokens {
|
||||||
|
value += token.Value()
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (token *CSSToken) NonMachineContent() bool {
|
||||||
|
switch token.Type {
|
||||||
|
case TokenType_Native:
|
||||||
|
switch {
|
||||||
|
case isNativeNonMachineContent(token.NativeToken):
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (token *CSSToken) MachineSubTokens() (result []*CSSToken) {
|
||||||
|
result = make([]*CSSToken, 0)
|
||||||
|
for _, token := range token.SubTokens {
|
||||||
|
if token.NonMachineContent() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
result = append(result, token)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (token *CSSToken) Find(tokenTypes ...tokenType) *CSSToken {
|
||||||
|
for _, token := range token.SubTokens {
|
||||||
|
for _, tokenType := range tokenTypes {
|
||||||
|
if token.Type == tokenType {
|
||||||
|
return token
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (token *CSSToken) String() string {
|
||||||
|
retval := fmt.Sprintf("type=%s native=%s", tokenTypeNames[token.Type], token.NativeToken)
|
||||||
|
if token.SubTokens != nil {
|
||||||
|
retval += " {\n"
|
||||||
|
for _, token := range token.SubTokens {
|
||||||
|
retval += "\t" + strings.Replace(token.String(), "\n", "\n\t", -1) + "\n"
|
||||||
|
}
|
||||||
|
retval += "}"
|
||||||
|
}
|
||||||
|
return retval
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package css
|
||||||
|
|
||||||
|
type tokenType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
TokenType_Native tokenType = "native"
|
||||||
|
TokenType_Rule = "rule"
|
||||||
|
TokenType_Selector = "selector"
|
||||||
|
TokenType_Closure = "closure"
|
||||||
|
TokenType_Property = "property"
|
||||||
|
TokenType_PropertyName = "propertyName"
|
||||||
|
TokenType_PropertyValue = "propertyValue"
|
||||||
|
)
|
||||||
|
|
||||||
|
var tokenTypeNames = map[tokenType]string{
|
||||||
|
TokenType_Native: "native",
|
||||||
|
TokenType_Rule: "rule",
|
||||||
|
TokenType_Selector: "selector",
|
||||||
|
TokenType_Closure: "closure",
|
||||||
|
TokenType_Property: "property",
|
||||||
|
TokenType_PropertyName: "propertyName",
|
||||||
|
TokenType_PropertyValue: "propertyValue",
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
package css
|
||||||
|
|
||||||
|
import "github.com/gorilla/css/scanner"
|
||||||
|
|
||||||
|
func isNativeNonMachineContent(token *scanner.Token) bool {
|
||||||
|
return token.Type == scanner.TokenComment || token.Type == scanner.TokenS
|
||||||
|
}
|
|
@ -0,0 +1,200 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/gorilla/css/scanner"
|
||||||
|
"gopkg.in/alecthomas/kingpin.v2"
|
||||||
|
|
||||||
|
"git.icedream.tech/icedream/remote-darkness/internal/css"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
cli = kingpin.New("remote-darkness-server", "Transforms CSS and linked images on the fly to darken websites.")
|
||||||
|
flagListenAddr = cli.Arg("addr", "The address for the server to listen on.").Default(":8080").TCP()
|
||||||
|
)
|
||||||
|
|
||||||
|
var httpClient = http.DefaultClient
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
kingpin.MustParse(cli.Parse(os.Args[1:]))
|
||||||
|
}
|
||||||
|
|
||||||
|
func must(err error) {
|
||||||
|
if err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
httpRouter := gin.New()
|
||||||
|
httpRouter.Any("url/:protocol/:host/*path", handlePathRequest)
|
||||||
|
httpRouter.POST("inline/css", handleInlineCssRequest)
|
||||||
|
|
||||||
|
httpServer := new(http.Server)
|
||||||
|
httpServer.Addr = (*flagListenAddr).String()
|
||||||
|
httpServer.Handler = httpRouter
|
||||||
|
must(httpServer.ListenAndServe())
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleCss(context *gin.Context, cssString string) {
|
||||||
|
tokens, token, err := css.ParseCSS(cssString, ioutil.Discard)
|
||||||
|
// context.Writer.WriteString("/*******************\n")
|
||||||
|
// context.Writer.WriteString("TOKEN LOG:\n")
|
||||||
|
// buf := new(bytes.Buffer)
|
||||||
|
|
||||||
|
// if err != nil {
|
||||||
|
// context.Writer.WriteString(fmt.Sprintf("ERROR: %s in line %d, col %d\n", err, token.Line, token.Column))
|
||||||
|
// context.Writer.WriteString("\n")
|
||||||
|
// lines := strings.Split(cssString, "\n")
|
||||||
|
// line := lines[token.Line-1]
|
||||||
|
// begin := token.Column - 20 - 1
|
||||||
|
// beginEllipsis := "..."
|
||||||
|
// if begin < 0 {
|
||||||
|
// begin = 0
|
||||||
|
// beginEllipsis = ""
|
||||||
|
// }
|
||||||
|
// end := token.Column + 20 - 1
|
||||||
|
// endEllipsis := "..."
|
||||||
|
// if end > len(line) {
|
||||||
|
// end = len(line)
|
||||||
|
// endEllipsis = ""
|
||||||
|
// }
|
||||||
|
// marker := strings.Repeat(" ", (token.Column-begin)-1+len(beginEllipsis)) + "^"
|
||||||
|
// context.Writer.WriteString(beginEllipsis + line[begin:end] + endEllipsis + "\n")
|
||||||
|
// context.Writer.WriteString(marker + "\n")
|
||||||
|
// context.Writer.WriteString("\n")
|
||||||
|
// }
|
||||||
|
|
||||||
|
// for _, token := range tokens {
|
||||||
|
// context.Writer.WriteString(token.String() + "\n")
|
||||||
|
// buf.WriteString(token.Value())
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if err != nil {
|
||||||
|
// context.Writer.WriteString("\n")
|
||||||
|
// context.Writer.WriteString(fmt.Sprintf("ERROR: %s in line %d, col %d\n", err, token.Line, token.Column))
|
||||||
|
// context.Status(http.StatusFailedDependency)
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
// context.Writer.WriteString("*******************/\n\n")
|
||||||
|
// copyHeaders(
|
||||||
|
// "Content-type",
|
||||||
|
// "Content-length")
|
||||||
|
// context.Header("Content-type", "text/css; charset=utf-8")
|
||||||
|
// io.Copy(context.Writer, buf)
|
||||||
|
errStr := ""
|
||||||
|
if err != nil {
|
||||||
|
errStr = err.Error()
|
||||||
|
}
|
||||||
|
context.JSON(200, struct {
|
||||||
|
Tokens []*css.CSSToken
|
||||||
|
LastToken *scanner.Token `json:"LastToken,omitempty"`
|
||||||
|
Error string `json:"Error,omitempty"`
|
||||||
|
}{
|
||||||
|
Tokens: tokens,
|
||||||
|
LastToken: token,
|
||||||
|
Error: errStr,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleInlineCssRequest(context *gin.Context) {
|
||||||
|
defer context.Request.Body.Close()
|
||||||
|
// TODO - limit size since we are reading everything into a string at once
|
||||||
|
cssBytes, err := ioutil.ReadAll(context.Request.Body)
|
||||||
|
if err != nil {
|
||||||
|
context.AbortWithError(http.StatusPreconditionFailed, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// TODO - support other encodings than UTF-8 properly
|
||||||
|
cssString := string(cssBytes)
|
||||||
|
handleCss(context, cssString)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handlePathRequest(context *gin.Context) {
|
||||||
|
urlProtocol := context.Param("protocol")
|
||||||
|
urlHost := context.Param("host")
|
||||||
|
urlPath := context.Param("path")
|
||||||
|
|
||||||
|
// filter colon and slashes from protocol
|
||||||
|
if strings.Contains(urlProtocol, ":") {
|
||||||
|
urlProtocol = strings.SplitN(urlProtocol, ":", 2)[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// filter leading slashes from path
|
||||||
|
urlPath = strings.TrimLeft(urlPath, "/")
|
||||||
|
|
||||||
|
u, err := url.Parse(fmt.Sprintf("%s://%s/%s",
|
||||||
|
urlProtocol,
|
||||||
|
urlHost,
|
||||||
|
urlPath))
|
||||||
|
if err != nil {
|
||||||
|
context.String(http.StatusBadRequest, fmt.Sprintf("Can not parse URL: %s", err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// open connection to download file from URL
|
||||||
|
log.Printf("%s %s", context.Request.Method, u)
|
||||||
|
downloadRequest, err := http.NewRequest(context.Request.Method, u.String(), context.Request.Body)
|
||||||
|
if err != nil {
|
||||||
|
context.String(http.StatusBadRequest, fmt.Sprintf("Can not create request: %s", err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
downloadResponse, err := httpClient.Do(downloadRequest)
|
||||||
|
if err != nil {
|
||||||
|
context.AbortWithError(http.StatusFailedDependency, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer downloadResponse.Body.Close()
|
||||||
|
|
||||||
|
copyHeaders := func(except ...string) {
|
||||||
|
headerLoop:
|
||||||
|
for key, values := range downloadResponse.Header {
|
||||||
|
for _, exceptKey := range except {
|
||||||
|
if strings.EqualFold(exceptKey, key) {
|
||||||
|
continue headerLoop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, value := range values {
|
||||||
|
context.Header(key, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if downloadResponse.StatusCode != 200 {
|
||||||
|
context.Status(downloadResponse.StatusCode)
|
||||||
|
copyHeaders()
|
||||||
|
io.Copy(context.Writer, downloadResponse.Body)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
contentType := strings.Split(downloadResponse.Header.Get("Content-type"), ";")
|
||||||
|
// TODO - images
|
||||||
|
switch strings.ToLower(contentType[0]) {
|
||||||
|
case "application/css", "text/css": // Cascading Stylesheet
|
||||||
|
// TODO - limit size since we are reading everything into a string at once
|
||||||
|
cssBytes, err := ioutil.ReadAll(downloadResponse.Body)
|
||||||
|
if err != nil {
|
||||||
|
context.AbortWithError(http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// TODO - support other encodings than UTF-8 properly
|
||||||
|
cssString := string(cssBytes)
|
||||||
|
handleCss(context, cssString)
|
||||||
|
default: // All other content types
|
||||||
|
// Just forward as-is
|
||||||
|
context.Status(downloadResponse.StatusCode)
|
||||||
|
copyHeaders()
|
||||||
|
io.Copy(context.Writer, downloadResponse.Body)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue