@Test
  public void getLinks() throws Exception {
    String link1Rel = "foo";
    String link2Rel = "bar";
    Uri link1Href = Uri.parse("http://example.org/foo");
    Uri link2Href = Uri.parse("/bar");
    String xml =
        "<ModulePrefs title='links'>"
            + "  <Link rel='"
            + link1Rel
            + "' href='"
            + link1Href
            + "'/>"
            + "  <Link rel='"
            + link2Rel
            + "' href='"
            + link2Href
            + "'/>"
            + "</ModulePrefs>";

    ModulePrefs prefs =
        new ModulePrefs(XmlUtil.parse(xml), SPEC_URL).substitute(new Substitutions());

    assertEquals(link1Href, prefs.getLinks().get(link1Rel).getHref());
    assertEquals(SPEC_URL.resolve(link2Href), prefs.getLinks().get(link2Rel).getHref());
  }
 /**
  * @param request the outbound http request
  * @return true if the request should be authenticated
  */
 private boolean isAuthEndpoint(HttpRequest request) {
   Uri uri = request.getUri();
   String path = uri.getPath();
   for (String authEndpoint : authEndpoints) {
     if (path.startsWith(authEndpoint)) {
       return true;
     }
   }
   return false;
 }
  private void expectMime(String expectedMime, String contentMime, String outputMime)
      throws Exception {
    String url =
        "http://example.org/file.img?" + Param.REWRITE_MIME_TYPE.getKey() + '=' + expectedMime;
    String domain = "example.org";

    setupProxyRequestMock(domain, url, false, -1, expectedMime, null);

    HttpRequest req = new HttpRequest(Uri.parse(url)).setRewriteMimeType(expectedMime);

    HttpResponse resp =
        new HttpResponseBuilder()
            .setResponseString("Hello")
            .addHeader("Content-Type", contentMime)
            .create();

    expect(pipeline.execute(req)).andReturn(resp);

    replay();
    HttpResponse response = proxyHandler.fetch(request);
    verify();

    assertEquals(outputMime, response.getHeader("Content-Type"));
    reset();
  }
Exemple #4
0
  /** Generate a remote content request based on the parameters sent from the client. */
  private HttpRequest buildHttpRequest(HttpServletRequest request) throws GadgetException {
    Uri url = validateUrl(request.getParameter(URL_PARAM));

    HttpRequest req = new HttpRequest(url);

    req.setContainer(getContainer(request));
    if (request.getParameter(GADGET_PARAM) != null) {
      req.setGadget(Uri.parse(request.getParameter(GADGET_PARAM)));
    }

    // Allow the rewriter to use an externally forced mime type. This is needed
    // allows proper rewriting of <script src="x"/> where x is returned with
    // a content type like text/html which unfortunately happens all too often
    req.setRewriteMimeType(request.getParameter(REWRITE_MIME_TYPE_PARAM));

    req.setIgnoreCache(getIgnoreCache(request));

    // If the proxy request specifies a refresh param then we want to force the min TTL for
    // the retrieved entry in the cache regardless of the headers on the content when it
    // is fetched from the original source.
    if (request.getParameter(REFRESH_PARAM) != null) {
      try {
        req.setCacheTtl(Integer.parseInt(request.getParameter(REFRESH_PARAM)));
      } catch (NumberFormatException nfe) {
        // Ignore
      }
    }

    return req;
  }
  @Test
  public void testInvalidHeaderDropped() throws Exception {
    String url = "http://example.org/mypage.html";
    String domain = "example.org";

    setupProxyRequestMock(domain, url, true, -1, null, null);

    HttpRequest req = new HttpRequest(Uri.parse(url)).setIgnoreCache(true);
    String contentType = "text/html; charset=UTF-8";
    HttpResponse resp =
        new HttpResponseBuilder()
            .setResponseString("Hello")
            .addHeader("Content-Type", contentType)
            .addHeader("Content-Length", "200") // Disallowed header.
            .addHeader(":", "someDummyValue") // Invalid header name.
            .create();

    expect(pipeline.execute(req)).andReturn(resp);

    replay();

    HttpResponse recorder = proxyHandler.fetch(request);

    verify();
    assertNull(recorder.getHeader(":"));
    assertNull(recorder.getHeader("Content-Length"));
    assertEquals(recorder.getHeader("Content-Type"), contentType);
  }
 private void setupProxyRequestMock(
     String host, String url, boolean noCache, int refresh, String rewriteMime, String fallbackUrl)
     throws Exception {
   request =
       new ProxyUriManager.ProxyUri(
           refresh, false, noCache, ContainerConfig.DEFAULT_CONTAINER, null, Uri.parse(url));
   request.setFallbackUrl(fallbackUrl);
   request.setRewriteMimeType(rewriteMime);
 }
 private void setupNoArgsProxyRequestMock(String host, String url) throws Exception {
   request =
       new ProxyUriManager.ProxyUri(
           -1,
           false,
           false,
           ContainerConfig.DEFAULT_CONTAINER,
           null,
           url != null ? Uri.parse(url) : null);
 }
 @Before
 public void setUp() {
   servlet.setMakeRequestHandler(handler);
   expect(fixture.request.getHeaderNames()).andReturn(EMPTY_ENUM).anyTimes();
   expect(fixture.request.getParameter(MakeRequestHandler.METHOD_PARAM))
       .andReturn("GET")
       .anyTimes();
   expect(fixture.request.getParameter(ProxyBase.URL_PARAM))
       .andReturn(REQUEST_URL.toString())
       .anyTimes();
 }
 private void assertResponseOk(int expectedStatus, String expectedBody) throws JSONException {
   if (recorder.getHttpStatusCode() == HttpServletResponse.SC_OK) {
     String body = recorder.getResponseAsString();
     assertStartsWith(MakeRequestHandler.UNPARSEABLE_CRUFT, body);
     body = body.substring(MakeRequestHandler.UNPARSEABLE_CRUFT.length());
     JSONObject object = new JSONObject(body);
     object = object.getJSONObject(REQUEST_URL.toString());
     assertEquals(expectedStatus, object.getInt("rc"));
     assertEquals(expectedBody, object.getString("body"));
   } else {
     fail("Invalid response for request.");
   }
 }
  @Test
  public void testFetch_noToken() throws Exception {

    Uri uri = Uri.parse("http://host?p=1");

    HttpRequest request = createMock(HttpRequest.class);
    expect(request.getUri()).andReturn(uri);
    expect(request.setUri(Uri.parse("http://host?p=1&st=default%3Anull"))).andReturn(request);
    replay(request);

    HttpResponse response = new HttpResponse();

    HttpFetcher fetcher = createMock(HttpFetcher.class);
    expect(fetcher.fetch(request)).andReturn(response);
    replay(fetcher);

    FakeUserHttpFetcher fakeFetcher;
    fakeFetcher = new FakeUserHttpFetcher(config, fetcher, crypter);
    fakeFetcher.fetch(request);

    verify(request);
    verify(fetcher);
  }
  @Test
  public void testWithBadTtl() throws Exception {
    String url = "http://example.org/file.evil";
    String domain = "example.org";

    setupProxyRequestMock(domain, url, false, -1, null, null);

    HttpRequest req = new HttpRequestCache(Uri.parse(url)).setCacheTtl(-1).setIgnoreCache(false);
    HttpResponse resp = new HttpResponse("Hello");
    expect(pipeline.execute(req)).andReturn(resp);

    replay();
    proxyHandler.fetch(request);
    verify();
  }
  /**
   * Derived from Harmony Resolves a given url relative to this url. Resolution rules are the same
   * as for {@code java.net.URI.resolve(URI)}
   */
  public Uri resolve(Uri relative) {
    if (relative == null) {
      return null;
    }
    if (relative.isAbsolute()) {
      return relative;
    }

    UriBuilder result;
    if (StringUtils.isEmpty(relative.path)
        && relative.scheme == null
        && relative.authority == null
        && relative.query == null
        && relative.fragment != null) {
      // if the relative URI only consists of fragment,
      // the resolved URI is very similar to this URI,
      // except that it has the fragement from the relative URI.
      result = new UriBuilder(this);
      result.setFragment(relative.fragment);
    } else if (relative.scheme != null) {
      result = new UriBuilder(relative);
    } else if (relative.authority != null) {
      // if the relative URI has authority,
      // the resolved URI is almost the same as the relative URI,
      // except that it has the scheme of this URI.
      result = new UriBuilder(relative);
      result.setScheme(scheme);
    } else {
      // since relative URI has no authority,
      // the resolved URI is very similar to this URI,
      // except that it has the query and fragment of the relative URI,
      // and the path is different.
      result = new UriBuilder(this);
      result.setFragment(relative.fragment);
      result.setQuery(relative.query);
      String relativePath = (relative.path == null) ? "" : relative.path;
      if (relativePath.startsWith("/")) { // $NON-NLS-1$
        result.setPath(relativePath);
      } else {
        // resolve a relative reference
        int endindex = path.lastIndexOf('/') + 1;
        result.setPath(normalizePath(path.substring(0, endindex) + relativePath));
      }
    }
    Uri resolved = result.toUri();
    validate(resolved);
    return resolved;
  }
  @Test
  public void testGetFallback() throws Exception {
    String url = "http://example.org/file.evil";
    String domain = "example.org";
    String fallback_url = "http://fallback.com/fallback.png";

    setupProxyRequestMock(domain, url, true, -1, null, fallback_url);

    HttpRequest req = new HttpRequest(Uri.parse(url)).setIgnoreCache(true);
    HttpResponse resp = HttpResponse.error();
    HttpResponse fallback_resp = new HttpResponse("Fallback");
    expect(pipeline.execute(req)).andReturn(resp);
    expect(pipeline.execute(isA(HttpRequest.class))).andReturn(fallback_resp);

    replay();
    proxyHandler.fetch(request);
    verify();
  }
  @Test
  public void testHttpRequestFillsParentAndContainer() throws Exception {
    setupNoArgsProxyRequestMock("www.example.com", URL_ONE);
    // HttpRequest req = new HttpRequest(Uri.parse(URL_ONE));
    HttpResponse resp = new HttpResponseBuilder().setResponse(DATA_ONE.getBytes()).create();

    Capture<HttpRequest> httpRequest = new Capture<HttpRequest>();
    expect(pipeline.execute(capture(httpRequest))).andReturn(resp);

    replay();
    HttpResponse response = proxyHandler.fetch(request);
    verify();

    // Check that the HttpRequest passed in has all the relevant fields sets
    assertEquals("default", httpRequest.getValue().getContainer());
    assertEquals(Uri.parse(URL_ONE), httpRequest.getValue().getUri());

    assertEquals(DATA_ONE, response.getResponseAsString());
    assertTrue(rewriter.responseWasRewritten());
  }
  @Test
  public void testFetch_withToken() throws Exception {

    Uri uri = Uri.parse("http://host?p=1&st=sometoken");

    // We should get the request untouched.
    HttpRequest request = createMock(HttpRequest.class);
    expect(request.getUri()).andReturn(uri);
    replay(request);

    HttpResponse response = new HttpResponse();

    HttpFetcher fetcher = createMock(HttpFetcher.class);
    expect(fetcher.fetch(request)).andReturn(response);
    replay(fetcher);

    FakeUserHttpFetcher fakeFetcher;
    fakeFetcher = new FakeUserHttpFetcher(config, fetcher, crypter);
    fakeFetcher.fetch(request);

    verify(request);
    verify(fetcher);
  }
