v1.0.1
This commit is contained in:
@@ -7,6 +7,7 @@ import (
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"net/http/cookiejar"
|
||||
urlpkg "net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -23,7 +24,13 @@ func PlecPage(w http.ResponseWriter, r *http.Request) {
|
||||
</header>
|
||||
|
||||
<section class="tool-panel">
|
||||
<div class="panel-header"><h3 class="panel-title">Inputs</h3></div>
|
||||
<div class="panel-header">
|
||||
<h3 class="panel-title">Inputs</h3>
|
||||
<select id="serverSelect">
|
||||
<option value="legacy">167.99.227.249 (Default)</option>
|
||||
<option value="wordpress">20.255.60.183 (Backup)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="control-group">
|
||||
<div id="dropZone" class="drop-zone">
|
||||
@@ -54,10 +61,15 @@ func PlecPage(w http.ResponseWriter, r *http.Request) {
|
||||
<style>
|
||||
.row-error { color:#f48771; font-size:12px; margin-top:4px; }
|
||||
#rows input[type=text] { width: 100%; }
|
||||
.panel-header { display: flex; align-items: center; }
|
||||
#serverSelect { margin-left:auto; padding:3px 6px; font-size:12px; cursor:pointer; }
|
||||
</style>
|
||||
|
||||
<script>
|
||||
const PAGE_SIZE = 5;
|
||||
const serverSelect = document.getElementById('serverSelect');
|
||||
serverSelect.value = localStorage.getItem('plec.lastServer') || 'legacy';
|
||||
serverSelect.addEventListener('change', () => localStorage.setItem('plec.lastServer', serverSelect.value));
|
||||
let nextId = 1;
|
||||
let rows = [];
|
||||
let page = 0;
|
||||
@@ -206,7 +218,7 @@ document.getElementById('upload').addEventListener('click', async () => {
|
||||
const res = await fetch('/api/plec/upload', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ rows }),
|
||||
body: JSON.stringify({ rows, server: serverSelect.value }),
|
||||
});
|
||||
await readJSONStream(res, (msg) => {
|
||||
if (msg.type === 'status') {
|
||||
@@ -299,7 +311,8 @@ type plecRow struct {
|
||||
func PlecUpload(w http.ResponseWriter, r *http.Request) {
|
||||
send := newJSONStream(w)
|
||||
var req struct {
|
||||
Rows []plecRow `json:"rows"`
|
||||
Rows []plecRow `json:"rows"`
|
||||
Server string `json:"server"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
send(map[string]any{"type": "error", "message": err.Error()})
|
||||
@@ -335,6 +348,14 @@ func PlecUpload(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if req.Server == "wordpress" {
|
||||
plecUploadWordPress(send, cfg, valid)
|
||||
} else {
|
||||
plecUploadLegacy(send, cfg, valid)
|
||||
}
|
||||
}
|
||||
|
||||
func plecUploadLegacy(send func(any), cfg PlecConfig, valid []plecRow) {
|
||||
stamp := time.Now().Format("20060102150405")
|
||||
|
||||
var buf bytes.Buffer
|
||||
@@ -357,9 +378,7 @@ func PlecUpload(w http.ResponseWriter, r *http.Request) {
|
||||
send(map[string]any{"type": "error", "message": err.Error()})
|
||||
return
|
||||
}
|
||||
iter := urlpkg.QueryEscape(strings.ReplaceAll(row.Iteration, " ", ""))
|
||||
// Original uses encodeURI which keeps more chars than QueryEscape; emulate by un-escaping safe chars.
|
||||
iter = encodeURICompatible(strings.ReplaceAll(row.Iteration, " ", ""))
|
||||
iter := encodeURICompatible(strings.ReplaceAll(row.Iteration, " ", ""))
|
||||
if err := mw.WriteField(fmt.Sprintf("iterationNameUpload_%d", idx), iter); err != nil {
|
||||
send(map[string]any{"type": "error", "message": err.Error()})
|
||||
return
|
||||
@@ -408,6 +427,171 @@ func PlecUpload(w http.ResponseWriter, r *http.Request) {
|
||||
send(map[string]any{"type": "done", "preview": preview, "raw": parsed, "rawText": string(respBody)})
|
||||
}
|
||||
|
||||
func wpAuthenticate(send func(any), baseUrl, username, password string) (*http.Client, string, error) {
|
||||
jar, _ := cookiejar.New(nil)
|
||||
client := &http.Client{Jar: jar}
|
||||
|
||||
// Pre-seed the test cookie WordPress checks for
|
||||
testURL, _ := urlpkg.Parse(baseUrl)
|
||||
jar.SetCookies(testURL, []*http.Cookie{{Name: "wordpress_test_cookie", Value: "WP Cookie check"}})
|
||||
|
||||
send(map[string]any{"type": "status", "message": "Authenticating with backup server..."})
|
||||
|
||||
form := urlpkg.Values{
|
||||
"log": {username},
|
||||
"pwd": {password},
|
||||
"wp-submit": {"Log In"},
|
||||
"redirect_to": {"/wp-admin/"},
|
||||
"testcookie": {"1"},
|
||||
}
|
||||
loginReq, _ := http.NewRequest("POST", baseUrl+"/wp-login.php", strings.NewReader(form.Encode()))
|
||||
loginReq.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
loginReq.Header.Set("Origin", baseUrl)
|
||||
loginReq.Header.Set("Referer", baseUrl+"/wp-login.php")
|
||||
loginReq.Header.Set("User-Agent", "Mozilla/5.0 (HPL-Toolbox-Standalone)")
|
||||
|
||||
loginResp, err := client.Do(loginReq)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("login request failed: %w", err)
|
||||
}
|
||||
io.ReadAll(loginResp.Body)
|
||||
loginResp.Body.Close()
|
||||
|
||||
// After following redirects, check final URL — wp-admin means success
|
||||
if strings.Contains(loginResp.Request.URL.String(), "wp-login") {
|
||||
return nil, "", fmt.Errorf("WordPress login failed — check wpUsername / wpPassword in settings")
|
||||
}
|
||||
|
||||
send(map[string]any{"type": "status", "message": "Fetching upload token..."})
|
||||
|
||||
pageReq, _ := http.NewRequest("GET", baseUrl+"/file-upload/", nil)
|
||||
pageReq.Header.Set("User-Agent", "Mozilla/5.0 (HPL-Toolbox-Standalone)")
|
||||
pageResp, err := client.Do(pageReq)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("nonce fetch failed: %w", err)
|
||||
}
|
||||
pageBody, _ := io.ReadAll(pageResp.Body)
|
||||
pageResp.Body.Close()
|
||||
|
||||
nonceRx := regexp.MustCompile(`"nonce"\s*:\s*"([a-f0-9]+)"`)
|
||||
m := nonceRx.FindSubmatch(pageBody)
|
||||
if m == nil {
|
||||
return nil, "", fmt.Errorf("could not extract nonce from backup server page")
|
||||
}
|
||||
return client, string(m[1]), nil
|
||||
}
|
||||
|
||||
func plecUploadWordPress(send func(any), cfg PlecConfig, valid []plecRow) {
|
||||
const baseUrl = "http://20.255.60.183"
|
||||
|
||||
if cfg.WpUsername == "" || cfg.WpPassword == "" {
|
||||
send(map[string]any{"type": "error", "message": "WordPress credentials not set. Configure wpUsername / wpPassword in settings."})
|
||||
return
|
||||
}
|
||||
|
||||
client, nonce, err := wpAuthenticate(send, baseUrl, cfg.WpUsername, cfg.WpPassword)
|
||||
if err != nil {
|
||||
send(map[string]any{"type": "error", "message": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
mw := multipart.NewWriter(&buf)
|
||||
_ = mw.WriteField("action", "plec_upload_files")
|
||||
_ = mw.WriteField("nonce", nonce)
|
||||
|
||||
type rowMeta struct {
|
||||
Field string `json:"field"`
|
||||
IterationName string `json:"iterationName"`
|
||||
}
|
||||
var rowsMeta []rowMeta
|
||||
|
||||
for i, row := range valid {
|
||||
idx := i + 1
|
||||
fieldName := fmt.Sprintf("file_%d", idx)
|
||||
sanitized := strings.ReplaceAll(row.Iteration, " ", "")
|
||||
filename := sanitized + ".html"
|
||||
send(map[string]any{"type": "status", "message": fmt.Sprintf("Preparing %d/%d %s", idx, len(valid), filename)})
|
||||
data, err := os.ReadFile(row.Path)
|
||||
if err != nil {
|
||||
send(map[string]any{"type": "error", "message": "Read failed: " + err.Error()})
|
||||
return
|
||||
}
|
||||
fw, err := createFormFileWithType(mw, fieldName, filename, "text/html")
|
||||
if err != nil {
|
||||
send(map[string]any{"type": "error", "message": err.Error()})
|
||||
return
|
||||
}
|
||||
if _, err := fw.Write(data); err != nil {
|
||||
send(map[string]any{"type": "error", "message": err.Error()})
|
||||
return
|
||||
}
|
||||
rowsMeta = append(rowsMeta, rowMeta{Field: fieldName, IterationName: sanitized})
|
||||
}
|
||||
|
||||
rowsJSON, _ := json.Marshal(rowsMeta)
|
||||
_ = mw.WriteField("rows", string(rowsJSON))
|
||||
if err := mw.Close(); err != nil {
|
||||
send(map[string]any{"type": "error", "message": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
send(map[string]any{"type": "status", "message": fmt.Sprintf("Uploading %d file%s...", len(valid), pluralS(len(valid)))})
|
||||
|
||||
httpReq, _ := http.NewRequest("POST", baseUrl+"/wp-admin/admin-ajax.php", &buf)
|
||||
httpReq.Header.Set("Content-Type", mw.FormDataContentType())
|
||||
httpReq.Header.Set("Origin", baseUrl)
|
||||
httpReq.Header.Set("Referer", baseUrl+"/file-upload/")
|
||||
httpReq.Header.Set("User-Agent", "Mozilla/5.0 (HPL-Toolbox-Standalone)")
|
||||
httpReq.Header.Set("X-Requested-With", "XMLHttpRequest")
|
||||
httpReq.Header.Set("Accept", "*/*")
|
||||
|
||||
resp, err := client.Do(httpReq)
|
||||
if err != nil {
|
||||
send(map[string]any{"type": "error", "message": "Network error: " + err.Error()})
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
respBody, _ := io.ReadAll(resp.Body)
|
||||
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
send(map[string]any{"type": "error", "message": fmt.Sprintf("HTTP %d\n%s", resp.StatusCode, truncate(string(respBody), 800))})
|
||||
return
|
||||
}
|
||||
var parsed map[string]any
|
||||
if err := json.Unmarshal(respBody, &parsed); err != nil {
|
||||
send(map[string]any{"type": "error", "message": "Server returned non-JSON:\n" + truncate(string(respBody), 800)})
|
||||
return
|
||||
}
|
||||
|
||||
var preview string
|
||||
if data, ok := parsed["data"].(map[string]any); ok {
|
||||
if files, ok := data["files"].([]any); ok && len(files) > 0 {
|
||||
params := urlpkg.Values{"theme": {"calcite"}}
|
||||
nonceRx := regexp.MustCompile(`(?i)\.html?$`)
|
||||
for i, f := range files {
|
||||
if fm, ok := f.(map[string]any); ok {
|
||||
orig, _ := fm["original"].(string)
|
||||
saved, _ := fm["saved"].(string)
|
||||
n := nonceRx.ReplaceAllString(orig, "")
|
||||
params.Set(fmt.Sprintf("n%d", i+1), n)
|
||||
params.Set(fmt.Sprintf("m%d", i+1), saved)
|
||||
}
|
||||
}
|
||||
preview = baseUrl + "/preview?" + params.Encode()
|
||||
}
|
||||
}
|
||||
if preview == "" {
|
||||
if v, ok := parsed["preview"].(string); ok {
|
||||
preview = v
|
||||
} else if v, ok := parsed["previewURL"].(string); ok {
|
||||
preview = v
|
||||
}
|
||||
}
|
||||
|
||||
send(map[string]any{"type": "done", "preview": preview, "raw": parsed, "rawText": string(respBody)})
|
||||
}
|
||||
|
||||
func ClipboardEndpoint(w http.ResponseWriter, r *http.Request) {
|
||||
var req struct {
|
||||
Text string `json:"text"`
|
||||
|
||||
Reference in New Issue
Block a user