mirror of
https://github.com/revanced/ARSCLib.git
synced 2025-04-29 22:04:25 +02:00
better xml namespace handling
This commit is contained in:
parent
75d6ed704b
commit
b07b8bddfb
@ -211,8 +211,7 @@ public class ApkModuleXmlDecoder extends ApkDecoder implements Predicate<Entry>
|
||||
}
|
||||
private void serializeXml(int currentPackageId, ResXmlDocument document, File outFile)
|
||||
throws IOException {
|
||||
XMLNamespaceValidator namespaceValidator = new XMLNamespaceValidator(document);
|
||||
namespaceValidator.validate();
|
||||
XMLNamespaceValidator.validateNamespaces(document);
|
||||
ResXmlDocumentSerializer serializer = getDocumentSerializer();
|
||||
if(currentPackageId != 0){
|
||||
serializer.getDecoder().setCurrentPackageId(currentPackageId);
|
||||
|
@ -121,8 +121,7 @@ public class ResXmlDocumentSerializer implements ResXmlPullParser.DocumentLoaded
|
||||
if(!validateXmlNamespace){
|
||||
return resXmlDocument;
|
||||
}
|
||||
XMLNamespaceValidator namespaceValidator = new XMLNamespaceValidator(resXmlDocument);
|
||||
namespaceValidator.validate();
|
||||
XMLNamespaceValidator.validateNamespaces(resXmlDocument);
|
||||
return resXmlDocument;
|
||||
}
|
||||
private IOException getError(Exception exception){
|
||||
|
@ -1,4 +1,4 @@
|
||||
/*
|
||||
/*
|
||||
* Copyright (C) 2022 github.com/REAndroid
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -17,54 +17,65 @@ package com.reandroid.apk.xmldecoder;
|
||||
|
||||
import com.reandroid.arsc.chunk.xml.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Collection;
|
||||
|
||||
public class XMLNamespaceValidator {
|
||||
private static final String URI_ANDROID="http://schemas.android.com/apk/res/android";
|
||||
private static final String URI_APP="http://schemas.android.com/apk/res-auto";
|
||||
private static final String PREFIX_ANDROID ="android";
|
||||
private static final String PREFIX_APP="app";
|
||||
private static final String URI_ANDROID = "http://schemas.android.com/apk/res/android";
|
||||
private static final String URI_APP = "http://schemas.android.com/apk/res-auto";
|
||||
private static final String PREFIX_ANDROID = "android";
|
||||
private static final String PREFIX_APP = "app";
|
||||
private final ResXmlDocument xmlBlock;
|
||||
private List<ResXmlAttribute> mAttributeList;
|
||||
private ResXmlElement mRootElement;
|
||||
private ResXmlStartNamespace mNsAndroid;
|
||||
private ResXmlStartNamespace mNsApp;
|
||||
public XMLNamespaceValidator(ResXmlDocument xmlBlock){
|
||||
this.xmlBlock=xmlBlock;
|
||||
}
|
||||
public void validate(){
|
||||
if(getRootElement()==null){
|
||||
return;
|
||||
}
|
||||
for(ResXmlAttribute attribute:getAttributes()){
|
||||
validate(attribute);
|
||||
}
|
||||
validateNamespaces(xmlBlock);
|
||||
}
|
||||
private void validate(ResXmlAttribute attribute){
|
||||
int resourceId=attribute.getNameResourceID();
|
||||
if(resourceId==0){
|
||||
removeNamespace(attribute);
|
||||
return;
|
||||
|
||||
public static boolean isValid(ResXmlAttribute attribute){
|
||||
int resourceId = attribute.getNameResourceID();
|
||||
if(resourceId == 0){
|
||||
return attribute.getUri() == null;
|
||||
}
|
||||
int pkgId=toPackageId(resourceId);
|
||||
if(isAndroid(pkgId)){
|
||||
setAndroidNamespace(attribute);
|
||||
if(isAndroid(toPackageId(resourceId))){
|
||||
return isValidAndroidNamespace(attribute);
|
||||
}else {
|
||||
setAppNamespace(attribute);
|
||||
return isValidAppNamespace(attribute);
|
||||
}
|
||||
}
|
||||
private void removeNamespace(ResXmlAttribute attribute){
|
||||
attribute.setNamespaceReference(-1);
|
||||
public static void validateNamespaces(ResXmlDocument resXmlDocument){
|
||||
validateNamespaces(resXmlDocument.getResXmlElement());
|
||||
}
|
||||
private void setAppNamespace(ResXmlAttribute attribute){
|
||||
if(isValidAppNamespace(attribute)){
|
||||
public static void validateNamespaces(ResXmlElement element){
|
||||
validateNamespaces(element.listAttributes());
|
||||
for(ResXmlElement child : element.listElements()){
|
||||
validateNamespaces(child);
|
||||
}
|
||||
}
|
||||
|
||||
private static void validateNamespaces(Collection<ResXmlAttribute> attributeList){
|
||||
for(ResXmlAttribute attribute : attributeList){
|
||||
validateNamespace(attribute);
|
||||
}
|
||||
}
|
||||
private static void validateNamespace(ResXmlAttribute attribute){
|
||||
int resourceId = attribute.getNameResourceID();
|
||||
if(resourceId == 0){
|
||||
attribute.setNamespaceReference(-1);
|
||||
return;
|
||||
}
|
||||
ResXmlStartNamespace ns = getOrCreateApp();
|
||||
attribute.setNamespaceReference(ns.getUriReference());
|
||||
if(isAndroid(toPackageId(resourceId))){
|
||||
if(!isValidAndroidNamespace(attribute)){
|
||||
attribute.setNamespace(URI_ANDROID, PREFIX_ANDROID);
|
||||
}
|
||||
}else {
|
||||
if(!isValidAppNamespace(attribute)){
|
||||
attribute.setNamespace(URI_APP, PREFIX_APP);
|
||||
}
|
||||
}
|
||||
}
|
||||
private boolean isValidAppNamespace(ResXmlAttribute attribute){
|
||||
|
||||
private static boolean isValidAppNamespace(ResXmlAttribute attribute){
|
||||
String uri = attribute.getUri();
|
||||
String prefix = attribute.getNamePrefix();
|
||||
if(URI_ANDROID.equals(uri) || PREFIX_ANDROID.equals(prefix)){
|
||||
@ -75,62 +86,15 @@ public class XMLNamespaceValidator {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
private void setAndroidNamespace(ResXmlAttribute attribute){
|
||||
if(URI_ANDROID.equals(attribute.getUri())
|
||||
&& PREFIX_ANDROID.equals(attribute.getNamePrefix())){
|
||||
return;
|
||||
}
|
||||
ResXmlStartNamespace ns = getOrCreateAndroid();
|
||||
attribute.setNamespaceReference(ns.getUriReference());
|
||||
private static boolean isValidAndroidNamespace(ResXmlAttribute attribute){
|
||||
return URI_ANDROID.equals(attribute.getUri())
|
||||
&& PREFIX_ANDROID.equals(attribute.getNamePrefix());
|
||||
}
|
||||
private ResXmlStartNamespace getOrCreateApp(){
|
||||
if(mNsApp !=null){
|
||||
return mNsApp;
|
||||
}
|
||||
ResXmlElement root=getRootElement();
|
||||
ResXmlStartNamespace ns = root.getOrCreateNamespace(URI_APP, PREFIX_APP);
|
||||
String prefix=ns.getPrefix();
|
||||
if(PREFIX_ANDROID.equals(prefix) || isEmpty(prefix)){
|
||||
ns.setPrefix(PREFIX_APP);
|
||||
}
|
||||
mNsApp = ns;
|
||||
return mNsApp;
|
||||
}
|
||||
private ResXmlStartNamespace getOrCreateAndroid(){
|
||||
if(mNsAndroid !=null){
|
||||
return mNsAndroid;
|
||||
}
|
||||
ResXmlElement root=getRootElement();
|
||||
ResXmlStartNamespace ns = root.getOrCreateNamespace(URI_ANDROID, PREFIX_ANDROID);
|
||||
if(!PREFIX_ANDROID.equals(ns.getPrefix())){
|
||||
ns.setPrefix(PREFIX_ANDROID);
|
||||
}
|
||||
mNsAndroid =ns;
|
||||
return mNsAndroid;
|
||||
}
|
||||
private ResXmlElement getRootElement() {
|
||||
if(mRootElement==null){
|
||||
mRootElement = xmlBlock.getResXmlElement();
|
||||
}
|
||||
return mRootElement;
|
||||
}
|
||||
private List<ResXmlAttribute> getAttributes(){
|
||||
if(mAttributeList==null){
|
||||
mAttributeList=listAttributes(xmlBlock.getResXmlElement());
|
||||
}
|
||||
return mAttributeList;
|
||||
}
|
||||
private List<ResXmlAttribute> listAttributes(ResXmlElement element){
|
||||
List<ResXmlAttribute> results = new ArrayList<>(element.listAttributes());
|
||||
for(ResXmlElement child:element.listElements()){
|
||||
results.addAll(listAttributes(child));
|
||||
}
|
||||
return results;
|
||||
}
|
||||
private boolean isAndroid(int pkgId){
|
||||
|
||||
private static boolean isAndroid(int pkgId){
|
||||
return pkgId==1;
|
||||
}
|
||||
private int toPackageId(int resId){
|
||||
private static int toPackageId(int resId){
|
||||
return (resId >> 24 & 0xFF);
|
||||
}
|
||||
private static boolean isEmpty(String str){
|
||||
|
@ -184,6 +184,18 @@ public class ResXmlAttribute extends ValueItem implements AttributeValue, Compar
|
||||
int getNamespaceReference(){
|
||||
return getInteger(getBytesInternal(), OFFSET_NS);
|
||||
}
|
||||
public void setNamespace(String uri, String prefix){
|
||||
if(uri == null || prefix == null){
|
||||
setNamespaceReference(-1);
|
||||
return;
|
||||
}
|
||||
ResXmlElement parentElement = getParentResXmlElement();
|
||||
if(parentElement == null){
|
||||
return;
|
||||
}
|
||||
ResXmlStartNamespace ns = parentElement.getOrCreateNamespace(uri, prefix);
|
||||
setNamespaceReference(ns.getUriReference());
|
||||
}
|
||||
public void setNamespaceReference(int ref){
|
||||
if(ref == getNamespaceReference()){
|
||||
return;
|
||||
|
@ -110,7 +110,7 @@ public class ResXmlElement extends ResXmlNode implements JSONConvert<JSONObject>
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public String getEndComment(){
|
||||
String getEndComment(){
|
||||
ResXmlEndElement end = getEndElement();
|
||||
if(end!=null){
|
||||
return end.getComment();
|
||||
@ -143,36 +143,30 @@ public class ResXmlElement extends ResXmlNode implements JSONConvert<JSONObject>
|
||||
public ResXmlAttribute newAttribute(){
|
||||
return getStartElement().newAttribute();
|
||||
}
|
||||
@Override
|
||||
void onRemoved(){
|
||||
for(ResXmlStartNamespace startNamespace:getStartNamespaceList()){
|
||||
startNamespace.onRemoved();
|
||||
}
|
||||
ResXmlStartElement start = getStartElement();
|
||||
if(start!=null){
|
||||
if(start != null){
|
||||
start.onRemoved();
|
||||
}
|
||||
ResXmlText resXmlText=getResXmlText();
|
||||
if(resXmlText!=null){
|
||||
resXmlText.onRemoved();
|
||||
}
|
||||
for(ResXmlElement child:listElements()){
|
||||
child.onRemoved();
|
||||
for(ResXmlNode xmlNode : listXmlNodes()){
|
||||
xmlNode.onRemoved();
|
||||
}
|
||||
}
|
||||
@Override
|
||||
void linkStringReferences(){
|
||||
for(ResXmlStartNamespace startNamespace:getStartNamespaceList()){
|
||||
startNamespace.linkStringReferences();
|
||||
}
|
||||
ResXmlStartElement start = getStartElement();
|
||||
if(start!=null){
|
||||
if(start != null){
|
||||
start.linkStringReferences();
|
||||
}
|
||||
ResXmlText resXmlText=getResXmlText();
|
||||
if(resXmlText!=null){
|
||||
resXmlText.linkStringReferences();
|
||||
}
|
||||
for(ResXmlElement child:listElements()){
|
||||
child.linkStringReferences();
|
||||
for(ResXmlNode xmlNode : getXmlNodes()){
|
||||
xmlNode.linkStringReferences();
|
||||
}
|
||||
}
|
||||
public ResXmlElement createChildElement(){
|
||||
@ -355,13 +349,11 @@ public class ResXmlElement extends ResXmlNode implements JSONConvert<JSONObject>
|
||||
|
||||
@Override
|
||||
public int getDepth(){
|
||||
int depth = 0;
|
||||
ResXmlElement parent = getParentResXmlElement();
|
||||
while (parent!=null){
|
||||
depth++;
|
||||
parent = parent.getParentResXmlElement();
|
||||
if(parent != null){
|
||||
return parent.getDepth() + 1;
|
||||
}
|
||||
return depth;
|
||||
return 0;
|
||||
}
|
||||
@Override
|
||||
void addEvents(ParserEventList parserEventList){
|
||||
@ -423,7 +415,7 @@ public class ResXmlElement extends ResXmlNode implements JSONConvert<JSONObject>
|
||||
if(xmlNode==null){
|
||||
continue;
|
||||
}
|
||||
xmlNode.onRemove();
|
||||
xmlNode.onRemoved();
|
||||
mBody.remove(xmlNode);
|
||||
}
|
||||
}
|
||||
@ -433,6 +425,22 @@ public class ResXmlElement extends ResXmlNode implements JSONConvert<JSONObject>
|
||||
public int countResXmlNodes(){
|
||||
return mBody.size();
|
||||
}
|
||||
public boolean hasText(){
|
||||
for(ResXmlNode xmlNode : getXmlNodes()){
|
||||
if(xmlNode instanceof ResXmlTextNode){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
public boolean hasElement(){
|
||||
for(ResXmlNode xmlNode : getXmlNodes()){
|
||||
if(xmlNode instanceof ResXmlElement){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
public List<ResXmlNode> listXmlNodes(){
|
||||
return new ArrayList<>(getXmlNodes());
|
||||
}
|
||||
@ -479,8 +487,8 @@ public class ResXmlElement extends ResXmlNode implements JSONConvert<JSONObject>
|
||||
return results;
|
||||
}
|
||||
public ResXmlElement getRootResXmlElement(){
|
||||
ResXmlElement parent=getParentResXmlElement();
|
||||
if(parent!=null){
|
||||
ResXmlElement parent = getParentResXmlElement();
|
||||
if(parent != null){
|
||||
return parent.getRootResXmlElement();
|
||||
}
|
||||
return this;
|
||||
@ -503,13 +511,31 @@ public class ResXmlElement extends ResXmlNode implements JSONConvert<JSONObject>
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public ResXmlStartNamespace getNamespace(String uri, String prefix){
|
||||
if(uri == null || prefix == null){
|
||||
return null;
|
||||
}
|
||||
for(ResXmlStartNamespace ns : mStartNamespaceList.getChildes()){
|
||||
if(uri.equals(ns.getUri()) && prefix.equals(ns.getPrefix())){
|
||||
return ns;
|
||||
}
|
||||
}
|
||||
ResXmlElement xmlElement = getParentResXmlElement();
|
||||
if(xmlElement != null){
|
||||
return xmlElement.getNamespace(uri, prefix);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public ResXmlStartNamespace getOrCreateNamespace(String uri, String prefix){
|
||||
ResXmlStartNamespace exist=getStartNamespaceByUri(uri);
|
||||
if(exist!=null){
|
||||
ResXmlStartNamespace exist = getNamespace(uri, prefix);
|
||||
if(exist != null){
|
||||
return exist;
|
||||
}
|
||||
ResXmlStartNamespace startNamespace=new ResXmlStartNamespace();
|
||||
ResXmlEndNamespace endNamespace=new ResXmlEndNamespace();
|
||||
return getRootResXmlElement().createNamespace(uri, prefix);
|
||||
}
|
||||
public ResXmlStartNamespace createNamespace(String uri, String prefix){
|
||||
ResXmlStartNamespace startNamespace = new ResXmlStartNamespace();
|
||||
ResXmlEndNamespace endNamespace = new ResXmlEndNamespace();
|
||||
startNamespace.setEnd(endNamespace);
|
||||
|
||||
addStartNamespace(startNamespace);
|
||||
@ -562,7 +588,7 @@ public class ResXmlElement extends ResXmlNode implements JSONConvert<JSONObject>
|
||||
public void addStartNamespace(ResXmlStartNamespace item){
|
||||
mStartNamespaceList.add(item);
|
||||
}
|
||||
public List<ResXmlEndNamespace> getEndNamespaceList(){
|
||||
private List<ResXmlEndNamespace> getEndNamespaceList(){
|
||||
return mEndNamespaceList.getChildes();
|
||||
}
|
||||
public void addEndNamespace(ResXmlEndNamespace item){
|
||||
@ -596,26 +622,17 @@ public class ResXmlElement extends ResXmlNode implements JSONConvert<JSONObject>
|
||||
public ResXmlStartElement getStartElement(){
|
||||
return mStartElementContainer.getItem();
|
||||
}
|
||||
public void setStartElement(ResXmlStartElement item){
|
||||
private void setStartElement(ResXmlStartElement item){
|
||||
mStartElementContainer.setItem(item);
|
||||
}
|
||||
|
||||
public ResXmlEndElement getEndElement(){
|
||||
private ResXmlEndElement getEndElement(){
|
||||
return mEndElementContainer.getItem();
|
||||
}
|
||||
public void setEndElement(ResXmlEndElement item){
|
||||
private void setEndElement(ResXmlEndElement item){
|
||||
mEndElementContainer.setItem(item);
|
||||
}
|
||||
|
||||
// Use listXmlText() instead to be removed on next version
|
||||
@Deprecated
|
||||
public ResXmlText getResXmlText(){
|
||||
List<ResXmlText> xmlTextList=listXmlText();
|
||||
if(xmlTextList.size()==0){
|
||||
return null;
|
||||
}
|
||||
return xmlTextList.get(0);
|
||||
}
|
||||
public void addResXmlTextNode(ResXmlTextNode xmlTextNode){
|
||||
mBody.add(xmlTextNode);
|
||||
}
|
||||
@ -624,16 +641,6 @@ public class ResXmlElement extends ResXmlNode implements JSONConvert<JSONObject>
|
||||
addResXmlTextNode(new ResXmlTextNode(xmlText));
|
||||
}
|
||||
}
|
||||
// Use addResXmlText()
|
||||
@Deprecated
|
||||
public void setResXmlText(ResXmlText xmlText){
|
||||
addResXmlText(xmlText);
|
||||
}
|
||||
@Deprecated
|
||||
public void setResXmlText(String text){
|
||||
clearChildes();
|
||||
addResXmlText(text);
|
||||
}
|
||||
public void addResXmlText(String text){
|
||||
if(text==null){
|
||||
return;
|
||||
@ -995,13 +1002,14 @@ public class ResXmlElement extends ResXmlNode implements JSONConvert<JSONObject>
|
||||
public String toString(){
|
||||
ResXmlStartElement start = getStartElement();
|
||||
if(start!=null){
|
||||
ResXmlText text=getResXmlText();
|
||||
StringBuilder builder=new StringBuilder();
|
||||
builder.append("<");
|
||||
builder.append(start.toString());
|
||||
if(text!=null){
|
||||
if(hasText() && !hasElement()){
|
||||
builder.append(">");
|
||||
builder.append(text.toString());
|
||||
for(ResXmlText xmlText : listXmlText()){
|
||||
builder.append(xmlText.getText());
|
||||
}
|
||||
builder.append("</");
|
||||
builder.append(start.getTagName());
|
||||
builder.append(">");
|
||||
|
@ -1,18 +1,18 @@
|
||||
/*
|
||||
* Copyright (C) 2022 github.com/REAndroid
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
/*
|
||||
* Copyright (C) 2022 github.com/REAndroid
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.reandroid.arsc.chunk.xml;
|
||||
|
||||
import com.reandroid.arsc.container.FixedBlockContainer;
|
||||
@ -23,10 +23,10 @@ public abstract class ResXmlNode extends FixedBlockContainer implements JSONCon
|
||||
ResXmlNode(int childesCount) {
|
||||
super(childesCount);
|
||||
}
|
||||
void onRemove(){
|
||||
}
|
||||
abstract void onRemoved();
|
||||
abstract void linkStringReferences();
|
||||
public abstract int getDepth();
|
||||
abstract void addEvents(ParserEventList parserEventList);
|
||||
|
||||
public static final String NAME_node_type="node_type";
|
||||
public static final String NAME_node_type = "node_type";
|
||||
}
|
||||
|
@ -1,18 +1,18 @@
|
||||
/*
|
||||
* Copyright (C) 2022 github.com/REAndroid
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
/*
|
||||
* Copyright (C) 2022 github.com/REAndroid
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.reandroid.arsc.chunk.xml;
|
||||
|
||||
import com.reandroid.arsc.decoder.ValueDecoder;
|
||||
@ -75,6 +75,14 @@ public class ResXmlTextNode extends ResXmlNode {
|
||||
getResXmlText().setTextReference(ref);
|
||||
}
|
||||
@Override
|
||||
void onRemoved(){
|
||||
getResXmlText().onRemoved();
|
||||
}
|
||||
@Override
|
||||
void linkStringReferences(){
|
||||
getResXmlText().linkStringReferences();
|
||||
}
|
||||
@Override
|
||||
public String toString(){
|
||||
String txt=getText();
|
||||
if(txt!=null){
|
||||
|
Loading…
x
Reference in New Issue
Block a user