public class DefaultInvalidationServiceTest extends Assert {

  private static final Uri URI = Uri.parse("http://www.example.org/spec.xml");
  private static final HttpResponse CACHEABLE =
      new HttpResponseBuilder()
          .setResponseString("ORIGINALCONTENT")
          .setHeader("Cache-Control", "max-age=1000")
          .create();

  IMocksControl control;
  HttpCache cache;
  DefaultInvalidationService service;
  LruCacheProvider cacheProvider;
  FakeGadgetToken appxToken;
  FakeGadgetToken appyToken;

  DefaultRequestPipelineTest.FakeHttpFetcher fetcher;
  DefaultRequestPipelineTest.FakeOAuthRequestProvider oauth;
  DefaultRequestPipeline requestPipeline;
  HttpRequest signedRequest;

  @Before
  public void setUp() {
    cacheProvider = new LruCacheProvider(100);
    cache = new DefaultHttpCache(cacheProvider);
    service = new DefaultInvalidationService(cache, cacheProvider, new AtomicLong());
    appxToken = new FakeGadgetToken();
    appxToken.setAppId("AppX");
    appxToken.setOwnerId("OwnerX");
    appxToken.setViewerId("ViewerX");
    appyToken = new FakeGadgetToken();
    appyToken.setAppId("AppY");
    appyToken.setOwnerId("OwnerY");
    appyToken.setViewerId("ViewerY");

    signedRequest = new HttpRequest(URI);
    signedRequest.setAuthType(AuthType.SIGNED);
    signedRequest.setSecurityToken(appxToken);
    signedRequest.setOAuthArguments(new OAuthArguments());
    signedRequest.getOAuthArguments().setUseToken(OAuthArguments.UseToken.NEVER);
    signedRequest.getOAuthArguments().setSignOwner(true);
    signedRequest.getOAuthArguments().setSignViewer(true);

    fetcher = new DefaultRequestPipelineTest.FakeHttpFetcher();
    oauth = new DefaultRequestPipelineTest.FakeOAuthRequestProvider();
    requestPipeline =
        new DefaultRequestPipeline(
            fetcher,
            cache,
            oauth,
            new DefaultResponseRewriterRegistry(null, null),
            service,
            new HttpResponseMetadataHelper());
  }

  @Test
  public void testInvalidateUrl() throws Exception {
    cache.addResponse(new HttpRequest(URI), CACHEABLE);
    assertEquals(1, cacheProvider.createCache(DefaultHttpCache.CACHE_NAME).getSize());
    service.invalidateApplicationResources(ImmutableSet.of(URI), appxToken);
    assertEquals(0, cacheProvider.createCache(DefaultHttpCache.CACHE_NAME).getSize());
  }

  @Test
  public void testInvalidateUsers() throws Exception {
    service.invalidateUserResources(ImmutableSet.of("example.org:1", "example.org:2"), appxToken);
    service.invalidateUserResources(ImmutableSet.of("example.org:1", "example.org:2"), appyToken);
    assertEquals(4, cacheProvider.createCache(DefaultInvalidationService.CACHE_NAME).getSize());
    assertNotNull(
        cacheProvider
            .createCache(DefaultInvalidationService.CACHE_NAME)
            .getElement("INV_TOK:AppX:1"));
    assertNotNull(
        cacheProvider
            .createCache(DefaultInvalidationService.CACHE_NAME)
            .getElement("INV_TOK:AppX:2"));
    assertNotNull(
        cacheProvider
            .createCache(DefaultInvalidationService.CACHE_NAME)
            .getElement("INV_TOK:AppY:1"));
    assertNotNull(
        cacheProvider
            .createCache(DefaultInvalidationService.CACHE_NAME)
            .getElement("INV_TOK:AppY:2"));
  }

