mirror of
https://git.huckle.dev/Huckles-Minecraft-Archive/jpexs-decompiler.git
synced 2026-07-17 14:38:19 +00:00
Fixed: #2586 AS3 direct editation + decompilation - XML escape sequences and other XML problems
This commit is contained in:
@@ -26,6 +26,7 @@ All notable changes to this project will be documented in this file.
|
||||
- [#2572] SVG import - incorrect stroke width when transform contains rotation/shear
|
||||
- [#2595] FLA export - incorrect handling of imported fonts
|
||||
- FLA export - incorrect handling of imported sprites
|
||||
- [#2586] AS3 direct editation + decompilation - XML escape sequences and other XML problems
|
||||
|
||||
### Changed
|
||||
- [#2575] dumpSWF CLI command only allows single SWF dump (no imports, etc.)
|
||||
@@ -4087,6 +4088,7 @@ Major version of SWF to XML export changed to 2.
|
||||
[#1893]: https://www.free-decompiler.com/flash/issues/1893
|
||||
[#2572]: https://www.free-decompiler.com/flash/issues/2572
|
||||
[#2595]: https://www.free-decompiler.com/flash/issues/2595
|
||||
[#2586]: https://www.free-decompiler.com/flash/issues/2586
|
||||
[#2556]: https://www.free-decompiler.com/flash/issues/2556
|
||||
[#2536]: https://www.free-decompiler.com/flash/issues/2536
|
||||
[#2537]: https://www.free-decompiler.com/flash/issues/2537
|
||||
|
||||
Binary file not shown.
@@ -16,6 +16,8 @@
|
||||
*/
|
||||
package com.jpexs.decompiler.flash.abc.avm2.model;
|
||||
|
||||
import com.jpexs.decompiler.flash.abc.avm2.parser.script.AbcIndexing;
|
||||
import com.jpexs.decompiler.flash.abc.avm2.parser.script.ActionScript3Parser;
|
||||
import com.jpexs.decompiler.flash.helpers.GraphTextWriter;
|
||||
import com.jpexs.decompiler.graph.DottedChain;
|
||||
import com.jpexs.decompiler.graph.GraphSourceItem;
|
||||
@@ -23,8 +25,17 @@ import com.jpexs.decompiler.graph.GraphTargetItem;
|
||||
import com.jpexs.decompiler.graph.GraphTargetVisitorInterface;
|
||||
import com.jpexs.decompiler.graph.TypeItem;
|
||||
import com.jpexs.decompiler.graph.model.LocalData;
|
||||
import com.jpexs.helpers.Helper;
|
||||
import com.jpexs.helpers.Reference;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* XML.
|
||||
@@ -40,6 +51,7 @@ public class XMLAVM2Item extends AVM2Item {
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param instruction Instruction
|
||||
* @param lineStartIns Line start instruction
|
||||
* @param parts Parts
|
||||
@@ -54,14 +66,104 @@ public class XMLAVM2Item extends AVM2Item {
|
||||
visitor.visitAll(parts);
|
||||
}
|
||||
|
||||
private String handleSingleXml(String s, Reference<Boolean> inAttributeRef, Reference<Boolean> inOpeningTagRef) {
|
||||
|
||||
String identRegexp = "[:A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD][\\-\\.0-9:A-Z_a-z\\u00B7\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0300-\\u036F\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u203F-\\u2040\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD]*";
|
||||
|
||||
StringBuilder writer = new StringBuilder();
|
||||
|
||||
for (int j = 0; j < s.length(); j++) {
|
||||
char c = s.charAt(j);
|
||||
switch (c) {
|
||||
case '"':
|
||||
if (inOpeningTagRef.getVal()) {
|
||||
inAttributeRef.setVal(!inAttributeRef.getVal());
|
||||
}
|
||||
writer.append(c);
|
||||
break;
|
||||
case '<':
|
||||
Pattern p = Pattern.compile("^(" + identRegexp + ").*", Pattern.MULTILINE | Pattern.DOTALL);
|
||||
String sub = s.substring(j + 1);
|
||||
Matcher m = p.matcher(sub);
|
||||
writer.append(c);
|
||||
if (m.matches()) {
|
||||
inOpeningTagRef.setVal(true);
|
||||
String tag = m.group(1);
|
||||
writer.append(tag);
|
||||
j += tag.length();
|
||||
}
|
||||
break;
|
||||
case '>':
|
||||
if (inOpeningTagRef.getVal()) {
|
||||
inOpeningTagRef.setVal(false);
|
||||
}
|
||||
writer.append(c);
|
||||
break;
|
||||
default:
|
||||
if (inAttributeRef.getVal()) {
|
||||
switch (c) {
|
||||
case '\r':
|
||||
writer.append(" ");
|
||||
break;
|
||||
case '\n':
|
||||
writer.append(" ");
|
||||
break;
|
||||
case '\t':
|
||||
writer.append("	");
|
||||
break;
|
||||
default:
|
||||
writer.append(c);
|
||||
}
|
||||
} else {
|
||||
writer.append(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
return writer.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public GraphTextWriter appendTo(GraphTextWriter writer, LocalData localData) throws InterruptedException {
|
||||
|
||||
Reference<Boolean> inAttributeRef = new Reference<>(false);
|
||||
Reference<Boolean> inOpeningTagRef = new Reference<>(false);
|
||||
|
||||
if (parts.size() == 1 && parts.get(0) instanceof StringAVM2Item) {
|
||||
String s = ((StringAVM2Item) parts.get(0)).getValue();
|
||||
s = handleSingleXml(s, inAttributeRef, inOpeningTagRef);
|
||||
boolean validXml = true;
|
||||
try {
|
||||
ActionScript3Parser par = new ActionScript3Parser(new AbcIndexing());
|
||||
if (!par.checkBasicXmlOnly(s)) {
|
||||
validXml = false;
|
||||
}
|
||||
} catch (Throwable ex) {
|
||||
validXml = false;
|
||||
}
|
||||
|
||||
if (!validXml) {
|
||||
writer.append("new XML");
|
||||
writer.spaceBeforeCallParenthesis(1);
|
||||
writer.append("(\"").append(Helper.escapeActionScriptString(s)).append("\")");
|
||||
return writer;
|
||||
}
|
||||
writer.spaceBeforeCallParenthesis(precedence);
|
||||
writer.append(s);
|
||||
return writer;
|
||||
}
|
||||
|
||||
for (int i = 0; i < parts.size(); i++) {
|
||||
GraphTargetItem part = parts.get(i);
|
||||
GraphTargetItem partBefore = i > 0 ? parts.get(i - 1) : null;
|
||||
GraphTargetItem partAfter = i < parts.size() - 1 ? parts.get(i + 1) : null;
|
||||
|
||||
/*
|
||||
Older versions of Flex allow inserting escape sequences like \r, \n, \t
|
||||
into attributes. Air does not allow this. We handle this by converting it into XML entities.
|
||||
*/
|
||||
if (part instanceof StringAVM2Item) {
|
||||
String s = ((StringAVM2Item) part).getValue();
|
||||
|
||||
if (partAfter instanceof EscapeXAttrAVM2Item) {
|
||||
if (s.endsWith("\"")) {
|
||||
s = s.substring(0, s.length() - 1);
|
||||
@@ -72,7 +174,8 @@ public class XMLAVM2Item extends AVM2Item {
|
||||
s = s.substring(1);
|
||||
}
|
||||
}
|
||||
writer.append(s);
|
||||
|
||||
writer.append(handleSingleXml(s, inAttributeRef, inOpeningTagRef));
|
||||
} else if ((part instanceof EscapeXElemAVM2Item) || (part instanceof EscapeXAttrAVM2Item)) {
|
||||
part.toString(writer, localData);
|
||||
} else {
|
||||
|
||||
@@ -1572,6 +1572,21 @@ public class ActionScript3Parser {
|
||||
//TODO: Order of additions as in official compiler
|
||||
return ret;
|
||||
}
|
||||
|
||||
public boolean checkBasicXmlOnly(String txt) {
|
||||
lexer = new ActionScriptLexer(txt);
|
||||
try {
|
||||
xml(new ArrayList<>(), null, null, new Reference<>(false), new ArrayList<>(), new ArrayList<>(), new HashMap<>(), false, false, new ArrayList<>(), null);
|
||||
|
||||
ParsedSymbol s = lexer.lex();
|
||||
if (s.type != SymbolType.EOF) {
|
||||
return false;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private GraphTargetItem command(List<List<NamespaceItem>> allOpenedNamespaces, TypeItem thisType, NamespaceItem pkg, Reference<Boolean> needsActivation, List<DottedChain> importedClasses, List<NamespaceItem> openedNamespaces, Stack<Loop> loops, Map<Loop, String> loopLabels, HashMap<String, Integer> registerVars, boolean inFunction, boolean inMethod, int forinlevel, boolean mustBeCommand, List<AssignableAVM2Item> variables, ABC abc) throws IOException, AVM2ParseException, InterruptedException {
|
||||
LexBufferer buf = new LexBufferer();
|
||||
|
||||
@@ -2847,4 +2847,38 @@ public class ActionScript3ClassicAirDecompileTest extends ActionScript3Decompile
|
||||
+ "var m:XMLList = myXML.*;\r\n",
|
||||
false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testXml2() {
|
||||
decompileMethod("classic_air", "testXml2", "var x1:XML = <elem name=\"aaa\" value=\"xxx \"/>;\r\n"
|
||||
+ "var x2:XML = <elem\r\n"
|
||||
+ "name=\"aaa\"\r\n"
|
||||
+ "value=\"xxx\"\r\n"
|
||||
+ "/>;\r\n"
|
||||
+ "var x3:XML = <elem\r\n"
|
||||
+ "name=\"aaa\" value=\"xxx\">\r\n"
|
||||
+ "<sub title=\"yyy\">\r\n"
|
||||
+ "ampersand: &\r\n"
|
||||
+ "</sub>\r\n"
|
||||
+ "<sub />\r\n"
|
||||
+ "</elem>;\r\n"
|
||||
+ "var x4:XML = <elem>\r\n"
|
||||
+ "<elem>\r\n"
|
||||
+ "A\r\n"
|
||||
+ "</elem>\r\n"
|
||||
+ "<elem>\r\n"
|
||||
+ "B\r\n"
|
||||
+ "</elem>\r\n"
|
||||
+ "<elem>\r\n"
|
||||
+ "<elem>\r\n"
|
||||
+ "C\r\n"
|
||||
+ "</elem>\r\n"
|
||||
+ "</elem>\r\n"
|
||||
+ "</elem>;\r\n"
|
||||
+ "var x5:XML = <elem attr=\"abc\\r\\n\\tdef\"></elem>;\r\n"
|
||||
+ "var x_invalid:XML = new XML(\"<aaa >> invalid \\\"\\n\");\r\n"
|
||||
+ "var a:int = 5;\r\n"
|
||||
+ "trace(\"B\");\r\n",
|
||||
false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2850,4 +2850,34 @@ public class ActionScript3ClassicDecompileTest extends ActionScript3DecompileTes
|
||||
+ "m = myXML.*;\r\n",
|
||||
false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testXml2() {
|
||||
decompileMethod("classic", "testXml2", "var x1:XML = <elem name=\"aaa\" value=\"xxx \"/>;\r\n"
|
||||
+ "var x2:XML = <elem name=\"aaa\" value=\"xxx\"/>;\r\n"
|
||||
+ "var x3:XML = <elem name=\"aaa\" value=\"xxx\">\r\n"
|
||||
+ "<sub title=\"yyy\">\r\n"
|
||||
+ "ampersand: &\r\n"
|
||||
+ "</sub>\r\n"
|
||||
+ "<sub/>\r\n"
|
||||
+ "</elem>;\r\n"
|
||||
+ "var x4:XML = <elem>\r\n"
|
||||
+ "<elem>\r\n"
|
||||
+ "A\r\n"
|
||||
+ "</elem>\r\n"
|
||||
+ "<elem>\r\n"
|
||||
+ "B\r\n"
|
||||
+ "</elem>\r\n"
|
||||
+ "<elem>\r\n"
|
||||
+ "<elem>\r\n"
|
||||
+ "C\r\n"
|
||||
+ "</elem>\r\n"
|
||||
+ "</elem>\r\n"
|
||||
+ "</elem>;\r\n"
|
||||
+ "var x5:XML = <elem attr=\"abc 	def\"></elem>;\r\n"
|
||||
+ "var x_invalid:XML = new XML(\"<aaa >> invalid \\\"\\n\");\r\n"
|
||||
+ "var a:int = 5;\r\n"
|
||||
+ "trace(\"B\");\r\n",
|
||||
false);
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -162,6 +162,7 @@ package
|
||||
TestWhileTry;
|
||||
TestWhileTry2;
|
||||
TestXml;
|
||||
TestXml2;
|
||||
|
||||
SetupMyPackage1;
|
||||
SetupMyPackage2;
|
||||
|
||||
43
libsrc/ffdec_lib/testdata/as3_new/src/tests/TestXml2.as
vendored
Normal file
43
libsrc/ffdec_lib/testdata/as3_new/src/tests/TestXml2.as
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
package tests
|
||||
{
|
||||
|
||||
public class TestXml2
|
||||
{
|
||||
public function run():*
|
||||
{
|
||||
var x1:XML = new XML("<elem name=\"aaa\" value=\"xxx\n\"/>");
|
||||
var x2:XML = <elem
|
||||
name="aaa"
|
||||
value="xxx"
|
||||
/>;
|
||||
var x3:XML = <elem
|
||||
name="aaa" value="xxx">
|
||||
<sub title="yyy">
|
||||
ampersand: &
|
||||
</sub>
|
||||
<sub />
|
||||
</elem>;
|
||||
|
||||
var x4:XML = <elem>
|
||||
<elem>
|
||||
A
|
||||
</elem>
|
||||
<elem>
|
||||
B
|
||||
</elem>
|
||||
<elem>
|
||||
<elem>
|
||||
C
|
||||
</elem>
|
||||
</elem>
|
||||
</elem>;
|
||||
|
||||
var x5:XML = <elem attr="abc\r\n\tdef"></elem>;
|
||||
|
||||
var x_invalid:XML = new XML("<aaa >> invalid \"\n");
|
||||
|
||||
var a:int = 5;
|
||||
trace("B");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -58,6 +58,8 @@ import java.util.List;
|
||||
|
||||
private boolean prevNew = false;
|
||||
|
||||
private int xmlNestingLevel = 0;
|
||||
|
||||
@Override
|
||||
public void parse(Segment segment, int ofst, List<Token> tokens) {
|
||||
try {
|
||||
@@ -115,10 +117,10 @@ IdentifierNs = {Identifier} ":" {Identifier}
|
||||
/* XML */
|
||||
LetterColon = [:jletter] | ":"
|
||||
XMLIdentifier = {Identifier} | {IdentifierNs}
|
||||
XMLAttribute = " "* {XMLIdentifier} " "* "=" " "* (\" {InputCharacter}* \" | "'" {InputCharacter}* "'") " "*
|
||||
XMLBeginOneTag = "<" {XMLIdentifier} {XMLAttribute}* ">" | "<{" {XMLIdentifier} "}" {XMLAttribute}* " "* ">"
|
||||
XMLBeginTag = "<" {XMLIdentifier} " " | "<{" {XMLIdentifier} "} "
|
||||
XMLEndTag = "</" {XMLIdentifier} " "* ">" | "</{" {XMLIdentifier} "}" " "* ">"
|
||||
XMLAttribute = {WhiteSpace}* {XMLIdentifier} {WhiteSpace}* "=" {WhiteSpace}* (\" {InputCharacter}* \" | "'" {InputCharacter}* "'") {WhiteSpace}*
|
||||
XMLBeginOneTag = "<" {XMLIdentifier} {XMLAttribute}* {WhiteSpace}* "/"? ">" | "<{" {XMLIdentifier} "}" {XMLAttribute}* {WhiteSpace}* "/"? ">"
|
||||
XMLBeginTag = "<" {XMLIdentifier} {WhiteSpace}+ | "<{" {XMLIdentifier} "}" {WhiteSpace}
|
||||
XMLEndTag = "</" {XMLIdentifier} {WhiteSpace}* ">" | "</{" {XMLIdentifier} "}" {WhiteSpace}* ">"
|
||||
|
||||
/* integer literals */
|
||||
DecIntegerLiteral = (0 | -?[1-9][0-9]*) [ui]?
|
||||
@@ -346,15 +348,20 @@ VerbatimString = "@\"" {VerbatimStringCharacter}* "\""
|
||||
yypushback(yytext().length() - 1);
|
||||
return token(TokenType.OPERATOR, yychar, 1);
|
||||
}
|
||||
yybegin(XML);
|
||||
tokenStart = yychar;
|
||||
tokenLength = yylength();
|
||||
String s=yytext();
|
||||
s=s.substring(1,s.length()-1);
|
||||
s = s.substring(1,s.length()-1);
|
||||
if (s.endsWith("/")) {
|
||||
return token(TokenType.STRING, tokenStart, tokenLength);
|
||||
}
|
||||
|
||||
if(s.contains(" ")){
|
||||
s=s.substring(0,s.indexOf(" "));
|
||||
}
|
||||
xmlTagName = s;
|
||||
yybegin(XML);
|
||||
xmlTagName = s;
|
||||
xmlNestingLevel = 1;
|
||||
}
|
||||
|
||||
">" { prevNew = false; return token(TokenType.OPERATOR); }
|
||||
@@ -373,12 +380,18 @@ VerbatimString = "@\"" {VerbatimStringCharacter}* "\""
|
||||
">" { yybegin(XML); tokenLength += yylength();}
|
||||
}
|
||||
<XML> {
|
||||
{XMLBeginOneTag} { tokenLength += yylength();}
|
||||
{XMLEndTag} { tokenLength += yylength();
|
||||
String endtagname=yytext();
|
||||
endtagname = endtagname.substring(2,endtagname.length()-1);
|
||||
endtagname = endtagname.trim();
|
||||
if(endtagname.equals(xmlTagName)){
|
||||
{XMLBeginOneTag} {
|
||||
tokenLength += yylength();
|
||||
String s = yytext();
|
||||
if (s.charAt(s.length() - 2) != '/') {
|
||||
xmlNestingLevel++;
|
||||
}
|
||||
}
|
||||
{XMLEndTag} {
|
||||
xmlNestingLevel--;
|
||||
|
||||
tokenLength += yylength();
|
||||
if (xmlNestingLevel == 0) {
|
||||
yybegin(YYINITIAL);
|
||||
return token(TokenType.STRING, tokenStart, tokenLength);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user