Merge "Allow to configure an alternative image server"
diff --git a/src/main/java/com/googlesource/gerrit/plugins/imagare/GetConfig.java b/src/main/java/com/googlesource/gerrit/plugins/imagare/GetConfig.java
index 1bf862a..426a855 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/imagare/GetConfig.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/imagare/GetConfig.java
@@ -15,6 +15,7 @@
 package com.googlesource.gerrit.plugins.imagare;
 
 import com.google.common.base.MoreObjects;
+import com.google.gerrit.extensions.annotations.PluginCanonicalWebUrl;
 import com.google.gerrit.extensions.annotations.PluginName;
 import com.google.gerrit.extensions.restapi.RestReadView;
 import com.google.gerrit.server.config.ConfigResource;
@@ -25,11 +26,16 @@
 public class GetConfig implements RestReadView<ConfigResource> {
 
   private final PluginConfig cfg;
+  private final String pluginName;
+  private final String canonicalWebUrl;
 
   @Inject
   public GetConfig(PluginConfigFactory cfgFactory,
-      @PluginName String pluginName) {
+      @PluginName String pluginName,
+      @PluginCanonicalWebUrl String canonicalWebUrl) {
     this.cfg = cfgFactory.getFromGerritConfig(pluginName);
+    this.pluginName = pluginName;
+    this.canonicalWebUrl = canonicalWebUrl;
   }
 
   @Override
@@ -45,12 +51,40 @@
     if (!info.stage) {
       info.stage = null;
     }
+    boolean enableImageServer = cfg.getBoolean("enableImageServer", true);
+    info.enableImageServer = enableImageServer;
+    if (!info.enableImageServer) {
+      info.enableImageServer = null;
+    }
+
+    if (enableImageServer) {
+      info.pattern = escapeRegexpForJavaScript(canonicalWebUrl)
+          + "project/.*/rev/.*/.*\\.(jpg|jpeg|png|gif|bmp|ico|svg|tif|tiff)";
+      info.uploadUrl = "#/x/" + pluginName + "/upload";
+    } else {
+      info.pattern = cfg.getString("pattern");
+      info.uploadUrl = cfg.getString("uploadUrl");
+    }
+
     return info;
   }
 
+  /**
+   * Escapes a string for being used in a JavaScript regexp.
+   * The following characters must be escaped: . * + ? ^ $ { } ( ) | [ ] / \
+   * @param s string to be escaped
+   * @return the escaped string
+   */
+  private String escapeRegexpForJavaScript(String s) {
+    return s.replaceAll("([.*+?^${}()|\\[\\]\\/\\\\])", "\\\\$1");
+  }
+
   public static class ConfigInfo {
     String defaultProject;
     LinkDecoration linkDecoration;
     Boolean stage;
+    Boolean enableImageServer;
+    String pattern;
+    String uploadUrl;
   }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/imagare/GetPreference.java b/src/main/java/com/googlesource/gerrit/plugins/imagare/GetPreference.java
index c470254..0711af9 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/imagare/GetPreference.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/imagare/GetPreference.java
@@ -83,6 +83,8 @@
       info.stage = null;
     }
 
+    info.pattern = globalCfg.pattern;
+
     return info;
   }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/imagare/HttpModule.java b/src/main/java/com/googlesource/gerrit/plugins/imagare/HttpModule.java
index daf5355..f83a243 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/imagare/HttpModule.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/imagare/HttpModule.java
@@ -14,18 +14,34 @@
 
 package com.googlesource.gerrit.plugins.imagare;
 
+import com.google.gerrit.extensions.annotations.PluginName;
 import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.gerrit.extensions.webui.GwtPlugin;
 import com.google.gerrit.extensions.webui.JavaScriptPlugin;
 import com.google.gerrit.extensions.webui.WebUiPlugin;
 import com.google.gerrit.httpd.plugins.HttpPluginModule;
