add ipa qrcode generate support
This commit is contained in:
@@ -5,18 +5,21 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
|
"mime"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
)
|
)
|
||||||
|
|
||||||
type HTTPStaticServer struct {
|
type HTTPStaticServer struct {
|
||||||
Root string
|
Root string
|
||||||
Theme string
|
Theme string
|
||||||
Upload bool
|
Upload bool
|
||||||
|
PlistProxy string
|
||||||
|
|
||||||
m *mux.Router
|
m *mux.Router
|
||||||
}
|
}
|
||||||
@@ -34,6 +37,7 @@ func NewHTTPStaticServer(root string) *HTTPStaticServer {
|
|||||||
m.HandleFunc("/-/status", s.hStatus)
|
m.HandleFunc("/-/status", s.hStatus)
|
||||||
m.HandleFunc("/-/raw/{path:.*}", s.hFileOrDirectory)
|
m.HandleFunc("/-/raw/{path:.*}", s.hFileOrDirectory)
|
||||||
m.HandleFunc("/-/zip/{path:.*}", s.hZip)
|
m.HandleFunc("/-/zip/{path:.*}", s.hZip)
|
||||||
|
m.HandleFunc("/-/unzip/{zip_path:.*}/-/{path:.*}", s.hUnzip)
|
||||||
m.HandleFunc("/-/json/{path:.*}", s.hJSONList)
|
m.HandleFunc("/-/json/{path:.*}", s.hJSONList)
|
||||||
// routers for Apple *.ipa
|
// routers for Apple *.ipa
|
||||||
m.HandleFunc("/-/ipa/icon/{path:.*}", s.hIpaIcon)
|
m.HandleFunc("/-/ipa/icon/{path:.*}", s.hIpaIcon)
|
||||||
@@ -101,7 +105,8 @@ func (s *HTTPStaticServer) hIndex(w http.ResponseWriter, r *http.Request) {
|
|||||||
relPath := filepath.Join(s.Root, path)
|
relPath := filepath.Join(s.Root, path)
|
||||||
finfo, err := os.Stat(relPath)
|
finfo, err := os.Stat(relPath)
|
||||||
if err == nil && finfo.IsDir() {
|
if err == nil && finfo.IsDir() {
|
||||||
tmpl.Execute(w, s)
|
tmpl.ExecuteTemplate(w, "index", s)
|
||||||
|
// tmpl.Execute(w, s)
|
||||||
} else {
|
} else {
|
||||||
http.ServeFile(w, r, relPath)
|
http.ServeFile(w, r, relPath)
|
||||||
}
|
}
|
||||||
@@ -112,7 +117,22 @@ func (s *HTTPStaticServer) hZip(w http.ResponseWriter, r *http.Request) {
|
|||||||
CompressToZip(w, filepath.Join(s.Root, path))
|
CompressToZip(w, filepath.Join(s.Root, path))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *HTTPStaticServer) hUnzip(w http.ResponseWriter, r *http.Request) {
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
zipPath, path := vars["zip_path"], vars["path"]
|
||||||
|
ctype := mime.TypeByExtension(filepath.Ext(path))
|
||||||
|
if ctype != "" {
|
||||||
|
w.Header().Set("Content-Type", ctype)
|
||||||
|
}
|
||||||
|
err := ExtractFromZip(filepath.Join(s.Root, zipPath), path, w)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (s *HTTPStaticServer) hIpaIcon(w http.ResponseWriter, r *http.Request) {
|
func (s *HTTPStaticServer) hIpaIcon(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// Useless now.
|
||||||
path := mux.Vars(r)["path"]
|
path := mux.Vars(r)["path"]
|
||||||
relPath := filepath.Join(s.Root, path)
|
relPath := filepath.Join(s.Root, path)
|
||||||
data, err := parseIpaIcon(relPath)
|
data, err := parseIpaIcon(relPath)
|
||||||
@@ -124,6 +144,18 @@ func (s *HTTPStaticServer) hIpaIcon(w http.ResponseWriter, r *http.Request) {
|
|||||||
w.Write(data)
|
w.Write(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func genURLStr(r *http.Request, path string) *url.URL {
|
||||||
|
scheme := "http"
|
||||||
|
if r.TLS != nil {
|
||||||
|
scheme = "https"
|
||||||
|
}
|
||||||
|
return &url.URL{
|
||||||
|
Scheme: scheme,
|
||||||
|
Host: r.Host,
|
||||||
|
Path: path,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (s *HTTPStaticServer) hPlist(w http.ResponseWriter, r *http.Request) {
|
func (s *HTTPStaticServer) hPlist(w http.ResponseWriter, r *http.Request) {
|
||||||
path := mux.Vars(r)["path"]
|
path := mux.Vars(r)["path"]
|
||||||
// rename *.plist to *.ipa
|
// rename *.plist to *.ipa
|
||||||
@@ -137,22 +169,16 @@ func (s *HTTPStaticServer) hPlist(w http.ResponseWriter, r *http.Request) {
|
|||||||
http.Error(w, err.Error(), 500)
|
http.Error(w, err.Error(), 500)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
scheme := "http"
|
scheme := "http"
|
||||||
if r.TLS != nil {
|
if r.TLS != nil {
|
||||||
scheme = "https"
|
scheme = "https"
|
||||||
}
|
}
|
||||||
ipaURL := url.URL{
|
baseURL := &url.URL{
|
||||||
Scheme: scheme,
|
Scheme: scheme,
|
||||||
Host: r.Host,
|
Host: r.Host,
|
||||||
Path: path,
|
|
||||||
}
|
}
|
||||||
imgURL := url.URL{
|
data, err := generateDownloadPlist(baseURL, path, plinfo)
|
||||||
Scheme: scheme,
|
|
||||||
Host: r.Host,
|
|
||||||
Path: filepath.Join("/-/ipa/icon", path),
|
|
||||||
}
|
|
||||||
// TODO: image ignore here.
|
|
||||||
data, err := generateDownloadPlist(ipaURL.String(), imgURL.String(), plinfo)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), 500)
|
http.Error(w, err.Error(), 500)
|
||||||
return
|
return
|
||||||
@@ -162,12 +188,20 @@ func (s *HTTPStaticServer) hPlist(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *HTTPStaticServer) hIpaLink(w http.ResponseWriter, r *http.Request) {
|
func (s *HTTPStaticServer) hIpaLink(w http.ResponseWriter, r *http.Request) {
|
||||||
// need ua_parser
|
path := mux.Vars(r)["path"]
|
||||||
w.Write([]byte("redirect to itms service")) // url need urjJoin if setted plist proxy
|
plistUrl := genURLStr(r, "/-/ipa/plist/"+path).String()
|
||||||
|
if s.PlistProxy != "" {
|
||||||
|
plistUrl = strings.TrimSuffix(s.PlistProxy, "/") + "/" + r.Host + "/-/ipa/plist/" + path
|
||||||
|
}
|
||||||
|
|
||||||
// WeChat need to tell to open with Safari
|
w.Header().Set("Content-Type", "text/html")
|
||||||
|
tmpl.ExecuteTemplate(w, "ipa-install", map[string]string{
|
||||||
// Browser just tell not supported.
|
"Name": filepath.Base(path),
|
||||||
|
"PlistLink": plistUrl,
|
||||||
|
})
|
||||||
|
// w.Write([]byte(fmt.Sprintf(
|
||||||
|
// `<a href='itms-services://?action=download-manifest&url=%s'>Click this link to install</a>`,
|
||||||
|
// plistUrl)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *HTTPStaticServer) hFileOrDirectory(w http.ResponseWriter, r *http.Request) {
|
func (s *HTTPStaticServer) hFileOrDirectory(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|||||||
276
ipa.go
276
ipa.go
@@ -1,133 +1,143 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"archive/zip"
|
"archive/zip"
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"path/filepath"
|
"net/url"
|
||||||
"regexp"
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
goplist "github.com/DHowett/go-plist"
|
|
||||||
)
|
goplist "github.com/DHowett/go-plist"
|
||||||
|
)
|
||||||
type plistBundle struct {
|
|
||||||
CFBundleIdentifier string `plist:"CFBundleIdentifier"`
|
func parseIpaIcon(path string) (data []byte, err error) {
|
||||||
CFBundleVersion string `plist:"CFBundleVersion"`
|
iconPattern := regexp.MustCompile(`(?i)^Payload/[^/]*/icon\.png$`)
|
||||||
CFBundleDisplayName string `plist:"CFBundleDisplayName"`
|
r, err := zip.OpenReader(path)
|
||||||
CFBundleIconFile string `plist:"CFBundleIconFile"`
|
if err != nil {
|
||||||
}
|
return
|
||||||
|
}
|
||||||
func parseIpaIcon(path string) (data []byte, err error) {
|
defer r.Close()
|
||||||
iconPattern := regexp.MustCompile(`(?i)^Payload/[^/]*/icon\.png$`)
|
|
||||||
r, err := zip.OpenReader(path)
|
var zfile *zip.File
|
||||||
if err != nil {
|
for _, file := range r.File {
|
||||||
return
|
if iconPattern.MatchString(file.Name) {
|
||||||
}
|
zfile = file
|
||||||
defer r.Close()
|
break
|
||||||
|
}
|
||||||
var zfile *zip.File
|
}
|
||||||
for _, file := range r.File {
|
if zfile == nil {
|
||||||
if iconPattern.MatchString(file.Name) {
|
err = errors.New("icon.png file not found")
|
||||||
zfile = file
|
return
|
||||||
break
|
}
|
||||||
}
|
plreader, err := zfile.Open()
|
||||||
}
|
if err != nil {
|
||||||
if zfile == nil {
|
return
|
||||||
err = errors.New("icon.png file not found")
|
}
|
||||||
return
|
defer plreader.Close()
|
||||||
}
|
return ioutil.ReadAll(plreader)
|
||||||
plreader, err := zfile.Open()
|
}
|
||||||
if err != nil {
|
|
||||||
return
|
func parseIPA(path string) (plinfo *plistBundle, err error) {
|
||||||
}
|
plistre := regexp.MustCompile(`^Payload/[^/]*/Info\.plist$`)
|
||||||
defer plreader.Close()
|
r, err := zip.OpenReader(path)
|
||||||
return ioutil.ReadAll(plreader)
|
if err != nil {
|
||||||
}
|
return
|
||||||
|
}
|
||||||
func parseIPA(path string) (plinfo *plistBundle, err error) {
|
defer r.Close()
|
||||||
plistre := regexp.MustCompile(`^Payload/[^/]*/Info\.plist$`)
|
|
||||||
r, err := zip.OpenReader(path)
|
var plfile *zip.File
|
||||||
if err != nil {
|
for _, file := range r.File {
|
||||||
return
|
if plistre.MatchString(file.Name) {
|
||||||
}
|
plfile = file
|
||||||
defer r.Close()
|
break
|
||||||
|
}
|
||||||
var plfile *zip.File
|
}
|
||||||
for _, file := range r.File {
|
if plfile == nil {
|
||||||
if plistre.MatchString(file.Name) {
|
err = errors.New("Info.plist file not found")
|
||||||
plfile = file
|
return
|
||||||
break
|
}
|
||||||
}
|
plreader, err := plfile.Open()
|
||||||
}
|
if err != nil {
|
||||||
if plfile == nil {
|
return
|
||||||
err = errors.New("Info.plist file not found")
|
}
|
||||||
return
|
defer plreader.Close()
|
||||||
}
|
buf := make([]byte, plfile.FileInfo().Size())
|
||||||
plreader, err := plfile.Open()
|
_, err = io.ReadFull(plreader, buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer plreader.Close()
|
dec := goplist.NewDecoder(bytes.NewReader(buf))
|
||||||
buf := make([]byte, plfile.FileInfo().Size())
|
plinfo = new(plistBundle)
|
||||||
_, err = io.ReadFull(plreader, buf)
|
err = dec.Decode(plinfo)
|
||||||
if err != nil {
|
return
|
||||||
return
|
}
|
||||||
}
|
|
||||||
dec := goplist.NewDecoder(bytes.NewReader(buf))
|
type plistBundle struct {
|
||||||
plinfo = new(plistBundle)
|
CFBundleIdentifier string `plist:"CFBundleIdentifier"`
|
||||||
err = dec.Decode(plinfo)
|
CFBundleVersion string `plist:"CFBundleVersion"`
|
||||||
return
|
CFBundleDisplayName string `plist:"CFBundleDisplayName"`
|
||||||
}
|
CFBundleName string `plist:"CFBundleName"`
|
||||||
|
CFBundleIconFile string `plist:"CFBundleIconFile"`
|
||||||
// ref: https://gist.github.com/frischmilch/b15d81eabb67925642bd#file_manifest.plist
|
CFBundleIcons struct {
|
||||||
type plAsset struct {
|
CFBundlePrimaryIcon struct {
|
||||||
Kind string `plist:"kind"`
|
CFBundleIconFiles []string `plist:"CFBundleIconFiles"`
|
||||||
URL string `plist:"url"`
|
} `plist:"CFBundlePrimaryIcon"`
|
||||||
}
|
} `plist:"CFBundleIcons"`
|
||||||
|
}
|
||||||
type plItem struct {
|
|
||||||
Assets []*plAsset `plist:"assets"`
|
// ref: https://gist.github.com/frischmilch/b15d81eabb67925642bd#file_manifest.plist
|
||||||
Metadata struct {
|
type plAsset struct {
|
||||||
BundleIdentifier string `plist:"bundle-identifier"`
|
Kind string `plist:"kind"`
|
||||||
BundleVersion string `plist:"bundle-version"`
|
URL string `plist:"url"`
|
||||||
Kind string `plist:"kind"`
|
}
|
||||||
Title string `plist:"title"`
|
|
||||||
} `plist:"metadata"`
|
type plItem struct {
|
||||||
}
|
Assets []*plAsset `plist:"assets"`
|
||||||
|
Metadata struct {
|
||||||
type downloadPlist struct {
|
BundleIdentifier string `plist:"bundle-identifier"`
|
||||||
Items []*plItem `plist:"items"`
|
BundleVersion string `plist:"bundle-version"`
|
||||||
}
|
Kind string `plist:"kind"`
|
||||||
|
Title string `plist:"title"`
|
||||||
func generateDownloadPlist(ipaUrl, imgUrl string, plinfo *plistBundle) ([]byte, error) {
|
} `plist:"metadata"`
|
||||||
dp := new(downloadPlist)
|
}
|
||||||
item := new(plItem)
|
|
||||||
item.Assets = append(item.Assets, &plAsset{
|
type downloadPlist struct {
|
||||||
Kind: "software-package",
|
Items []*plItem `plist:"items"`
|
||||||
URL: ipaUrl,
|
}
|
||||||
})
|
|
||||||
|
func generateDownloadPlist(baseURL *url.URL, ipaPath string, plinfo *plistBundle) ([]byte, error) {
|
||||||
// FIXME(ssx): find icon from CFBundleIconFile
|
dp := new(downloadPlist)
|
||||||
_ = imgUrl
|
item := new(plItem)
|
||||||
// , &plAsset{
|
baseURL.Path = ipaPath
|
||||||
// Kind: "display-image",
|
ipaUrl := baseURL.String()
|
||||||
// URL: imgUrl,
|
item.Assets = append(item.Assets, &plAsset{
|
||||||
// })
|
Kind: "software-package",
|
||||||
|
URL: ipaUrl,
|
||||||
item.Metadata.Kind = "software"
|
})
|
||||||
|
|
||||||
item.Metadata.BundleIdentifier = plinfo.CFBundleIdentifier
|
iconFiles := plinfo.CFBundleIcons.CFBundlePrimaryIcon.CFBundleIconFiles
|
||||||
item.Metadata.BundleVersion = plinfo.CFBundleVersion
|
if iconFiles != nil && len(iconFiles) > 0 {
|
||||||
item.Metadata.Title = plinfo.CFBundleDisplayName
|
baseURL.Path = "/-/unzip/" + ipaPath + "/-/**/" + iconFiles[0] + ".png"
|
||||||
if item.Metadata.Title == "" {
|
imgUrl := baseURL.String()
|
||||||
item.Metadata.Title = filepath.Base(ipaUrl)
|
item.Assets = append(item.Assets, &plAsset{
|
||||||
}
|
Kind: "display-image",
|
||||||
|
URL: imgUrl,
|
||||||
dp.Items = append(dp.Items, item)
|
})
|
||||||
data, err := goplist.MarshalIndent(dp, goplist.XMLFormat, " ")
|
}
|
||||||
// fmt.Println(string(data))
|
|
||||||
// fmt.Println(err)
|
item.Metadata.Kind = "software"
|
||||||
return data, err
|
|
||||||
}
|
item.Metadata.BundleIdentifier = plinfo.CFBundleIdentifier
|
||||||
|
item.Metadata.BundleVersion = plinfo.CFBundleVersion
|
||||||
|
item.Metadata.Title = plinfo.CFBundleName
|
||||||
|
if item.Metadata.Title == "" {
|
||||||
|
item.Metadata.Title = filepath.Base(ipaUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
dp.Items = append(dp.Items, item)
|
||||||
|
data, err := goplist.MarshalIndent(dp, goplist.XMLFormat, " ")
|
||||||
|
return data, err
|
||||||
|
}
|
||||||
|
|||||||
7
main.go
7
main.go
@@ -25,7 +25,9 @@ type Configure struct {
|
|||||||
PlistProxy *url.URL
|
PlistProxy *url.URL
|
||||||
}
|
}
|
||||||
|
|
||||||
var gcfg = Configure{}
|
var (
|
||||||
|
gcfg = Configure{}
|
||||||
|
)
|
||||||
|
|
||||||
func parseFlags() {
|
func parseFlags() {
|
||||||
kingpin.HelpFlag.Short('h')
|
kingpin.HelpFlag.Short('h')
|
||||||
@@ -52,6 +54,9 @@ func main() {
|
|||||||
if gcfg.Upload {
|
if gcfg.Upload {
|
||||||
ss.EnableUpload()
|
ss.EnableUpload()
|
||||||
}
|
}
|
||||||
|
if gcfg.PlistProxy != nil {
|
||||||
|
ss.PlistProxy = gcfg.PlistProxy.String()
|
||||||
|
}
|
||||||
|
|
||||||
var hdlr http.Handler = ss
|
var hdlr http.Handler = ss
|
||||||
// HTTP Basic Authentication
|
// HTTP Basic Authentication
|
||||||
|
|||||||
24
res.go
Normal file
24
res.go
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "html/template"
|
||||||
|
|
||||||
|
var (
|
||||||
|
tmpl *template.Template
|
||||||
|
templates = map[string]string{
|
||||||
|
"index": "res/index.tmpl.html",
|
||||||
|
"ipa-install": "res/ipa-install.tmpl.html",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func ParseTemplate(name string, content string) {
|
||||||
|
if tmpl == nil {
|
||||||
|
tmpl = template.New(name)
|
||||||
|
}
|
||||||
|
var t *template.Template
|
||||||
|
if tmpl.Name() == name {
|
||||||
|
t = tmpl
|
||||||
|
} else {
|
||||||
|
t = tmpl.New(name)
|
||||||
|
}
|
||||||
|
template.Must(t.New(name).Delims("[[", "]]").Parse(content))
|
||||||
|
}
|
||||||
BIN
res/imgs/wx.png
Normal file
BIN
res/imgs/wx.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
71
res/ipa-install.tmpl.html
Normal file
71
res/ipa-install.tmpl.html
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>[[.Name]] install</title>
|
||||||
|
<meta http-equiv="Content-Type" content="text/HTML; charset=utf-8">
|
||||||
|
<meta content="target-densitydpi=device-dpi,width=640" name="viewport" id="viewport">
|
||||||
|
<link rel="shortcut icon" type="image/png" href="/-/res/favicon.png" />
|
||||||
|
<script type="text/javascript" src="/-/res/js/ua-parser.min.js"></script>
|
||||||
|
<script type="text/javascript">
|
||||||
|
function showById(name) {
|
||||||
|
document.getElementById(name).style.display = 'block';
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkBrowerAndDownload() {
|
||||||
|
var parser = new UAParser();
|
||||||
|
var os_info = parser.getOS();
|
||||||
|
console.log(os_info)
|
||||||
|
|
||||||
|
if (navigator.userAgent.toLowerCase().match(/MicroMessenger/i) == "micromessenger") {
|
||||||
|
showById('wechat');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var plistLink = "[[.PlistLink]]";
|
||||||
|
var ipaInstallLink = 'itms-services://?action=download-manifest&url=' + plistLink;
|
||||||
|
|
||||||
|
document.getElementById('itms-link').href = ipaInstallLink;
|
||||||
|
|
||||||
|
if (os_info.name == 'Android') {
|
||||||
|
return;
|
||||||
|
} else if (os_info.name == 'iOS') {
|
||||||
|
showById('safari');
|
||||||
|
|
||||||
|
location.href = ipaInstallLink;
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
showById('browser');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<style>
|
||||||
|
#wechat {
|
||||||
|
position: relative;
|
||||||
|
width: 640px;
|
||||||
|
margin: 0 auto;
|
||||||
|
background: #fff;
|
||||||
|
overflow: hidden;
|
||||||
|
min-height: 777px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<div id="wechat" style="display: none">
|
||||||
|
<img style='width: 100%;position: relative;' src='/-/res/imgs/wx.png' />
|
||||||
|
</div>
|
||||||
|
<div id="browser" style="display: none">
|
||||||
|
This is IPA install page, you should open this link with your iPhone.
|
||||||
|
</div>
|
||||||
|
<div id="safari" style="display: none">
|
||||||
|
If install not started soon, click <a id="itms-link" href="#">here</a>
|
||||||
|
</div>
|
||||||
|
<div id="android" style="display: none">
|
||||||
|
This is IPA install page, not for android.
|
||||||
|
</div>
|
||||||
|
<script type="text/javascript">
|
||||||
|
checkBrowerAndDownload();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -82,9 +82,14 @@ var vm = new Vue({
|
|||||||
this.showHidden = !this.showHidden;
|
this.showHidden = !this.showHidden;
|
||||||
},
|
},
|
||||||
genQrcode: function(text) {
|
genQrcode: function(text) {
|
||||||
var urlPath = location.protocol + "//" + location.host + location.pathname + text;
|
var urlPath = location.protocol + "//" + pathJoin([location.host, location.pathname, text]);
|
||||||
$("#qrcode-title").html(text);
|
$("#qrcode-title").html(text);
|
||||||
$("#qrcode-link").attr("href", urlPath);
|
$("#qrcode-link").attr("href", urlPath);
|
||||||
|
|
||||||
|
if (getExtention(text) == "ipa") {
|
||||||
|
urlPath = location.protocol + "//" + pathJoin([location.host, "/-/ipa/link", location.pathname, text]);
|
||||||
|
console.log(urlPath)
|
||||||
|
}
|
||||||
$('#qrcodeCanvas').empty().qrcode({
|
$('#qrcodeCanvas').empty().qrcode({
|
||||||
text: urlPath
|
text: urlPath
|
||||||
});
|
});
|
||||||
|
|||||||
9
res/js/ua-parser.min.js
vendored
Normal file
9
res/js/ua-parser.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -1,17 +1,20 @@
|
|||||||
// +build bindata
|
// +build bindata
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"html/template"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
var tmpl *template.Template
|
func init() {
|
||||||
|
http.Handle("/-/res/", http.StripPrefix("/-/res/", http.FileServer(assetFS())))
|
||||||
func init() {
|
|
||||||
http.Handle("/-/res/", http.StripPrefix("/-/res/", http.FileServer(assetFS())))
|
for name, path := range templates {
|
||||||
|
data, err := Asset(path)
|
||||||
indexContent, _ := Asset("res/index.tmpl.html")
|
if err != nil {
|
||||||
tmpl = template.Must(template.New("t").Delims("[[", "]]").Parse(string(indexContent)))
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
ParseTemplate(name, string(data))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,18 +1,21 @@
|
|||||||
// +build !bindata
|
// +build !bindata
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"html/template"
|
"io/ioutil"
|
||||||
"io/ioutil"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
var tmpl *template.Template
|
func init() {
|
||||||
|
http.Handle("/-/res/", http.StripPrefix("/-/res/", http.FileServer(http.Dir("./res"))))
|
||||||
func init() {
|
|
||||||
http.Handle("/-/res/", http.StripPrefix("/-/res/", http.FileServer(http.Dir("./res"))))
|
for name, path := range templates {
|
||||||
|
content, err := ioutil.ReadFile(path)
|
||||||
indexContent, _ := ioutil.ReadFile("./res/index.tmpl.html")
|
if err != nil {
|
||||||
tmpl = template.Must(template.New("t").Delims("[[", "]]").Parse(string(indexContent)))
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
ParseTemplate(name, string(content))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
221
zip.go
221
zip.go
@@ -1,92 +1,129 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"archive/zip"
|
"archive/zip"
|
||||||
"bytes"
|
"bytes"
|
||||||
"io"
|
"fmt"
|
||||||
"io/ioutil"
|
"io"
|
||||||
"net/http"
|
"io/ioutil"
|
||||||
"os"
|
"net/http"
|
||||||
"path/filepath"
|
"os"
|
||||||
"runtime"
|
"path/filepath"
|
||||||
"strings"
|
"runtime"
|
||||||
)
|
"strconv"
|
||||||
|
"strings"
|
||||||
type Zip struct {
|
|
||||||
*zip.Writer
|
dkignore "github.com/codeskyblue/dockerignore"
|
||||||
}
|
)
|
||||||
|
|
||||||
func sanitizedName(filename string) string {
|
type Zip struct {
|
||||||
if len(filename) > 1 && filename[1] == ':' &&
|
*zip.Writer
|
||||||
runtime.GOOS == "windows" {
|
}
|
||||||
filename = filename[2:]
|
|
||||||
}
|
func sanitizedName(filename string) string {
|
||||||
filename = strings.TrimLeft(strings.Replace(filename, `\`, "/", -1), `/`)
|
if len(filename) > 1 && filename[1] == ':' &&
|
||||||
filename = filepath.ToSlash(filename)
|
runtime.GOOS == "windows" {
|
||||||
filename = filepath.Clean(filename)
|
filename = filename[2:]
|
||||||
return filename
|
}
|
||||||
}
|
filename = strings.TrimLeft(strings.Replace(filename, `\`, "/", -1), `/`)
|
||||||
|
filename = filepath.ToSlash(filename)
|
||||||
func statFile(filename string) (info os.FileInfo, reader io.ReadCloser, err error) {
|
filename = filepath.Clean(filename)
|
||||||
info, err = os.Lstat(filename)
|
return filename
|
||||||
if err != nil {
|
}
|
||||||
return
|
|
||||||
}
|
func statFile(filename string) (info os.FileInfo, reader io.ReadCloser, err error) {
|
||||||
// content
|
info, err = os.Lstat(filename)
|
||||||
if info.Mode()&os.ModeSymlink != 0 {
|
if err != nil {
|
||||||
var target string
|
return
|
||||||
target, err = os.Readlink(filename)
|
}
|
||||||
if err != nil {
|
// content
|
||||||
return
|
if info.Mode()&os.ModeSymlink != 0 {
|
||||||
}
|
var target string
|
||||||
reader = ioutil.NopCloser(bytes.NewBuffer([]byte(target)))
|
target, err = os.Readlink(filename)
|
||||||
} else if !info.IsDir() {
|
if err != nil {
|
||||||
reader, err = os.Open(filename)
|
return
|
||||||
if err != nil {
|
}
|
||||||
return
|
reader = ioutil.NopCloser(bytes.NewBuffer([]byte(target)))
|
||||||
}
|
} else if !info.IsDir() {
|
||||||
} else {
|
reader, err = os.Open(filename)
|
||||||
reader = ioutil.NopCloser(bytes.NewBuffer(nil))
|
if err != nil {
|
||||||
}
|
return
|
||||||
return
|
}
|
||||||
}
|
} else {
|
||||||
|
reader = ioutil.NopCloser(bytes.NewBuffer(nil))
|
||||||
func (z *Zip) Add(relpath, abspath string) error {
|
}
|
||||||
info, rdc, err := statFile(abspath)
|
return
|
||||||
if err != nil {
|
}
|
||||||
return err
|
|
||||||
}
|
func (z *Zip) Add(relpath, abspath string) error {
|
||||||
defer rdc.Close()
|
info, rdc, err := statFile(abspath)
|
||||||
|
if err != nil {
|
||||||
hdr, err := zip.FileInfoHeader(info)
|
return err
|
||||||
if err != nil {
|
}
|
||||||
return err
|
defer rdc.Close()
|
||||||
}
|
|
||||||
hdr.Name = sanitizedName(relpath)
|
hdr, err := zip.FileInfoHeader(info)
|
||||||
if info.IsDir() {
|
if err != nil {
|
||||||
hdr.Name += "/"
|
return err
|
||||||
}
|
}
|
||||||
hdr.Method = zip.Deflate // compress method
|
hdr.Name = sanitizedName(relpath)
|
||||||
writer, err := z.CreateHeader(hdr)
|
if info.IsDir() {
|
||||||
if err != nil {
|
hdr.Name += "/"
|
||||||
return err
|
}
|
||||||
}
|
hdr.Method = zip.Deflate // compress method
|
||||||
_, err = io.Copy(writer, rdc)
|
writer, err := z.CreateHeader(hdr)
|
||||||
return err
|
if err != nil {
|
||||||
}
|
return err
|
||||||
|
}
|
||||||
func CompressToZip(w http.ResponseWriter, rootDir string) {
|
_, err = io.Copy(writer, rdc)
|
||||||
rootDir = filepath.Clean(rootDir)
|
return err
|
||||||
zipFileName := filepath.Base(rootDir) + ".zip"
|
}
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/zip")
|
func CompressToZip(w http.ResponseWriter, rootDir string) {
|
||||||
w.Header().Set("Content-Disposition", `attachment; filename="`+zipFileName+`"`)
|
rootDir = filepath.Clean(rootDir)
|
||||||
|
zipFileName := filepath.Base(rootDir) + ".zip"
|
||||||
zw := &Zip{Writer: zip.NewWriter(w)}
|
|
||||||
defer zw.Close()
|
w.Header().Set("Content-Type", "application/zip")
|
||||||
|
w.Header().Set("Content-Disposition", `attachment; filename="`+zipFileName+`"`)
|
||||||
filepath.Walk(rootDir, func(path string, info os.FileInfo, err error) error {
|
|
||||||
zipPath := path[len(rootDir):]
|
zw := &Zip{Writer: zip.NewWriter(w)}
|
||||||
return zw.Add(zipPath, path)
|
defer zw.Close()
|
||||||
})
|
|
||||||
}
|
filepath.Walk(rootDir, func(path string, info os.FileInfo, err error) error {
|
||||||
|
zipPath := path[len(rootDir):]
|
||||||
|
return zw.Add(zipPath, path)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExtractFromZip(zipFile, path string, w io.Writer) (err error) {
|
||||||
|
cf, err := zip.OpenReader(zipFile)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer cf.Close()
|
||||||
|
|
||||||
|
rd := ioutil.NopCloser(bytes.NewBufferString(path))
|
||||||
|
patterns, err := dkignore.ReadIgnore(rd)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, file := range cf.File {
|
||||||
|
matched, _ := dkignore.Matches(file.Name, patterns)
|
||||||
|
if !matched {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
rc, er := file.Open()
|
||||||
|
if er != nil {
|
||||||
|
err = er
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer rc.Close()
|
||||||
|
_, err = io.Copy(w, rc)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return fmt.Errorf("File %s not found", strconv.Quote(path))
|
||||||
|
}
|
||||||
|
|||||||
15
zip_test.go
Normal file
15
zip_test.go
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestExtractFromZip(t *testing.T) {
|
||||||
|
buf := bytes.NewBuffer(nil)
|
||||||
|
err := ExtractFromZip("testdata/test.zip", "*/foo.txt", buf)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Log("Content: " + buf.String())
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user