View Javadoc
1   package net.trajano.wagon.git;
2   
3   import java.io.File;
4   import java.io.IOException;
5   import java.net.HttpURLConnection;
6   import java.net.URI;
7   import java.net.URISyntaxException;
8   import java.util.ResourceBundle;
9   import java.util.logging.Level;
10  import java.util.logging.Logger;
11  import java.util.regex.Matcher;
12  import java.util.regex.Pattern;
13  
14  import net.trajano.wagon.git.internal.AbstractGitWagon;
15  import net.trajano.wagon.git.internal.GitUri;
16  
17  import org.apache.maven.wagon.ResourceDoesNotExistException;
18  import org.apache.maven.wagon.Wagon;
19  import org.codehaus.plexus.component.annotations.Component;
20  import org.eclipse.jgit.api.Git;
21  import org.eclipse.jgit.api.errors.GitAPIException;
22  import org.xbill.DNS.CNAMERecord;
23  import org.xbill.DNS.Lookup;
24  import org.xbill.DNS.TextParseException;
25  import org.xbill.DNS.Type;
26  
27  /**
28   * Github Pages Wagon.
29   */
30  @Component(role = Wagon.class, hint = "github", instantiationStrategy = "per-lookup")
31  public class GitHubPagesWagon extends AbstractGitWagon {
32  
33      /**
34       * Github pages host pattern.
35       */
36      private static final Pattern GITHUB_PAGES_HOST_PATTERN = Pattern
37              .compile("([a-z]+)\\.github\\.io.?");
38  
39      /**
40       * Github pages path pattern.
41       */
42      private static final Pattern GITHUB_PAGES_PATH_PATTERN = Pattern
43              .compile("/([^/]+)(/.*)?");
44  
45      /**
46       * Logger.
47       */
48      private static final Logger LOG;
49  
50      /**
51       * Messages resource path.
52       */
53      private static final String MESSAGES = "META-INF/Messages";
54  
55      /**
56       * Resource bundle.
57       */
58      private static final ResourceBundle R;
59  
60      static {
61          LOG = Logger.getLogger("net.trajano.wagon.git", MESSAGES);
62          R = ResourceBundle.getBundle(MESSAGES);
63      }
64  
65      /**
66       * Builds a GitUri from a GitHub Pages URL. It performs a DNS lookup for the
67       * CNAME if the host does not match {@link #GITHUB_PAGES_HOST_PATTERN}.
68       * {@inheritDoc}
69       */
70      @Override
71      public GitUri buildGitUri(final URI uri) throws IOException,
72              URISyntaxException {
73          final URI finalUri;
74          // Resolve redirects if needed.
75          if ("http".equals(uri.getScheme()) || "https".equals(uri.getScheme())) {
76              final HttpURLConnection urlConnection = (HttpURLConnection) uri
77                      .toURL().openConnection();
78              urlConnection.connect();
79              urlConnection.getResponseCode();
80              finalUri = urlConnection.getURL().toURI();
81              urlConnection.disconnect();
82          } else {
83              finalUri = uri;
84          }
85          final Matcher m = GITHUB_PAGES_HOST_PATTERN.matcher(finalUri.getHost());
86          final String username;
87          if (m.matches()) {
88              username = m.group(1);
89          } else {
90              final String cnameHost = getCnameForHost(finalUri.getHost());
91              final Matcher m2 = GITHUB_PAGES_HOST_PATTERN.matcher(cnameHost);
92              if (!m2.matches()) {
93                  throw new RuntimeException(String.format(
94                          R.getString("invalidGitHubPagesHost"), uri));
95              }
96              username = m2.group(1);
97          }
98  
99          if ("".equals(finalUri.getPath()) || "/".equals(finalUri.getPath())) {
100             return buildRootUri(username);
101         } else {
102             return buildProjectUri(username, finalUri.getPath());
103         }
104     }
105 
106     /**
107      * Builds the project Github URI.
108      *
109      * @param username
110      *            user name
111      * @param path
112      *            path
113      * @return URI
114      */
115     private GitUri buildProjectUri(final String username, final String path) {
116         final Matcher pathMatcher = GITHUB_PAGES_PATH_PATTERN.matcher(path);
117         pathMatcher.matches();
118         final String resource = pathMatcher.group(2);
119         return new GitUri("ssh://git@github.com/" + username + "/"
120                 + pathMatcher.group(1) + ".git", "gh-pages", resource);
121     }
122 
123     /**
124      * Builds the user's Github URI.
125      *
126      * @param username
127      *            user name
128      * @return URI
129      */
130     private GitUri buildRootUri(final String username) {
131         return new GitUri("ssh://git@github.com/" + username + "/" + username
132                 + ".github.io.git", "master", "/");
133     }
134 
135     /**
136      * Gets the CNAME record for the host.
137      *
138      * @param host
139      *            host
140      * @return CNAME record may return null if not found.
141      * @throws TextParseException
142      */
143     private String getCnameForHost(final String host) throws TextParseException {
144         final Lookup lookup = new Lookup(host, Type.CNAME);
145         lookup.run();
146         if (lookup.getAnswers().length == 0) {
147             LOG.log(Level.SEVERE, "unableToFindCNAME", new Object[] { host });
148             return null;
149         }
150         return ((CNAMERecord) lookup.getAnswers()[0]).getTarget().toString();
151     }
152 
153     /**
154      * Does resolution a different way.
155      *
156      * @param resourceName
157      *            resource name
158      * @return file for the resource.
159      * @throws ResourceDoesNotExistException
160      */
161     @Override
162     public File getFileForResource(final String resourceName)
163             throws GitAPIException, IOException, URISyntaxException {
164         // /foo/bar/foo.git + ../bar.git == /foo/bar/bar.git + /
165         // /foo/bar/foo.git + ../bar.git/abc == /foo/bar/bar.git + /abc
166         final GitUri resolved = buildGitUri(URI.create(
167                 URI.create(getRepository().getUrl()).getSchemeSpecificPart())
168                 .resolve(resourceName.replace(" ", "%20")));
169         Git resourceGit;
170         try {
171             resourceGit = getGit(resolved.getGitRepositoryUri());
172         } catch (final ResourceDoesNotExistException e) {
173             return null;
174         }
175 
176         final File workTree = resourceGit.getRepository().getWorkTree();
177         final File resolvedFile = new File(workTree, resolved.getResource());
178         if (!resolvedFile.getCanonicalPath().startsWith(
179                 workTree.getCanonicalPath())) {
180             throw new IOException(String.format(
181                     "The resolved file '%s' is not in work tree '%s'",
182                     resolvedFile, workTree));
183         }
184         return resolvedFile;
185     }
186 }