  @Test
  public void testFetchWithInvalidationEnabled() throws Exception {
    cache.addResponse(new HttpRequest(URI), CACHEABLE);
    assertEquals(CACHEABLE, requestPipeline.execute(new HttpRequest(URI)));
  }

  @Test
  public void testFetchInvalidatedContent() throws Exception {
    // Prime the cache
    cache.addResponse(new HttpRequest(URI), CACHEABLE);

    // Invalidate the entry
    service.invalidateApplicationResources(ImmutableSet.of(URI), appxToken);

    fetcher.response = new HttpResponseBuilder(CACHEABLE).setResponseString("NEWCONTENT1").create();
    assertEquals(requestPipeline.execute(new HttpRequest(URI)), fetcher.response);
  }

  @Test
  public void testFetchContentWithMarker() throws Exception {
    oauth.httpResponse = CACHEABLE;

    // First entry added to cache is unmarked
    HttpResponse httpResponse = requestPipeline.execute(signedRequest);
    assertEquals(CACHEABLE, httpResponse);
    assertEquals(1, cacheProvider.createCache(DefaultHttpCache.CACHE_NAME).getSize());

    // Invalidate content for OwnerX. Next entry will have owner mark
    service.invalidateUserResources(ImmutableSet.of("OwnerX"), appxToken);

    oauth.httpResponse =
        new HttpResponseBuilder(CACHEABLE).setResponseString("NEWCONTENT1").create();
    httpResponse = requestPipeline.execute(signedRequest);
    assertEquals("NEWCONTENT1", httpResponse.getResponseAsString());
    assertEquals("o=1;", httpResponse.getHeader(DefaultInvalidationService.INVALIDATION_HEADER));
    assertEquals(1, cacheProvider.createCache(DefaultHttpCache.CACHE_NAME).getSize());

    // Invalidate content for ViewerX. Next entry will have both owner and viewer mark
    service.invalidateUserResources(ImmutableSet.of("ViewerX"), appxToken);
    oauth.httpResponse =
        new HttpResponseBuilder(CACHEABLE).setResponseString("NEWCONTENT2").create();
    httpResponse = requestPipeline.execute(signedRequest);
    assertEquals("NEWCONTENT2", httpResponse.getResponseAsString());
    assertEquals(
        "o=1;v=2;", httpResponse.getHeader(DefaultInvalidationService.INVALIDATION_HEADER));
    assertEquals(1, cacheProvider.createCache(DefaultHttpCache.CACHE_NAME).getSize());
  }

  @Test
  public void testFetchContentSignedOwner() throws Exception {
    oauth.httpResponse = CACHEABLE;
    signedRequest.getOAuthArguments().setSignViewer(false);
    HttpResponse httpResponse = requestPipeline.execute(signedRequest);
    assertEquals(CACHEABLE, httpResponse);
    assertEquals(1, cacheProvider.createCache(DefaultHttpCache.CACHE_NAME).getSize());

    // Invalidate by owner only
    service.invalidateUserResources(ImmutableSet.of("OwnerX"), appxToken);

    oauth.httpResponse =
        new HttpResponseBuilder(CACHEABLE).setResponseString("NEWCONTENT1").create();
    httpResponse = requestPipeline.execute(signedRequest);
    assertEquals("NEWCONTENT1", httpResponse.getResponseAsString());
    assertEquals("o=1;", httpResponse.getHeader(DefaultInvalidationService.INVALIDATION_HEADER));
    assertEquals(1, cacheProvider.createCache(DefaultHttpCache.CACHE_NAME).getSize());

    // Invalidating viewer has no effect
    service.invalidateUserResources(ImmutableSet.of("ViewerX"), appxToken);
    oauth.httpResponse =
        new HttpResponseBuilder(CACHEABLE).setResponseString("NEWCONTENT2").create();
    httpResponse = requestPipeline.execute(signedRequest);
    assertEquals("NEWCONTENT1", httpResponse.getResponseAsString());
    assertEquals("o=1;", httpResponse.getHeader(DefaultInvalidationService.INVALIDATION_HEADER));
    assertEquals(1, cacheProvider.createCache(DefaultHttpCache.CACHE_NAME).getSize());
  }

  @Test
  public void testFetchContentSignedViewer() throws Exception {
    oauth.httpResponse = CACHEABLE;
    signedRequest.getOAuthArguments().setSignOwner(false);
    HttpResponse httpResponse = requestPipeline.execute(signedRequest);
    assertEquals(CACHEABLE, httpResponse);
    assertEquals(1, cacheProvider.createCache(DefaultHttpCache.CACHE_NAME).getSize());

    // Invalidate by owner has no effect
    service.invalidateUserResources(ImmutableSet.of("OwnerX"), appxToken);
    oauth.httpResponse =
        new HttpResponseBuilder(CACHEABLE).setResponseString("NEWCONTENT1").create();
    httpResponse = requestPipeline.execute(signedRequest);
    assertEquals(CACHEABLE, httpResponse);

    // Invalidate the viewer
    service.invalidateUserResources(ImmutableSet.of("ViewerX"), appxToken);
    oauth.httpResponse =
        new HttpResponseBuilder(CACHEABLE).setResponseString("NEWCONTENT2").create();
    httpResponse = requestPipeline.execute(signedRequest);
    assertEquals("NEWCONTENT2", httpResponse.getResponseAsString());
    assertEquals("v=2;", httpResponse.getHeader(DefaultInvalidationService.INVALIDATION_HEADER));
    assertEquals(1, cacheProvider.createCache(DefaultHttpCache.CACHE_NAME).getSize());
  }

  @Test
  public void testServeInvalidatedContentWithFetcherError() throws Exception {
    oauth.httpResponse = CACHEABLE;
    HttpResponse httpResponse = requestPipeline.execute(signedRequest);

    // Invalidate by owner
    service.invalidateUserResources(ImmutableSet.of("OwnerX"), appxToken);

    // Next request returns error
    oauth.httpResponse = HttpResponse.error();
    httpResponse = requestPipeline.execute(signedRequest);
    assertEquals(CACHEABLE, httpResponse);
  }
}
  private void doAsserts(ModulePrefs prefs) {
    assertEquals("title", prefs.getTitle());
    assertEquals(SPEC_URL.resolve(Uri.parse("title_url")), prefs.getTitleUrl());
    assertEquals("description", prefs.getDescription());
    assertEquals("author", prefs.getAuthor());
    assertEquals("author_email", prefs.getAuthorEmail());
    assertEquals(SPEC_URL.resolve(Uri.parse("screenshot")), prefs.getScreenshot());
    assertEquals(SPEC_URL.resolve(Uri.parse("thumbnail")), prefs.getThumbnail());
    assertEquals("directory_title", prefs.getDirectoryTitle());
    assertEquals(1, prefs.getWidth());
    assertEquals(2, prefs.getHeight());
    assertTrue(prefs.getScrolling());
    assertFalse(prefs.getScaling());
    assertEquals("category", prefs.getCategories().get(0));
    assertEquals("category2", prefs.getCategories().get(1));
    assertEquals("author_affiliation", prefs.getAuthorAffiliation());
    assertEquals("author_location", prefs.getAuthorLocation());
    assertEquals(SPEC_URL.resolve(Uri.parse("author_photo")), prefs.getAuthorPhoto());
    assertEquals(SPEC_URL.resolve(Uri.parse("author_link")), prefs.getAuthorLink());
    assertEquals("author_aboutme", prefs.getAuthorAboutme());
    assertEquals("author_quote", prefs.getAuthorQuote());
    assertTrue(prefs.getShowStats());
    assertTrue(prefs.getShowInDirectory());
    assertTrue(prefs.getSingleton());

    assertTrue(prefs.getFeatures().get("require").getRequired());
    assertFalse(prefs.getFeatures().get("optional").getRequired());

    assertEquals("http://example.org", prefs.getPreloads().get(0).getHref().toString());

    assertEquals(1, prefs.getIcons().size());

    assertEquals(1, prefs.getLocales().size());

    assertEquals(Uri.parse("http://example.org/link"), prefs.getLinks().get("link").getHref());

    OAuthService oauth = prefs.getOAuthSpec().getServices().get("serviceOne");
    assertEquals(Uri.parse("http://www.example.com/request"), oauth.getRequestUrl().url);
    assertEquals(OAuthService.Method.GET, oauth.getRequestUrl().method);
    assertEquals(OAuthService.Method.GET, oauth.getAccessUrl().method);
    assertEquals(OAuthService.Location.HEADER, oauth.getAccessUrl().location);
    assertEquals(Uri.parse("http://www.example.com/authorize"), oauth.getAuthorizationUrl());

    Multimap<String, Node> extra = prefs.getExtraElements();

    assertTrue(extra.containsKey("NavigationItem"));
    assertEquals(1, extra.get("NavigationItem").iterator().next().getChildNodes().getLength());
  }