+import com.google.gerrit.server.config.PluginConfigFactory;
+import com.google.inject.Inject;
 
 public class HttpModule extends HttpPluginModule {
 
+  private final PluginConfigFactory cfgFactory;
+  private final String pluginName;
+
+  @Inject
+  HttpModule(PluginConfigFactory cfgFactory,
+      @PluginName String pluginName) {
+    this.cfgFactory = cfgFactory;
+    this.pluginName = pluginName;
+  }
+
   @Override
   protected void configureServlets() {
-    serveRegex("^" + ImageServlet.PATH_PREFIX + "(.+)?$")
-        .with(ImageServlet.class);
+    if (cfgFactory.getFromGerritConfig(pluginName, true)
+        .getBoolean("enableImageServer", true)) {
+      serveRegex("^" + ImageServlet.PATH_PREFIX + "(.+)?$")
+          .with(ImageServlet.class);
+    }
 
     DynamicSet.bind(binder(), WebUiPlugin.class)
         .toInstance(new GwtPlugin("imagare"));
diff --git a/src/main/java/com/googlesource/gerrit/plugins/imagare/ImagareMenu.java b/src/main/java/com/googlesource/gerrit/plugins/imagare/ImagareMenu.java
index 804538e..975c6ae 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/imagare/ImagareMenu.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/imagare/ImagareMenu.java
@@ -16,6 +16,8 @@
 
 import com.google.gerrit.extensions.annotations.PluginName;
 import com.google.gerrit.extensions.webui.TopMenu;
+import com.google.gerrit.server.config.PluginConfig;
+import com.google.gerrit.server.config.PluginConfigFactory;
 import com.google.inject.Inject;
 
 import java.util.ArrayList;
@@ -26,10 +28,20 @@
   private final List<MenuEntry> menuEntries;
 
   @Inject
-  public ImagareMenu(@PluginName String pluginName) {
+  public ImagareMenu(PluginConfigFactory cfgFactory,
+      @PluginName String pluginName) {
     menuEntries = new ArrayList<>();
-    menuEntries.add(new MenuEntry("Tools", Collections
-        .singletonList(new MenuItem("Image Upload", "#/x/" + pluginName + "/upload", ""))));
+    PluginConfig cfg = cfgFactory.getFromGerritConfig(pluginName, true);
+    if (cfg.getBoolean("enableImageServer", true)) {
+      menuEntries.add(new MenuEntry("Tools", Collections
+          .singletonList(new MenuItem("Image Upload", "#/x/" + pluginName + "/upload", ""))));
+    } else {
+      String uploadUrl = cfg.getString("uploadUrl");
+      if (uploadUrl != null) {
+        menuEntries.add(new MenuEntry("Tools", Collections
+            .singletonList(new MenuItem("Image Upload", uploadUrl))));
+      }
+    }
   }
 
   @Override
diff --git a/src/main/java/com/googlesource/gerrit/plugins/imagare/Module.java b/src/main/java/com/googlesource/gerrit/plugins/imagare/Module.java
index 309e5fd..11c221a 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/imagare/Module.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/imagare/Module.java
@@ -21,27 +21,49 @@
 import static com.googlesource.gerrit.plugins.imagare.ImageResource.IMAGE_KIND;
 
 import com.google.gerrit.extensions.annotations.Exports;
+import com.google.gerrit.extensions.annotations.PluginName;
 import com.google.gerrit.extensions.registration.DynamicMap;
 import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.gerrit.extensions.restapi.RestApiModule;
 import com.google.gerrit.extensions.webui.TopMenu;
+import com.google.gerrit.server.config.PluginConfigFactory;
 import com.google.inject.AbstractModule;
+import com.google.inject.Inject;
 
 public class Module extends AbstractModule {
 
+  private final PluginConfigFactory cfgFactory;
+  private final String pluginName;
+
+  @Inject
+  Module(PluginConfigFactory cfgFactory,
+      @PluginName String pluginName) {
+    this.cfgFactory = cfgFactory;
+    this.pluginName = pluginName;
+  }
+
   @Override
   protected void configure() {
-    DynamicSet.bind(binder(), TopMenu.class).to(ImagareMenu.class);
-    bind(com.google.gerrit.extensions.config.CapabilityDefinition.class)
+    if (cfgFactory.getFromGerritConfig(pluginName, true)
+        .getBoolean("enableImageServer", true)) {
+      bind(com.google.gerrit.extensions.config.CapabilityDefinition.class)
         .annotatedWith(Exports.named(DELETE_OWN_IMAGES))
         .to(DeleteOwnImagesCapability.class);
+      install(new RestApiModule() {
+        @Override
+        protected void configure() {
+          DynamicMap.mapOf(binder(), IMAGE_KIND);
+          bind(ImagesCollection.class);
+          child(PROJECT_KIND, "images").to(ImagesCollection.class);
+          delete(IMAGE_KIND).to(DeleteImage.class);
+        }
+      });
+    }
+
+    DynamicSet.bind(binder(), TopMenu.class).to(ImagareMenu.class);
     install(new RestApiModule() {
       @Override
       protected void configure() {
-        DynamicMap.mapOf(binder(), IMAGE_KIND);
-        bind(ImagesCollection.class);
-        child(PROJECT_KIND, "images").to(ImagesCollection.class);
-        delete(IMAGE_KIND).to(DeleteImage.class);
         get(CONFIG_KIND, "config").to(GetConfig.class);
         put(CONFIG_KIND, "config").to(PutConfig.class);
         get(ACCOUNT_KIND, "preference").to(GetPreference.class);
diff --git a/src/main/java/com/googlesource/gerrit/plugins/imagare/PutConfig.java b/src/main/java/com/googlesource/gerrit/plugins/imagare/PutConfig.java
index 026d7aa..1227810 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/imagare/PutConfig.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/imagare/PutConfig.java
@@ -43,6 +43,8 @@
     public String defaultProject;
     public LinkDecoration linkDecoration;
     public Boolean stage;
+    public String pattern;
+    public String uploadUrl;
   }
 
   private final PluginConfigFactory cfgFactory;
@@ -95,6 +97,14 @@
       }
     }
 
+    if (input.pattern != null) {
+      cfg.setString("plugin", pluginName, "pattern", input.pattern);
+    }
+
+    if (input.uploadUrl != null) {
+      cfg.setString("plugin", pluginName, "uploadUrl", input.uploadUrl);
+    }
+
     cfg.save();
     cfgFactory.getFromGerritConfig(pluginName, true);
     return Response.<String> ok("OK");
diff --git a/src/main/java/com/googlesource/gerrit/plugins/imagare/client/ConfigInfo.java b/src/main/java/com/googlesource/gerrit/plugins/imagare/client/ConfigInfo.java
index f503031..901856a 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/imagare/client/ConfigInfo.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/imagare/client/ConfigInfo.java
@@ -28,6 +28,7 @@
   private final native String link_decoration() /*-{ return this.link_decoration; }-*/;
 
   final native boolean stage() /*-{ return this.stage ? true : false; }-*/;
+  final native boolean enableImageServer() /*-{ return this.enable_image_server ? true : false; }-*/;
 
   final native void setDefaultProject(String p) /*-{ this.default_project = p; }-*/;
   final native void setLinkDecoration(String d) /*-{ this.link_decoration = d; }-*/;
diff --git a/src/main/java/com/googlesource/gerrit/plugins/imagare/client/ImagareAdminScreen.java b/src/main/java/com/googlesource/gerrit/plugins/imagare/client/ImagareAdminScreen.java
index b6d77f2..a0092d6 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/imagare/client/ImagareAdminScreen.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/imagare/client/ImagareAdminScreen.java
@@ -21,15 +21,22 @@
 public class ImagareAdminScreen extends ImagareConfigScreen {
 
   static class Factory implements Screen.EntryPoint {
+    private final boolean enableImageServer;
+
+    Factory(boolean enableImageServer) {
+      this.enableImageServer = enableImageServer;
+    }
+
     @Override
     public void onLoad(Screen screen) {
       screen.setPageTitle("Imagare Admin");
-      screen.show(new ImagareAdminScreen());
+      screen.show(new ImagareAdminScreen(enableImageServer));
     }
   }
 
-  ImagareAdminScreen() {
-    super(new RestApi("config").id("server").view(Plugin.get().getPluginName(),
-        "config"));
+  ImagareAdminScreen(boolean enableImageServer) {
+    super(enableImageServer,
+        new RestApi("config").id("server").view(
+            Plugin.get().getPluginName(), "config"));
   }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/imagare/client/ImagareConfigScreen.java b/src/main/java/com/googlesource/gerrit/plugins/imagare/client/ImagareConfigScreen.java
index f554fea..0a4f83d 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/imagare/client/ImagareConfigScreen.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/imagare/client/ImagareConfigScreen.java
@@ -29,6 +29,7 @@
 import com.google.gwt.user.client.ui.VerticalPanel;
 
 public abstract class ImagareConfigScreen extends VerticalPanel {
+  protected final boolean enableImageServer;
   private final RestApi restApi;
 
   private TextBox projectBox;
@@ -36,7 +37,8 @@
   private CheckBox stageBox;
   private Button saveButton;
 
-  protected ImagareConfigScreen(RestApi restApi) {
+  protected ImagareConfigScreen(boolean enableImageServer, RestApi restApi) {
+    this.enableImageServer = enableImageServer;
     this.restApi = restApi;
     setStyleName("imagare-config-screen");
     restApi.get(new AsyncCallback<ConfigInfo>() {
@@ -55,18 +57,6 @@
   protected void display(ConfigInfo info) {
     HorizontalPanel p = new HorizontalPanel();
     p.setStyleName("imagare-label-panel");
-    p.add(new Label("Project"));
-    Image projectInfo = new Image(ImagarePlugin.RESOURCES.info());
-    projectInfo.setTitle("The default project for the image upload.");
-    p.add(projectInfo);
-    p.add(new Label(":"));
-    projectBox = new TextBox();
-    projectBox.setValue(info.getDefaultProject());
-    p.add(projectBox);
-    add(p);
-
-    p = new HorizontalPanel();
-    p.setStyleName("imagare-label-panel");
     p.add(new Label("Link Decoration"));
     Image linkDecorationInfo = new Image(ImagarePlugin.RESOURCES.info());
     linkDecorationInfo.setTitle("Decoration for image links in the Gerrit WebUI."
@@ -87,16 +77,30 @@
     p.add(linkDecorationBox);
     add(p);
 
-    p = new HorizontalPanel();
-    p.setStyleName("imagare-label-panel");
-    stageBox = new CheckBox("Stage images before upload");
-    stageBox.setValue(info.stage());
-    p.add(stageBox);
-    Image stageInfo = new Image(ImagarePlugin.RESOURCES.info());
-    stageInfo.setTitle("Images are not uploaded immediately but put into a "
-        + "staging area. The upload must be triggered explicitely.");
-    p.add(stageInfo);
-    add(p);
+    if (enableImageServer) {
+      p = new HorizontalPanel();
+      p.setStyleName("imagare-label-panel");
+      p.add(new Label("Project"));
+      Image projectInfo = new Image(ImagarePlugin.RESOURCES.info());
+      projectInfo.setTitle("The default project for the image upload.");
+      p.add(projectInfo);
+      p.add(new Label(":"));
+      projectBox = new TextBox();
+      projectBox.setValue(info.getDefaultProject());
+      p.add(projectBox);
+      add(p);
+
+      p = new HorizontalPanel();
+      p.setStyleName("imagare-label-panel");
+      stageBox = new CheckBox("Stage images before upload");
+      stageBox.setValue(info.stage());
+      p.add(stageBox);
+      Image stageInfo = new Image(ImagarePlugin.RESOURCES.info());
+      stageInfo.setTitle("Images are not uploaded immediately but put into a "
+          + "staging area. The upload must be triggered explicitely.");
+      p.add(stageInfo);
+      add(p);
+    }
 
     HorizontalPanel buttons = new HorizontalPanel();
     add(buttons);
@@ -111,9 +115,11 @@
     });
     buttons.add(saveButton);
     saveButton.setEnabled(false);
-    OnEditEnabler onEditEnabler = new OnEditEnabler(saveButton, projectBox);
-    onEditEnabler.listenTo(linkDecorationBox);
-    onEditEnabler.listenTo(stageBox);
+    OnEditEnabler onEditEnabler = new OnEditEnabler(saveButton, linkDecorationBox);
+    if (enableImageServer) {
+      onEditEnabler.listenTo(projectBox);
+      onEditEnabler.listenTo(stageBox);
+    }
 
     projectBox.setFocus(true);
     saveButton.setEnabled(false);
@@ -121,9 +127,11 @@
 
   private void doSave() {
     ConfigInfo in = ConfigInfo.create();
-    in.setDefaultProject(projectBox.getValue());
     in.setLinkDecoration(linkDecorationBox.getValue(linkDecorationBox.getSelectedIndex()));
-    in.setStage(stageBox.getValue());
+    if (enableImageServer) {
+      in.setDefaultProject(projectBox.getValue());
+      in.setStage(stageBox.getValue());
+    }
     restApi.put(in, new AsyncCallback<JavaScriptObject>() {
         @Override
         public void onSuccess(JavaScriptObject result) {
diff --git a/src/main/java/com/googlesource/gerrit/plugins/imagare/client/ImagarePlugin.java b/src/main/java/com/googlesource/gerrit/plugins/imagare/client/ImagarePlugin.java
index 240c770..5b04b81 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/imagare/client/ImagarePlugin.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/imagare/client/ImagarePlugin.java
@@ -16,17 +16,35 @@
 
 import com.google.gerrit.plugin.client.Plugin;
 import com.google.gerrit.plugin.client.PluginEntryPoint;
+import com.google.gerrit.plugin.client.rpc.RestApi;
 import com.google.gwt.core.client.GWT;
+import com.google.gwt.user.client.rpc.AsyncCallback;
 
 public class ImagarePlugin extends PluginEntryPoint {
   public static final Resources RESOURCES = GWT.create(Resources.class);
 
   @Override
   public void onPluginLoad() {
-    Plugin.get().screen("upload", new ImageUploadScreen.Factory());
-    Plugin.get().screen("settings", new ImagareAdminScreen.Factory());
-    Plugin.get().settingsScreen("preferences",
-        Plugin.get().getName() + " Preferences",
-        new ImagarePreferenceScreen.Factory());
+    new RestApi("config").id("server").view(Plugin.get().getPluginName(),
+        "config").get(new AsyncCallback<ConfigInfo>() {
+          @Override
+          public void onSuccess(ConfigInfo info) {
+            if (info.enableImageServer()) {
+              Plugin.get().screen("upload", new ImageUploadScreen.Factory());
+            }
+
+            Plugin.get().screen("settings",
+                new ImagareAdminScreen.Factory(info.enableImageServer()));
+            Plugin.get().settingsScreen("preferences",
+                Plugin.get().getName() + " Preferences",
+                new ImagarePreferenceScreen.Factory(info.enableImageServer()));
+          }
+
+          @Override
+          public void onFailure(Throwable caught) {
+            // never invoked
+          }
+        });
+
   }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/imagare/client/ImagarePreferenceScreen.java b/src/main/java/com/googlesource/gerrit/plugins/imagare/client/ImagarePreferenceScreen.java
index 9d63ecd..bae87be 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/imagare/client/ImagarePreferenceScreen.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/imagare/client/ImagarePreferenceScreen.java
@@ -24,28 +24,37 @@
 public class ImagarePreferenceScreen extends ImagareConfigScreen {
 
   static class Factory implements Screen.EntryPoint {
+    private final boolean enableImageServer;
+
+    Factory(boolean enableImageServer) {
+      this.enableImageServer = enableImageServer;
+    }
+
     @Override
     public void onLoad(Screen screen) {
       screen.setPageTitle("Imagare Preferences");
-      screen.show(new ImagarePreferenceScreen());
+      screen.show(new ImagarePreferenceScreen(enableImageServer));
     }
   }
 
-  ImagarePreferenceScreen() {
-    super(new RestApi("accounts").id("self")
-        .view(Plugin.get().getPluginName(), "preference"));
+  ImagarePreferenceScreen(boolean enableImageServer) {
+    super(enableImageServer,
+        new RestApi("accounts").id("self").view(
+            Plugin.get().getPluginName(), "preference"));
   }
 
   @Override
   protected void display(ConfigInfo info) {
-    HorizontalPanel p = new HorizontalPanel();
-    p.setStyleName("imagare-menu-panel");
-    Anchor prefsAnchor = new Anchor(new ImageResourceRenderer().render(
-        ImagarePlugin.RESOURCES.image()),
-        "#/x/" + Plugin.get().getPluginName() + "/upload");
-    prefsAnchor.setTitle("Upload Image");
-    p.add(prefsAnchor);
-    add(p);
+    if (enableImageServer) {
+      HorizontalPanel p = new HorizontalPanel();
+      p.setStyleName("imagare-menu-panel");
+      Anchor uploadAnchor = new Anchor(new ImageResourceRenderer().render(
+          ImagarePlugin.RESOURCES.image()),
+          "#/x/" + Plugin.get().getPluginName() + "/upload");
+      uploadAnchor.setTitle("Upload Image");
+      p.add(uploadAnchor);
+      add(p);
+    }
 
     super.display(info);
   }
diff --git a/src/main/resources/Documentation/about.md b/src/main/resources/Documentation/about.md
index c8fc4bd..9279cb3 100644
--- a/src/main/resources/Documentation/about.md
+++ b/src/main/resources/Documentation/about.md
@@ -5,9 +5,12 @@
 the uploaded image is available. This is useful for sharing screenshots
 and linking them from review comments.
 
+You can either [use the images server that comes with the plugin](#setup)
+or [configure an external image server](#external).
+
 <a id="setup"></a>
-Setup
------
+Setup of Image Server
+---------------------
 The uploaded images are stored in the `refs/images/*` namespace of the
 `All-Projects` project.
 
@@ -31,3 +34,24 @@
 To allow the deletion of any uploaded image the
 [Force Push](../../../Documentation/access-control.html#category_push)
 access right can be assigned on the images namespace.
+
+<a id="external"></a>
+Configure external Image Server
+-------------------------------
+
+To configure an external image server the image server that comes with
+the plugin must be disabled. This is done by setting the
+[enableImageServer](config.html#enable-image-server) to `false`.
+
+A regular expression to match URLs of images that should be embedded
+must be configured as [pattern](config.html#pattern).
+
+Optionally also a [URL for uploading images](config.html#upload-url)
+can be defined.
+
+```
+  [plugin "imagare"]
+    enableImageServer = false
+    pattern = http:\\/\\/i\\.imgur\\.com\\/.*\\.png
+    uploadUrl = http://imgur.com
+```
diff --git a/src/main/resources/Documentation/config.md b/src/main/resources/Documentation/config.md
index c001bb4..5fbbfc7 100644
--- a/src/main/resources/Documentation/config.md
+++ b/src/main/resources/Documentation/config.md
@@ -23,3 +23,27 @@
 <a id="stage">
 `plugin.@PLUGIN@.stage`
 :	Whether images should be staged before upload.
+
+<a id="enable-image-server">
+`plugin.@PLUGIN@.enableImageServer`
+:	Whether image server functionality should be enabled.
+    If `true` images can be uploaded to Gerrit.
+    When this parameter is changed the plugin must be reloaded.
+    By default `true`.
+
+<a id="pattern">
+`plugin.@PLUGIN@.pattern`
+:	URL pattern for image links. Matching images will be decorated.
+    The URL pattern is a JavaScript regexp, the following characters
+    must be escaped: `.`, `*`, `+`, `?`, `^`, `$`, `{`, `}`, `(`,
+    `)`, `|`, `[`, `]`, `/`, `\`
+    Images that match this pattern are rendered in the UI even when the
+    corresponding mime type is not configured as safe in the Gerrit
+    configuration.
+    Must be set if an external image server is used.
+
+<a id="upload-url">
+`plugin.@PLUGIN@.uploadUrl`
+:	Optional, URL for uploading images.
+    May be set if an external image server is used.
+    If Gerrit is used as image server the value is ignored.
diff --git a/src/main/resources/Documentation/rest-api-accounts.md b/src/main/resources/Documentation/rest-api-accounts.md
index 7ba476d..e1952b3 100644
--- a/src/main/resources/Documentation/rest-api-accounts.md
+++ b/src/main/resources/Documentation/rest-api-accounts.md
@@ -21,9 +21,8 @@
   GET /accounts/self/@PLUGIN@~preference HTTP/1.0
 ```
 
-As response a [ConfigInfo](rest-api-config.html#config-info) entity is
-returned that contains the preferences of a user for the @PLUGIN@
-plugin.
+As response a [PreferenceInfo](#preference-info) entity is returned
+that contains the preferences of a user for the @PLUGIN@ plugin.
 
 #### Response
 
@@ -35,7 +34,8 @@
   )]}'
   {
     "default_project": "All-Images",
-    "link_decoration": "INLINE"
+    "link_decoration": "INLINE",
+    "enable\_image_server": true
   }
 ```
 
@@ -44,7 +44,7 @@
 
 Sets the configuration of the @PLUGIN@ plugin.
 
-The new preferences must be specified as a [ConfigInfo](rest-api-config.html#config-info)
+The new preferences must be specified as a [PreferenceInfo](#preference-info)
 entity in the request body. Not setting a parameter means that the
 parameter is unset and that the global setting for this parameter
 applies again.
@@ -60,6 +60,24 @@
   }
 ```
 
+<a id="json-entities">JSON Entities
+-----------------------------------
+
+### <a id="preference-info"></a>PreferenceInfo
+
+The `PreferenceInfo` entity contains the configuration of the
+@PLUGIN@ plugin.
+
+* _default\_project_: The project to which images should be uploaded by
+  default.
+* _link\_decoration_: Decoration for image links in the Gerrit WebUI.
+  `NONE`: no decoration, `TOOLTIP`: the image is shown as tooltip on
+  mouse over an image link, `INLINE`: the image is inlined instead of
+  the URL.
+* _stage_: Whether images should be staged before upload.
+* _pattern_: JavaScript Regular expression to match URLs of images
+  that should be embedded (read-only).
+
 SEE ALSO
 --------
 
diff --git a/src/main/resources/Documentation/rest-api-config.md b/src/main/resources/Documentation/rest-api-config.md
index 23a959c..2d223cc 100644
--- a/src/main/resources/Documentation/rest-api-config.md
+++ b/src/main/resources/Documentation/rest-api-config.md
@@ -33,7 +33,8 @@
 
   )]}'
   {
-    "default_project": "All-Images"
+    "default_project": "All-Images",
+    "enable\_image_server": true
   }
 ```
 
@@ -72,6 +73,10 @@
   mouse over an image link, `INLINE`: the image is inlined instead of
   the URL.
 * _stage_: Whether images should be staged before upload.
+* _enable\_image\_server_: Whether Gerrit is used as image server.
+* _pattern_: JavaScript Regular expression to match URLs of images
+  that should be embedded.
+* _upload\_url_: URL to upload images.
 
 SEE ALSO
 --------
diff --git a/src/main/resources/Documentation/rest-api-projects.md b/src/main/resources/Documentation/rest-api-projects.md
index 83bf26e..9e738eb 100644
--- a/src/main/resources/Documentation/rest-api-projects.md
+++ b/src/main/resources/Documentation/rest-api-projects.md
@@ -22,6 +22,8 @@
 [Create Reference](../../../Documentation/access-control.html#category_create)
 access right on the `refs/images/*` namespace of the project.
 
+Only available if [image server is enabled](config.html#enable-image-server).
+
 #### Request
 
 ```
@@ -50,6 +52,8 @@
 granted the `Delete Own Images` global capability which allows deleting
 own images.
 
+Only available if [image server is enabled](config.html#enable-image-server).
+
 #### Request
 
 ```
diff --git a/src/main/resources/static/imagare.js b/src/main/resources/static/imagare.js
index 244350b..e9d0705 100644
--- a/src/main/resources/static/imagare.js
+++ b/src/main/resources/static/imagare.js
@@ -15,18 +15,22 @@
 Gerrit.install(function(self) {
     function onHistory(t) {
       Gerrit.get('/accounts/self/preference', function(r) {
+        if (!r.pattern) {
+          return;
+        }
+
         if ('TOOLTIP' === r.link_decoration) {
-          addTooltips();
+          addTooltips(r.pattern);
         } else if ('INLINE' === r.link_decoration) {
-          inlineImages();
+          inlineImages(r.pattern);
         }
       });
     }
 
-    function inlineImages() {
+    function inlineImages(pattern) {
       var l = document.links;
       for(var i = 0; i < l.length; i++) {
-        if (isImage(l[i].href)) {
+        if (l[i].href.match(pattern)) {
           var a = document.createElement('a');
           a.setAttribute('href', l[i].href);
           var img = document.createElement('img');
@@ -38,10 +42,10 @@
       }
     }
 
-    function addTooltips() {
+    function addTooltips(pattern) {
       var l = document.links;
       for(var i = 0; i < l.length; i++) {
-        if (isImage(l[i].href)) {
+        if (l[i].href.match(pattern)) {
           l[i].onmouseover = function (evt) {
             var img = document.createElement('img');
             img.setAttribute('src', this.href);
@@ -55,9 +59,5 @@
       }
     }
 
-    function isImage(href) {
-      return href.match(window.location.hostname + '.*project/.*/rev/.*/.*\.(jpg|jpeg|png|gif|bmp|ico|svg|tif|tiff)')
-    }
-
     Gerrit.on('history', onHistory);
   });