/**
 * Tests for MakeRequestServlet.
 *
 * <p>Tests are trivial; real tests are in MakeRequestHandlerTest.
 */
public class MakeRequestServletTest {
  private static final Uri REQUEST_URL = Uri.parse("http://example.org/file");
  private static final String RESPONSE_BODY = "Hello, world!";
  private static final String ERROR_MESSAGE = "Broken!";
  private static final Enumeration<String> EMPTY_ENUM =
      Collections.enumeration(Collections.<String>emptyList());

  private final ServletTestFixture fixture = new ServletTestFixture();
  private final MakeRequestServlet servlet = new MakeRequestServlet();
  private final MakeRequestHandler handler =
      new MakeRequestHandler(
          fixture.contentFetcherFactory, fixture.securityTokenDecoder, fixture.rewriter);
  private final HttpServletResponseRecorder recorder =
      new HttpServletResponseRecorder(fixture.response);
  private final HttpRequest request = new HttpRequest(REQUEST_URL);
  private final HttpResponse response = new HttpResponse(RESPONSE_BODY);

  @Before
  public void setUp() {
    servlet.setMakeRequestHandler(handler);
    expect(fixture.request.getHeaderNames()).andReturn(EMPTY_ENUM).anyTimes();
    expect(fixture.request.getParameter(MakeRequestHandler.METHOD_PARAM))
        .andReturn("GET")
        .anyTimes();
    expect(fixture.request.getParameter(ProxyBase.URL_PARAM))
        .andReturn(REQUEST_URL.toString())
        .anyTimes();
  }

  private void setupGet() {
    expect(fixture.request.getMethod()).andReturn("GET").anyTimes();
  }

  private void setupPost() {
    expect(fixture.request.getMethod()).andReturn("POST").anyTimes();
  }

  private void assertResponseOk(int expectedStatus, String expectedBody) throws JSONException {
    if (recorder.getHttpStatusCode() == HttpServletResponse.SC_OK) {
      String body = recorder.getResponseAsString();
      assertStartsWith(MakeRequestHandler.UNPARSEABLE_CRUFT, body);
      body = body.substring(MakeRequestHandler.UNPARSEABLE_CRUFT.length());
      JSONObject object = new JSONObject(body);
      object = object.getJSONObject(REQUEST_URL.toString());
      assertEquals(expectedStatus, object.getInt("rc"));
      assertEquals(expectedBody, object.getString("body"));
    } else {
      fail("Invalid response for request.");
    }
  }

  @Test
  public void doGetNormal() throws Exception {
    setupGet();
    expect(fixture.httpFetcher.fetch(request)).andReturn(response);
    fixture.replay();

    servlet.doGet(fixture.request, recorder);

    assertResponseOk(HttpResponse.SC_OK, RESPONSE_BODY);
  }

  @Test
  public void doGetHttpError() throws Exception {
    setupGet();
    expect(fixture.httpFetcher.fetch(request)).andReturn(HttpResponse.notFound());
    fixture.replay();

    servlet.doGet(fixture.request, recorder);

    assertResponseOk(HttpResponse.SC_NOT_FOUND, "");
  }

  @Test
  public void doGetException() throws Exception {
    setupGet();
    expect(fixture.httpFetcher.fetch(request))
        .andThrow(
            new GadgetException(GadgetException.Code.FAILED_TO_RETRIEVE_CONTENT, ERROR_MESSAGE));
    fixture.replay();

    servlet.doGet(fixture.request, recorder);

    assertEquals(HttpServletResponse.SC_BAD_REQUEST, recorder.getHttpStatusCode());
    assertContains(ERROR_MESSAGE, recorder.getResponseAsString());
  }

  @Test
  public void doPostNormal() throws Exception {
    setupPost();
    expect(fixture.httpFetcher.fetch(request)).andReturn(response);
    fixture.replay();

    servlet.doPost(fixture.request, recorder);

    assertResponseOk(HttpResponse.SC_OK, RESPONSE_BODY);
  }

  @Test
  public void doPostHttpError() throws Exception {
    setupPost();
    expect(fixture.httpFetcher.fetch(request)).andReturn(HttpResponse.notFound());
    fixture.replay();

    servlet.doGet(fixture.request, recorder);

    assertResponseOk(HttpResponse.SC_NOT_FOUND, "");
  }

  @Test
  public void doPostException() throws Exception {
    setupPost();
    expect(fixture.httpFetcher.fetch(request))
        .andThrow(
            new GadgetException(GadgetException.Code.FAILED_TO_RETRIEVE_CONTENT, ERROR_MESSAGE));
    fixture.replay();

    servlet.doPost(fixture.request, recorder);

    assertEquals(HttpServletResponse.SC_BAD_REQUEST, recorder.getHttpStatusCode());
    assertContains(ERROR_MESSAGE, recorder.getResponseAsString());
  }
}
 private URI nullSafeToJavaUri(org.apache.shindig.common.uri.Uri shindigUri) {
   if (shindigUri != null) {
     return shindigUri.toJavaUri();
   }
   return null;
 }
 private void expectGetAndReturnHeaders(String url, Map<String, List<String>> headers)
     throws Exception {
   HttpRequest req = new HttpRequest(Uri.parse(url));
   HttpResponse resp = new HttpResponseBuilder().addAllHeaders(headers).create();
   expect(pipeline.execute(req)).andReturn(resp);
 }
 private void expectGetAndReturnData(String url, byte[] data) throws Exception {
   HttpRequest req = new HttpRequest(Uri.parse(url));
   HttpResponse resp = new HttpResponseBuilder().setResponse(data).create();
   expect(pipeline.execute(req)).andReturn(resp);
 }
  public HttpResponse fetch(org.apache.shindig.gadgets.http.HttpRequest request)
      throws GadgetException {
    HttpUriRequest httpMethod = null;
    Preconditions.checkNotNull(request);
    final String methodType = request.getMethod();

    final org.apache.http.HttpResponse response;
    final long started = System.currentTimeMillis();

    // Break the request Uri to its components:
    Uri uri = request.getUri();
    if (StringUtils.isEmpty(uri.getAuthority())) {
      throw new GadgetException(
          GadgetException.Code.INVALID_USER_DATA,
          "Missing domain name for request: " + uri,
          HttpServletResponse.SC_BAD_REQUEST);
    }
    if (StringUtils.isEmpty(uri.getScheme())) {
      throw new GadgetException(
          GadgetException.Code.INVALID_USER_DATA,
          "Missing schema for request: " + uri,
          HttpServletResponse.SC_BAD_REQUEST);
    }
    String[] hostparts = StringUtils.splitPreserveAllTokens(uri.getAuthority(), ':');
    int port = -1; // default port
    if (hostparts.length > 2) {
      throw new GadgetException(
          GadgetException.Code.INVALID_USER_DATA,
          "Bad host name in request: " + uri.getAuthority(),
          HttpServletResponse.SC_BAD_REQUEST);
    }
    if (hostparts.length == 2) {
      try {
        port = Integer.parseInt(hostparts[1]);
      } catch (NumberFormatException e) {
        throw new GadgetException(
            GadgetException.Code.INVALID_USER_DATA,
            "Bad port number in request: " + uri.getAuthority(),
            HttpServletResponse.SC_BAD_REQUEST);
      }
    }

    String requestUri = uri.getPath();
    // Treat path as / if set as null.
    if (uri.getPath() == null) {
      requestUri = "/";
    }
    if (uri.getQuery() != null) {
      requestUri += '?' + uri.getQuery();
    }

    // Get the http host to connect to.
    HttpHost host = new HttpHost(hostparts[0], port, uri.getScheme());

    try {
      if ("POST".equals(methodType) || "PUT".equals(methodType)) {
        HttpEntityEnclosingRequestBase enclosingMethod =
            ("POST".equals(methodType)) ? new HttpPost(requestUri) : new HttpPut(requestUri);

        if (request.getPostBodyLength() > 0) {
          enclosingMethod.setEntity(
              new InputStreamEntity(request.getPostBody(), request.getPostBodyLength()));
        }
        httpMethod = enclosingMethod;
      } else if ("GET".equals(methodType)) {
        httpMethod = new HttpGet(requestUri);
      } else if ("HEAD".equals(methodType)) {
        httpMethod = new HttpHead(requestUri);
      } else if ("DELETE".equals(methodType)) {
        httpMethod = new HttpDelete(requestUri);
      }
      for (Map.Entry<String, List<String>> entry : request.getHeaders().entrySet()) {
        httpMethod.addHeader(entry.getKey(), StringUtils.join(entry.getValue(), ','));
      }

      // Disable following redirects.
      if (!request.getFollowRedirects()) {
        httpMethod.getParams().setBooleanParameter(ClientPNames.HANDLE_REDIRECTS, false);
      }

      // HttpClient doesn't handle all cases when breaking url (specifically '_' in domain)
      // So lets pass it the url parsed:
      response = FETCHER.execute(host, httpMethod);

      if (response == null) {
        throw new IOException("Unknown problem with request");
      }

      long now = System.currentTimeMillis();
      if (now - started > slowResponseWarning) {
        slowResponseWarning(request, started, now);
      }

      return makeResponse(response);

    } catch (Exception e) {
      long now = System.currentTimeMillis();

      // Find timeout exceptions, respond accordingly
      if (TIMEOUT_EXCEPTIONS.contains(e.getClass())) {
        LOG.info(
            "Timeout for "
                + request.getUri()
                + " Exception: "
                + e.getClass().getName()
                + " - "
                + e.getMessage()
                + " - "
                + (now - started)
                + "ms");
        return HttpResponse.timeout();
      }

      LOG.log(
          Level.INFO,
          "Got Exception fetching " + request.getUri() + " - " + (now - started) + "ms",
          e);

      // Separate shindig error from external error
      throw new GadgetException(
          GadgetException.Code.INTERNAL_SERVER_ERROR,
          e,
          HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
    } finally {
      // cleanup any outstanding resources..
      if (httpMethod != null)
        try {
          httpMethod.abort();
        } catch (UnsupportedOperationException e) {
          // ignore
        }
    }
  }
public class PipelineExecutorTest {

  private IMocksControl control;
  private PipelinedDataPreloader preloader;
  private PreloaderService preloaderService;
  private GadgetContext context;
  private PipelineExecutor executor;

  private static final Uri GADGET_URI = Uri.parse("http://example.org/gadget.php");

  private static final String CONTENT =
      "<Content xmlns:os=\"http://ns.opensocial.org/2008/markup\">"
          + "  <os:PeopleRequest key=\"me\" userId=\"canonical\"/>"
          + "  <os:HttpRequest key=\"json\" href=\"test.json\"/>"
          + "</Content>";

  // Two requests, one depends on the other
  private static final String TWO_BATCH_CONTENT =
      "<Content xmlns:os=\"http://ns.opensocial.org/2008/markup\">"
          + "  <os:PeopleRequest key=\"me\" userId=\"${json.user}\"/>"
          + "  <os:HttpRequest key=\"json\" href=\"${ViewParams.file}\"/>"
          + "</Content>";

  // One request, but it requires data that isn\"t present
  private static final String BLOCKED_FIRST_BATCH_CONTENT =
      "<Content xmlns:os=\"http://ns.opensocial.org/2008/markup\">"
          + "  <os:PeopleRequest key=\"me\" userId=\"${json.user}\"/>"
          + "</Content>";

  @Before
  public void setUp() throws Exception {
    control = EasyMock.createStrictControl();
    preloader = control.createMock(PipelinedDataPreloader.class);
    preloaderService = new ConcurrentPreloaderService(Executors.newSingleThreadExecutor(), null);
    executor = new PipelineExecutor(preloader, preloaderService, Expressions.forTesting());

    context = new GadgetContext() {};
  }

  private PipelinedData getPipelinedData(String pipelineXml) throws SpecParserException {
    Element element = XmlUtil.parseSilent(pipelineXml);
    return new PipelinedData(element, GADGET_URI);
  }

  @Test
  public void execute() throws Exception {
    PipelinedData pipeline = getPipelinedData(CONTENT);

    Capture<PipelinedData.Batch> batchCapture = new Capture<PipelinedData.Batch>();

    JSONObject expectedData = new JSONObject("{result: {foo: 'bar'}}");

    // Dummy return results (the "real" return would have two values)
    Callable<PreloadedData> callable = createPreloadTask("key", expectedData.toString());

    // One batch with 1 each HTTP and Social preload
    expect(preloader.createPreloadTasks(same(context), and(eqBatch(1, 1), capture(batchCapture))))
        .andReturn(ImmutableList.of(callable));

    control.replay();

    PipelineExecutor.Results results = executor.execute(context, ImmutableList.of(pipeline));

    // Verify the data set is injected, and the os-data was deleted
    assertTrue(batchCapture.getValue().getPreloads().containsKey("me"));
    assertTrue(batchCapture.getValue().getPreloads().containsKey("json"));

    JsonAssert.assertJsonEquals(
        "[{id: 'key', result: {foo: 'bar'}}]", JsonSerializer.serialize(results.results));
    JsonAssert.assertJsonEquals(
        "{foo: 'bar'}", JsonSerializer.serialize(results.keyedResults.get("key")));
    assertTrue(results.remainingPipelines.isEmpty());

    control.verify();
  }

  @Test
  public void executeWithTwoBatches() throws Exception {
    PipelinedData pipeline = getPipelinedData(TWO_BATCH_CONTENT);

    context =
        new GadgetContext() {
          @Override
          public String getParameter(String property) {
            // Provide the filename to be requested in the first batch
            if ("view-params".equals(property)) {
              return "{'file': 'test.json'}";
            }
            return null;
          }
        };

    // First batch, the HTTP fetch
    Capture<PipelinedData.Batch> firstBatch = new Capture<PipelinedData.Batch>();
    Callable<PreloadedData> firstTask = createPreloadTask("json", "{result: {user: '******'}}");

    // Second batch, the user fetch
    Capture<PipelinedData.Batch> secondBatch = new Capture<PipelinedData.Batch>();
    Callable<PreloadedData> secondTask = createPreloadTask("me", "{result: {'id':'canonical'}}");

    // First, a batch with an HTTP request
    expect(preloader.createPreloadTasks(same(context), and(eqBatch(0, 1), capture(firstBatch))))
        .andReturn(ImmutableList.of(firstTask));
    // Second, a batch with a social request
    expect(preloader.createPreloadTasks(same(context), and(eqBatch(1, 0), capture(secondBatch))))
        .andReturn(ImmutableList.of(secondTask));

    control.replay();

    PipelineExecutor.Results results = executor.execute(context, ImmutableList.of(pipeline));

    JsonAssert.assertJsonEquals(
        "[{id: 'json', result: {user: '******'}}," + "{id: 'me', result: {id: 'canonical'}}]",
        JsonSerializer.serialize(results.results));
    assertEquals(ImmutableSet.of("json", "me"), results.keyedResults.keySet());
    assertTrue(results.remainingPipelines.isEmpty());

    control.verify();

    // Verify the data set is injected, and the os-data was deleted

    // Check the evaluated HTTP request
    RequestAuthenticationInfo request =
        (RequestAuthenticationInfo) firstBatch.getValue().getPreloads().get("json").getData();
    assertEquals("http://example.org/test.json", request.getHref().toString());

    // Check the evaluated person request
    JSONObject personRequest =
        (JSONObject) secondBatch.getValue().getPreloads().get("me").getData();
    assertEquals("canonical", personRequest.getJSONObject("params").getJSONArray("userId").get(0));
  }

  @Test
  public void executeWithBlockedBatch() throws Exception {
    PipelinedData pipeline = getPipelinedData(BLOCKED_FIRST_BATCH_CONTENT);

    // Expect a batch with no content
    expect(preloader.createPreloadTasks(same(context), eqBatch(0, 0)))
        .andReturn(ImmutableList.<Callable<PreloadedData>>of());

    control.replay();

    PipelineExecutor.Results results = executor.execute(context, ImmutableList.of(pipeline));
    assertEquals(0, results.results.size());
    assertTrue(results.keyedResults.isEmpty());
    assertEquals(1, results.remainingPipelines.size());
    assertSame(pipeline, results.remainingPipelines.iterator().next());

    control.verify();
  }

  @Test
  public void executeError() throws Exception {
    PipelinedData pipeline = getPipelinedData(CONTENT);

    Capture<PipelinedData.Batch> batchCapture = new Capture<PipelinedData.Batch>();

    JSONObject expectedData = new JSONObject("{error: {message: 'NO!', code: 500}}");

    // Dummy return results (the "real" return would have two values)
    Callable<PreloadedData> callable = createPreloadTask("key", expectedData.toString());

    // One batch with 1 each HTTP and Social preload
    expect(preloader.createPreloadTasks(same(context), and(eqBatch(1, 1), capture(batchCapture))))
        .andReturn(ImmutableList.of(callable));

    control.replay();

    PipelineExecutor.Results results = executor.execute(context, ImmutableList.of(pipeline));

    // Verify the data set is injected, and the os-data was deleted
    assertTrue(batchCapture.getValue().getPreloads().containsKey("me"));
    assertTrue(batchCapture.getValue().getPreloads().containsKey("json"));

    JsonAssert.assertJsonEquals(
        "[{id: 'key', error: {message: 'NO!', code: 500}}]",
        JsonSerializer.serialize(results.results));
    JsonAssert.assertJsonEquals(
        "{message: 'NO!', code: 500}", JsonSerializer.serialize(results.keyedResults.get("key")));
    assertTrue(results.remainingPipelines.isEmpty());

    control.verify();
  }

  @Test
  public void executePreloadException() throws Exception {
    PipelinedData pipeline = getPipelinedData(CONTENT);
    final PreloadedData willThrow = control.createMock(PreloadedData.class);

    Callable<PreloadedData> callable =
        new Callable<PreloadedData>() {
          public PreloadedData call() throws Exception {
            return willThrow;
          }
        };

    // One batch
    expect(preloader.createPreloadTasks(same(context), isA(PipelinedData.Batch.class)))
        .andReturn(ImmutableList.of(callable));
    // And PreloadedData that throws an exception
    expect(willThrow.toJson()).andThrow(new PreloadException("Failed"));

    control.replay();

    PipelineExecutor.Results results = executor.execute(context, ImmutableList.of(pipeline));

    // The exception is fully handled, and leads to empty results
    assertEquals(0, results.results.size());
    assertTrue(results.keyedResults.isEmpty());
    assertTrue(results.remainingPipelines.isEmpty());

    control.verify();
  }

  /** Match a batch with the specified count of social and HTTP data items */
  private PipelinedData.Batch eqBatch(int socialCount, int httpCount) {
    reportMatcher(new BatchMatcher(socialCount, httpCount));
    return null;
  }

  private static class BatchMatcher implements IArgumentMatcher {
    private final int socialCount;
    private final int httpCount;

    public BatchMatcher(int socialCount, int httpCount) {
      this.socialCount = socialCount;
      this.httpCount = httpCount;
    }

    public void appendTo(StringBuffer buffer) {
      buffer
          .append("eqBuffer[social=")
          .append(socialCount)
          .append(",http=")
          .append(httpCount)
          .append(']');
    }

    public boolean matches(Object obj) {
      if (!(obj instanceof PipelinedData.Batch)) {
        return false;
      }

      PipelinedData.Batch batch = (PipelinedData.Batch) obj;
      int actualSocialCount = 0;
      int actualHttpCount = 0;
      for (PipelinedData.BatchItem item : batch.getPreloads().values()) {
        if (item.getType() == PipelinedData.BatchType.HTTP) {
          actualHttpCount++;
        } else if (item.getType() == PipelinedData.BatchType.SOCIAL) {
          actualSocialCount++;
        }
      }

      return socialCount == actualSocialCount && httpCount == actualHttpCount;
    }
  }
  /** Create a mock Callable for a single preload task */
  private Callable<PreloadedData> createPreloadTask(final String key, String jsonResult)
      throws JSONException {
    final JSONObject value = new JSONObject(jsonResult);
    value.put("id", key);
    final PreloadedData preloadResult =
        new PreloadedData() {
          public Collection<Object> toJson() throws PreloadException {
            return ImmutableList.<Object>of(value);
          }
        };

    Callable<PreloadedData> callable =
        new Callable<PreloadedData>() {
          public PreloadedData call() throws Exception {
            return preloadResult;
          }
        };
    return callable;
  }
}
Exemple #24
0
/** Tests for HtmlRenderer */
public class HtmlRendererTest {
  private static final Uri SPEC_URL = Uri.parse("http://example.org/gadget.xml");
  private static final String BASIC_HTML_CONTENT = "Hello, World!";
  private static final String PROXIED_HTML_CONTENT = "Hello, Universe!";
  private static final Uri PROXIED_HTML_HREF = Uri.parse("http://example.org/proxied.php");
  private static final GadgetContext CONTEXT =
      new GadgetContext() {
        @Override
        public SecurityToken getToken() {
          return new AnonymousSecurityToken();
        }
      };

  private final FakePreloaderService preloaderService = new FakePreloaderService();
  private final FakeProxyRenderer proxyRenderer = new FakeProxyRenderer();
  private final CaptureRewriter captureRewriter = new CaptureRewriter();
  private HtmlRenderer renderer;

  private Gadget makeGadget(String content) throws GadgetException {
    GadgetSpec spec =
        new GadgetSpec(
            SPEC_URL,
            "<Module><ModulePrefs title=''/><Content><![CDATA["
                + content
                + "]]></Content></Module>");

    return new Gadget().setSpec(spec).setContext(CONTEXT).setCurrentView(spec.getView("default"));
  }

  private Gadget makeHrefGadget(String authz) throws Exception {
    Gadget gadget = makeGadget("");
    String doc = "<Content href='" + PROXIED_HTML_HREF + "' authz='" + authz + "'/>";
    View view = new View("proxied", Arrays.asList(XmlUtil.parse(doc)), SPEC_URL);
    gadget.setCurrentView(view);
    return gadget;
  }

  @Before
  public void setUp() throws Exception {
    renderer =
        new HtmlRenderer(
            preloaderService,
            proxyRenderer,
            new GadgetRewritersProvider(ImmutableList.of((GadgetRewriter) captureRewriter)),
            null);
  }

  @Test
  public void renderPlainTypeHtml() throws Exception {
    String content = renderer.render(makeGadget(BASIC_HTML_CONTENT));
    assertEquals(BASIC_HTML_CONTENT, content);
  }

  @Test
  public void renderProxied() throws Exception {
    String content = renderer.render(makeHrefGadget("none"));
    assertEquals(PROXIED_HTML_CONTENT, content);
  }

  @Test
  public void doPreloading() throws Exception {
    renderer.render(makeGadget(BASIC_HTML_CONTENT));
    assertTrue("Preloading not performed.", preloaderService.wasPreloaded);
  }

  @Test
  public void doRewriting() throws Exception {
    renderer.render(makeGadget(BASIC_HTML_CONTENT));
    assertTrue("Rewriting not performed.", captureRewriter.viewWasRewritten());
  }

  private static class FakeProxyRenderer extends ProxyRenderer {
    public FakeProxyRenderer() {
      super(null, null, null);
    }

    @Override
    public String render(Gadget gadget) throws RenderingException, GadgetException {
      return PROXIED_HTML_CONTENT;
    }
  }

  private static class FakePreloaderService implements PreloaderService {
    protected boolean wasPreloaded;
    protected Collection<PreloadedData> preloads;

    protected FakePreloaderService() {}

    public Collection<PreloadedData> preload(Gadget gadget) {
      wasPreloaded = true;
      return preloads;
    }

    public Collection<PreloadedData> preload(Collection<Callable<PreloadedData>> tasks) {
      wasPreloaded = true;
      return preloads;
    }
  }
}
public class ModulePrefsTest {
  private static final Uri SPEC_URL = Uri.parse("http://example.org/g.xml");
  private static final String FULL_XML =
      "<ModulePrefs"
          + " title='title'"
          + " title_url='title_url'"
          + " description='description'"
          + " author='author'"
          + " author_email='author_email'"
          + " screenshot='screenshot'"
          + " thumbnail='thumbnail'"
          + " directory_title='directory_title'"
          + " width='1'"
          + " height='2'"
          + " scrolling='true'"
          + " category='category'"
          + " category2='category2'"
          + " author_affiliation='author_affiliation'"
          + " author_location='author_location'"
          + " author_photo='author_photo'"
          + " author_aboutme='author_aboutme'"
          + " author_quote='author_quote'"
          + " author_link='author_link'"
          + " show_stats='true'"
          + " show_in_directory='true'"
          + " singleton='true'>"
          + "  <Require feature='require'/>"
          + "  <Optional feature='optional'/>"
          + "  <Preload href='http://example.org' authz='signed'/>"
          + "  <Icon/>"
          + "  <Locale/>"
          + "  <Link rel='link' href='http://example.org/link'/>"
          + "  <OAuth>"
          + "    <Service name='serviceOne'>"
          + "      <Request url='http://www.example.com/request'"
          + "          method='GET' param_location='auth-header' />"
          + "      <Authorization url='http://www.example.com/authorize'/>"
          + "      <Access url='http://www.example.com/access' method='GET'"
          + "          param_location='auth-header' />"
          + "    </Service>"
          + "  </OAuth>"
          + "  <NavigationItem title=\"moo\"><AppParameter key=\"test\" value=\"1\"/></NavigationItem>"
          + "</ModulePrefs>";

  private void doAsserts(ModulePrefs prefs) {
    assertEquals("title", prefs.getTitle());
    assertEquals(SPEC_URL.resolve(Uri.parse("title_url")), prefs.getTitleUrl());
    assertEquals("description", prefs.getDescription());
    assertEquals("author", prefs.getAuthor());
    assertEquals("author_email", prefs.getAuthorEmail());
    assertEquals(SPEC_URL.resolve(Uri.parse("screenshot")), prefs.getScreenshot());
    assertEquals(SPEC_URL.resolve(Uri.parse("thumbnail")), prefs.getThumbnail());
    assertEquals("directory_title", prefs.getDirectoryTitle());
    assertEquals(1, prefs.getWidth());
    assertEquals(2, prefs.getHeight());
    assertTrue(prefs.getScrolling());
    assertFalse(prefs.getScaling());
    assertEquals("category", prefs.getCategories().get(0));
    assertEquals("category2", prefs.getCategories().get(1));
    assertEquals("author_affiliation", prefs.getAuthorAffiliation());
    assertEquals("author_location", prefs.getAuthorLocation());
    assertEquals(SPEC_URL.resolve(Uri.parse("author_photo")), prefs.getAuthorPhoto());
    assertEquals(SPEC_URL.resolve(Uri.parse("author_link")), prefs.getAuthorLink());
    assertEquals("author_aboutme", prefs.getAuthorAboutme());
    assertEquals("author_quote", prefs.getAuthorQuote());
    assertTrue(prefs.getShowStats());
    assertTrue(prefs.getShowInDirectory());
    assertTrue(prefs.getSingleton());

    assertTrue(prefs.getFeatures().get("require").getRequired());
    assertFalse(prefs.getFeatures().get("optional").getRequired());

    assertEquals("http://example.org", prefs.getPreloads().get(0).getHref().toString());

    assertEquals(1, prefs.getIcons().size());

    assertEquals(1, prefs.getLocales().size());

    assertEquals(Uri.parse("http://example.org/link"), prefs.getLinks().get("link").getHref());

    OAuthService oauth = prefs.getOAuthSpec().getServices().get("serviceOne");
    assertEquals(Uri.parse("http://www.example.com/request"), oauth.getRequestUrl().url);
    assertEquals(OAuthService.Method.GET, oauth.getRequestUrl().method);
    assertEquals(OAuthService.Method.GET, oauth.getAccessUrl().method);
    assertEquals(OAuthService.Location.HEADER, oauth.getAccessUrl().location);
    assertEquals(Uri.parse("http://www.example.com/authorize"), oauth.getAuthorizationUrl());

    Multimap<String, Node> extra = prefs.getExtraElements();

    assertTrue(extra.containsKey("NavigationItem"));
    assertEquals(1, extra.get("NavigationItem").iterator().next().getChildNodes().getLength());
  }

  @Test
  public void basicElementsParseOk() throws Exception {
    doAsserts(new ModulePrefs(XmlUtil.parse(FULL_XML), SPEC_URL));
  }

  @Test
  public void getAttribute() throws Exception {
    String xml = "<ModulePrefs title='title' some_attribute='attribute' " + "empty_attribute=''/>";
    ModulePrefs prefs = new ModulePrefs(XmlUtil.parse(xml), SPEC_URL);
    assertEquals("title", prefs.getAttribute("title"));
    assertEquals("attribute", prefs.getAttribute("some_attribute"));
    assertEquals("", prefs.getAttribute("empty_attribute"));
    assertNull(prefs.getAttribute("gobbledygook"));
  }

  @Test
  public void getLocale() throws Exception {
    String xml =
        "<ModulePrefs title='locales'>"
            + "  <Locale lang='en' country='UK' messages='en.xml'/>"
            + "  <Locale lang='foo' language_direction='rtl'/>"
            + "</ModulePrefs>";
    ModulePrefs prefs = new ModulePrefs(XmlUtil.parse(xml), SPEC_URL);
    LocaleSpec spec = prefs.getLocale(new Locale("en", "UK"));
    assertEquals("http://example.org/en.xml", spec.getMessages().toString());

    spec = prefs.getLocale(new Locale("foo", "ALL"));
    assertEquals("rtl", spec.getLanguageDirection());

    spec = prefs.getLocale(new Locale("en", "notexist"));
    assertNull(spec);
  }

  @Test
  public void getLinks() throws Exception {
    String link1Rel = "foo";
    String link2Rel = "bar";
    Uri link1Href = Uri.parse("http://example.org/foo");
    Uri link2Href = Uri.parse("/bar");
    String xml =
        "<ModulePrefs title='links'>"
            + "  <Link rel='"
            + link1Rel
            + "' href='"
            + link1Href
            + "'/>"
            + "  <Link rel='"
            + link2Rel
            + "' href='"
            + link2Href
            + "'/>"
            + "</ModulePrefs>";

    ModulePrefs prefs =
        new ModulePrefs(XmlUtil.parse(xml), SPEC_URL).substitute(new Substitutions());

    assertEquals(link1Href, prefs.getLinks().get(link1Rel).getHref());
    assertEquals(SPEC_URL.resolve(link2Href), prefs.getLinks().get(link2Rel).getHref());
  }

  @Test
  public void doSubstitution() throws Exception {
    String xml =
        "<ModulePrefs title='__MSG_title__'>"
            + "  <Icon>__MSG_icon__</Icon>"
            + "  <Link rel='__MSG_rel__' href='__MSG_link_href__'/>"
            + "  <Preload href='__MSG_pre_href__'/>"
            + "</ModulePrefs>";
    String title = "blah";
    String icon = "http://example.org/icon.gif";
    String rel = "foo-bar";
    String linkHref = "http://example.org/link.html";
    String preHref = "http://example.org/preload.html";

    ModulePrefs prefs = new ModulePrefs(XmlUtil.parse(xml), SPEC_URL);
    Substitutions subst = new Substitutions();
    subst.addSubstitution(Substitutions.Type.MESSAGE, "title", title);
    subst.addSubstitution(Substitutions.Type.MESSAGE, "icon", icon);
    subst.addSubstitution(Substitutions.Type.MESSAGE, "rel", rel);
    subst.addSubstitution(Substitutions.Type.MESSAGE, "link_href", linkHref);
    subst.addSubstitution(Substitutions.Type.MESSAGE, "pre_href", preHref);
    prefs = prefs.substitute(subst);

    assertEquals(title, prefs.getTitle());
    assertEquals(icon, prefs.getIcons().get(0).getContent());
    assertEquals(rel, prefs.getLinks().get(rel).getRel());
    assertEquals(linkHref, prefs.getLinks().get(rel).getHref().toString());
    assertEquals(preHref, prefs.getPreloads().get(0).getHref().toString());
  }

  @Test
  public void malformedIntAttributeTreatedAsZero() throws Exception {
    String xml = "<ModulePrefs title='' height='100px' width='foobar' arbitrary='0xff'/>";

    ModulePrefs prefs = new ModulePrefs(XmlUtil.parse(xml), SPEC_URL);

    assertEquals(0, prefs.getHeight());
    assertEquals(0, prefs.getWidth());
    assertEquals(0, prefs.getIntAttribute("arbitrary"));
  }

  @Test
  public void missingTitleOkay() throws Exception {
    String xml = "<ModulePrefs/>";
    ModulePrefs prefs = new ModulePrefs(XmlUtil.parse(xml), SPEC_URL);
    assertNotNull("Empty ModulePrefs Parses", prefs);
    assertEquals("Title is empty string", "", prefs.getTitle());
  }

  @Test
  public void coreInjectedAutomatically() throws Exception {
    String xml = "<ModulePrefs title=''><Require feature='foo'/></ModulePrefs>";
    ModulePrefs prefs = new ModulePrefs(XmlUtil.parse(xml), SPEC_URL);
    assertEquals(2, prefs.getFeatures().size());
    assertTrue(prefs.getFeatures().containsKey("foo"));
    assertTrue(prefs.getFeatures().containsKey("core"));
  }

  @Test
  public void coreNotInjectedOnSplitCoreInclusion() throws Exception {
    String xml = "<ModulePrefs title=''><Require feature='core.config'/></ModulePrefs>";
    ModulePrefs prefs = new ModulePrefs(XmlUtil.parse(xml), SPEC_URL);
    assertEquals(1, prefs.getFeatures().size());
    assertTrue(prefs.getFeatures().containsKey("core.config"));
  }

  @Test
  public void securityTokenInjectedOnOAuthTag() throws Exception {
    String xml =
        "<ModulePrefs title=''>"
            + "  <OAuth>"
            + "    <Service name='serviceOne'>"
            + "      <Request url='http://www.example.com/request'"
            + "          method='GET' param_location='auth-header' />"
            + "      <Authorization url='http://www.example.com/authorize'/>"
            + "      <Access url='http://www.example.com/access' method='GET'"
            + "          param_location='auth-header' />"
            + "    </Service>"
            + "  </OAuth>"
            + "</ModulePrefs>";
    ModulePrefs prefs = new ModulePrefs(XmlUtil.parse(xml), SPEC_URL);
    assertEquals(2, prefs.getFeatures().size());
    assertTrue(prefs.getFeatures().containsKey("core"));
    assertTrue(prefs.getFeatures().containsKey("security-token"));
  }

  @Test
  public void toStringIsSane() throws Exception {
    ModulePrefs prefs = new ModulePrefs(XmlUtil.parse(FULL_XML), SPEC_URL);
    doAsserts(new ModulePrefs(XmlUtil.parse(prefs.toString()), SPEC_URL));
  }

  @Test
  public void needsUserPrefSubstInTitle() throws Exception {
    String xml = "<ModulePrefs title='Title __UP_foo__'/>";
    ModulePrefs prefs = new ModulePrefs(XmlUtil.parse(xml), SPEC_URL);
    assertTrue(prefs.needsUserPrefSubstitution());
  }

  @Test
  public void needsUserPrefSubstInTitleUrl() throws Exception {
    String xml = "<ModulePrefs title='foo' title_url='http://__UP_url__'/>";
    ModulePrefs prefs = new ModulePrefs(XmlUtil.parse(xml), SPEC_URL);
    assertTrue(prefs.needsUserPrefSubstitution());
  }

  @Test
  public void needsUserPrefSubstInPreload() throws Exception {
    String xml =
        "<ModulePrefs title='foo'>" + "  <Preload href='__UP_foo__' authz='signed'/></ModulePrefs>";
    ModulePrefs prefs = new ModulePrefs(XmlUtil.parse(xml), SPEC_URL);
    assertTrue(prefs.needsUserPrefSubstitution());
  }
}